aboutsummaryrefslogtreecommitdiffhomepage
path: root/patch-remap/mache-spigotflower-stripped/net
diff options
context:
space:
mode:
authorMiniDigger | Martin <[email protected]>2024-01-14 11:04:49 +0100
committerMiniDigger | Martin <[email protected]>2024-01-14 11:04:49 +0100
commitbee74680e607c2e29b038329f62181238911cd83 (patch)
tree708fd1a4a0227d9071243adf2a42d5e9e96cde4a /patch-remap/mache-spigotflower-stripped/net
parent0a44692ef6ff6e255d48eb3ba1bb114166eafda9 (diff)
downloadPaper-bee74680e607c2e29b038329f62181238911cd83.tar.gz
Paper-bee74680e607c2e29b038329f62181238911cd83.zip
add remapped patches as a testsoftspoon
Diffstat (limited to 'patch-remap/mache-spigotflower-stripped/net')
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/CrashReport.java.patch16
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/advancements/AdvancementHolder.java.patch24
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/advancements/AdvancementTree.java.patch11
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/commands/CommandSource.java.patch23
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/commands/CommandSourceStack.java.patch68
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/commands/Commands.java.patch171
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/commands/arguments/EntityArgument.java.patch23
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/commands/arguments/blocks/BlockStateParser.java.patch11
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/commands/arguments/selector/EntitySelector.java.patch13
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/commands/arguments/selector/EntitySelectorParser.java.patch37
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/core/cauldron/CauldronInteraction.java.patch234
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/core/dispenser/AbstractProjectileDispenseBehavior.java.patch58
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch61
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch113
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch687
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java.patch84
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java.patch46
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/nbt/ByteArrayTag.java.patch1
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/nbt/IntArrayTag.java.patch1
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/nbt/NbtIo.java.patch1
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/network/Connection.java.patch10
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/network/FriendlyByteBuf.java.patch43
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/network/chat/Component.java.patch27
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/network/chat/TextColor.java.patch32
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/PacketUtils.java.patch47
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch44
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/game/ClientboundInitializeBorderPacket.java.patch29
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java.patch25
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/game/ClientboundSetBorderCenterPacket.java.patch17
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java.patch1
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/game/ServerboundUseItemOnPacket.java.patch1
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/game/ServerboundUseItemPacket.java.patch1
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java.patch1
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/network/syncher/SynchedEntityData.java.patch35
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/Bootstrap.java.patch96
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/Main.java.patch194
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/MinecraftServer.java.patch682
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/PlayerAdvancements.java.patch20
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/ServerAdvancementManager.java.patch1
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/ServerFunctionManager.java.patch11
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/ServerScoreboard.java.patch176
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/ServerTickRateManager.java.patch53
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/bossevents/CustomBossEvent.java.patch37
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/DifficultyCommand.java.patch23
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/EffectCommands.java.patch29
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/GameRuleCommand.java.patch26
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/GiveCommand.java.patch1
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/ListPlayersCommand.java.patch24
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/LootCommand.java.patch14
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/PlaceCommand.java.patch16
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/ReloadCommand.java.patch25
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/ScheduleCommand.java.patch15
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/SetSpawnCommand.java.patch11
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/SpreadPlayersCommand.java.patch11
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/SummonCommand.java.patch11
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/TeleportCommand.java.patch53
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/TimeCommand.java.patch59
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/WorldBorderCommand.java.patch79
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/dedicated/DedicatedServer.java.patch250
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/dedicated/DedicatedServerProperties.java.patch53
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/dedicated/DedicatedServerSettings.java.patch27
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/dedicated/Settings.java.patch102
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/gui/MinecraftServerGui.java.patch21
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ChunkHolder.java.patch122
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ChunkMap.java.patch147
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/DistanceManager.java.patch149
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ServerChunkCache.java.patch139
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ServerEntity.java.patch146
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ServerLevel.java.patch657
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ServerPlayer.java.patch1159
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ServerPlayerGameMode.java.patch334
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/TicketType.java.patch11
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/WorldGenRegion.java.patch27
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/LegacyQueryHandler.java.patch45
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch208
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch30
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/ServerConnectionListener.java.patch24
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch1682
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch78
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch125
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch131
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/players/BanListEntry.java.patch36
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/players/GameProfileCache.java.patch1
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/players/OldUsersConverter.java.patch71
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/players/PlayerList.java.patch856
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/players/SleepStatus.java.patch48
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/players/StoredUserList.java.patch1
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/players/UserBanListEntry.java.patch1
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/rcon/RconConsoleSource.java.patch46
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/server/rcon/thread/RconClient.java.patch74
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/stats/ServerRecipeBook.java.patch28
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/stats/ServerStatsCounter.java.patch1
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/stats/StatsCounter.java.patch15
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/util/SpawnUtil.java.patch31
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/util/datafix/DataFixers.java.patch29
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java.patch11
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java.patch11
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/util/worldupdate/WorldUpgrader.java.patch37
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/CompoundContainer.java.patch57
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/Container.java.patch56
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/LockCode.java.patch37
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/SimpleContainer.java.patch76
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/damagesource/DamageSource.java.patch43
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/damagesource/DamageSources.java.patch20
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/effect/HealOrHarmMobEffect.java.patch31
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/effect/HungerMobEffect.java.patch11
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/effect/MobEffectUtil.java.patch29
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/effect/PoisonMobEffect.java.patch18
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/effect/RegenerationMobEffect.java.patch18
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/effect/SaturationMobEffect.java.patch30
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/AgeableMob.java.patch49
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/AreaEffectCloud.java.patch49
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/Entity.java.patch1002
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/EntitySelector.java.patch13
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/EntityType.java.patch87
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ExperienceOrb.java.patch121
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/Interaction.java.patch39
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ItemBasedSteering.java.patch18
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/LightningBolt.java.patch55
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/LivingEntity.java.patch921
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/Mob.java.patch349
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/NeutralMob.java.patch20
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/PathfinderMob.java.patch28
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/attributes/Attributes.java.patch1
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java.patch33
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java.patch40
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java.patch35
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java.patch32
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java.patch47
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java.patch40
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java.patch31
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/ResetProfession.java.patch35
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/StartAttacking.java.patch33
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java.patch36
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java.patch40
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java.patch15
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/goal/EatBlockGoal.java.patch40
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java.patch30
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java.patch29
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java.patch24
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/goal/SitWhenOrderedToGoal.java.patch11
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/goal/TemptGoal.java.patch40
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/sensing/TemptingSensor.java.patch35
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/village/VillageSiege.java.patch13
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ambient/Bat.java.patch46
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Animal.java.patch115
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Bee.java.patch115
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Bucketable.java.patch41
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Cat.java.patch39
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Chicken.java.patch13
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Cow.java.patch33
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Dolphin.java.patch64
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Fox.java.patch62
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/IronGolem.java.patch13
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/MushroomCow.java.patch73
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Ocelot.java.patch11
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Panda.java.patch39
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Parrot.java.patch46
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Pig.java.patch30
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Pufferfish.java.patch20
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Rabbit.java.patch40
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Sheep.java.patch73
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/SnowGolem.java.patch60
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Turtle.java.patch45
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Wolf.java.patch76
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/allay/Allay.java.patch75
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/axolotl/Axolotl.java.patch33
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/frog/Tadpole.java.patch18
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/goat/Goat.java.patch32
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch105
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/horse/Llama.java.patch19
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java.patch41
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/horse/TraderLlama.java.patch11
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/sniffer/Sniffer.java.patch50
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch53
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch231
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch36
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch108
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/decoration/ArmorStand.java.patch183
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/decoration/HangingEntity.java.patch152
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/decoration/ItemFrame.java.patch65
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java.patch65
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/item/FallingBlockEntity.java.patch65
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/item/ItemEntity.java.patch162
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/item/PrimedTnt.java.patch57
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch25
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/CaveSpider.java.patch11
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Creeper.java.patch98
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Drowned.java.patch20
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/ElderGuardian.java.patch13
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/EnderMan.java.patch75
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Evoker.java.patch13
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Ghast.java.patch15
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Guardian.java.patch23
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Husk.java.patch11
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Illusioner.java.patch20
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Phantom.java.patch13
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Ravager.java.patch27
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Shulker.java.patch47
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Silverfish.java.patch45
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Skeleton.java.patch11
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Slime.java.patch67
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/SpellcasterIllager.java.patch24
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Spider.java.patch13
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Strider.java.patch18
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Vex.java.patch11
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Witch.java.patch11
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/WitherSkeleton.java.patch11
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Zombie.java.patch186
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/ZombieVillager.java.patch99
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/ZombifiedPiglin.java.patch62
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java.patch13
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/piglin/Piglin.java.patch117
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/piglin/PiglinAi.java.patch134
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/warden/Warden.java.patch11
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/npc/AbstractVillager.java.patch55
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/npc/InventoryCarrier.java.patch18
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/npc/Villager.java.patch80
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/npc/WanderingTrader.java.patch47
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch36
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/player/Inventory.java.patch100
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/player/Player.java.patch519
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/AbstractArrow.java.patch62
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java.patch62
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/Arrow.java.patch21
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/EvokerFangs.java.patch17
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/EyeOfEnder.java.patch24
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/Fireball.java.patch10
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch80
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/FishingHook.java.patch184
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/LargeFireball.java.patch60
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/LlamaSpit.java.patch17
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/Projectile.java.patch69
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ShulkerBullet.java.patch61
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/SmallFireball.java.patch58
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/SpectralArrow.java.patch11
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ThrowableItemProjectile.java.patch17
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ThrowableProjectile.java.patch13
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ThrownEgg.java.patch73
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch56
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java.patch22
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ThrownPotion.java.patch169
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ThrownTrident.java.patch19
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/WindCharge.java.patch1
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/WitherSkull.java.patch47
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/raid/Raid.java.patch155
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/raid/Raider.java.patch31
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/raid/Raids.java.patch40
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/vehicle/AbstractMinecart.java.patch207
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java.patch51
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/vehicle/Boat.java.patch118
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/vehicle/ChestBoat.java.patch68
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java.patch15
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/vehicle/MinecartTNT.java.patch29
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/vehicle/VehicleEntity.java.patch64
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/food/FoodData.java.patch100
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/AbstractContainerMenu.java.patch194
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/AbstractFurnaceMenu.java.patch55
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/AnvilMenu.java.patch138
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/BeaconMenu.java.patch58
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/BrewingStandMenu.java.patch82
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/CartographyTableMenu.java.patch76
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/ChestMenu.java.patch45
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/ContainerLevelAccess.java.patch42
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/CrafterMenu.java.patch38
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/CraftingMenu.java.patch81
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/DispenserMenu.java.patch66
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/EnchantmentMenu.java.patch198
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/FurnaceResultSlot.java.patch11
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/GrindstoneMenu.java.patch109
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/HopperMenu.java.patch49
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/HorseInventoryMenu.java.patch38
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/InventoryMenu.java.patch62
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/ItemCombinerMenu.java.patch14
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/LecternMenu.java.patch87
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/LoomMenu.java.patch92
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/MenuType.java.patch13
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/MerchantContainer.java.patch63
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/MerchantMenu.java.patch50
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/PlayerEnderChestContainer.java.patch26
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/ResultContainer.java.patch54
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/ShulkerBoxMenu.java.patch42
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/SmithingMenu.java.patch60
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/StonecutterMenu.java.patch64
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/TransientCraftingContainer.java.patch74
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/ArmorItem.java.patch55
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/ArmorStandItem.java.patch22
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/BlockItem.java.patch125
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/BoatItem.java.patch42
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/BoneMealItem.java.patch48
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/BowItem.java.patch35
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/BucketItem.java.patch107
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/ChorusFruitItem.java.patch33
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/CrossbowItem.java.patch33
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/DebugStickItem.java.patch1
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/DyeItem.java.patch31
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/EggItem.java.patch27
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/EndCrystalItem.java.patch22
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/EnderEyeItem.java.patch21
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/EnderpearlItem.java.patch23
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/FireChargeItem.java.patch46
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/FishingRodItem.java.patch34
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/FlintAndSteelItem.java.patch51
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/HangingEntityItem.java.patch36
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/ItemStack.java.patch324
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/LeadItem.java.patch82
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/MapItem.java.patch11
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/MilkBucketItem.java.patch11
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/MinecartItem.java.patch73
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/PotionItem.java.patch11
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/RecordItem.java.patch16
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/SignItem.java.patch27
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/SnowballItem.java.patch26
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/SpawnEggItem.java.patch17
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/StandingAndWallBlockItem.java.patch34
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/TridentItem.java.patch63
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/BlastingRecipe.java.patch35
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/CampfireCookingRecipe.java.patch35
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/CustomRecipe.java.patch26
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/Ingredient.java.patch28
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/Recipe.java.patch9
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/RecipeHolder.java.patch21
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/RecipeManager.java.patch161
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/ShapedRecipe.java.patch85
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/ShapelessRecipe.java.patch38
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/SmeltingRecipe.java.patch35
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/SmithingTransformRecipe.java.patch37
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/SmithingTrimRecipe.java.patch29
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/SmokingRecipe.java.patch35
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/StonecutterRecipe.java.patch34
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/enchantment/DamageEnchantment.java.patch11
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/enchantment/FrostWalkerEnchantment.java.patch18
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/trading/Merchant.java.patch9
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/trading/MerchantOffer.java.patch73
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/BaseCommandBlock.java.patch22
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/BaseSpawner.java.patch30
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/BlockGetter.java.patch49
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/ClipContext.java.patch13
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/Explosion.java.patch189
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/GameRules.java.patch1
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/Level.java.patch316
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/LevelAccessor.java.patch9
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/LevelWriter.java.patch13
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/NaturalSpawner.java.patch101
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/ServerLevelAccessor.java.patch21
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/AbstractCandleBlock.java.patch21
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BambooSaplingBlock.java.patch12
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BambooStalkBlock.java.patch63
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BaseFireBlock.java.patch50
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BasePressurePlateBlock.java.patch48
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BedBlock.java.patch87
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BeehiveBlock.java.patch11
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BellBlock.java.patch14
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BigDripleafBlock.java.patch115
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/Block.java.patch52
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BuddingAmethystBlock.java.patch11
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BushBlock.java.patch21
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ButtonBlock.java.patch80
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CactusBlock.java.patch34
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CakeBlock.java.patch27
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CampfireBlock.java.patch43
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CarvedPumpkinBlock.java.patch34
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CauldronBlock.java.patch34
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CaveVines.java.patch44
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CeilingHangingSignBlock.java.patch1
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ChangeOverTimeBlock.java.patch13
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ChestBlock.java.patch71
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ChorusFlowerBlock.java.patch81
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CocoaBlock.java.patch22
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CommandBlock.java.patch40
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ComparatorBlock.java.patch32
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ComposterBlock.java.patch134
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ConcretePowderBlock.java.patch85
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CoralBlock.java.patch21
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CoralFanBlock.java.patch21
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CoralPlantBlock.java.patch21
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CoralWallFanBlock.java.patch21
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CropBlock.java.patch44
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DaylightDetectorBlock.java.patch14
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DetectorRailBlock.java.patch30
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DiodeBlock.java.patch32
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DispenserBlock.java.patch22
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DoorBlock.java.patch46
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DoublePlantBlock.java.patch18
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DragonEggBlock.java.patch31
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DropExperienceBlock.java.patch22
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DropperBlock.java.patch47
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/EndPortalBlock.java.patch46
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/FarmBlock.java.patch75
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/FenceGateBlock.java.patch22
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/FireBlock.java.patch166
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/FungusBlock.java.patch23
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/GrowingPlantHeadBlock.java.patch13
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/IceBlock.java.patch16
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/InfestedBlock.java.patch25
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/LayeredCauldronBlock.java.patch100
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/LeavesBlock.java.patch34
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/LecternBlock.java.patch29
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/LeverBlock.java.patch40
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/LightningRodBlock.java.patch53
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/LiquidBlock.java.patch28
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/MagmaBlock.java.patch12
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/MultifaceSpreader.java.patch50
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/MushroomBlock.java.patch35
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/NetherPortalBlock.java.patch37
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/NetherWartBlock.java.patch15
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/NoteBlock.java.patch33
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/NyliumBlock.java.patch21
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ObserverBlock.java.patch39
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/PointedDripstoneBlock.java.patch81
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/PowderSnowBlock.java.patch18
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/PoweredRailBlock.java.patch33
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/PressurePlateBlock.java.patch56
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/RedStoneOreBlock.java.patch115
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/RedStoneWireBlock.java.patch32
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/RedstoneLampBlock.java.patch43
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/RedstoneTorchBlock.java.patch59
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/RespawnAnchorBlock.java.patch15
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/RootedDirtBlock.java.patch13
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SaplingBlock.java.patch60
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ScaffoldingBlock.java.patch15
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SculkBlock.java.patch16
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SculkCatalystBlock.java.patch23
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SculkSensorBlock.java.patch116
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SculkShriekerBlock.java.patch38
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SculkSpreader.java.patch43
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SculkVeinBlock.java.patch46
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SignBlock.java.patch13
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SnowLayerBlock.java.patch23
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SpawnerBlock.java.patch23
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SpongeBlock.java.patch116
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java.patch32
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/StemBlock.java.patch45
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SugarCaneBlock.java.patch13
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SweetBerryBushBlock.java.patch58
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/TntBlock.java.patch92
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/TrapDoorBlock.java.patch39
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/TripWireBlock.java.patch51
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/TripWireHookBlock.java.patch39
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/TurtleEggBlock.java.patch83
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/VineBlock.java.patch73
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/WallHangingSignBlock.java.patch1
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/WaterlilyBlock.java.patch26
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/WeightedPressurePlateBlock.java.patch44
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/WitherRoseBlock.java.patch13
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/WitherSkullBlock.java.patch62
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch244
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BannerBlockEntity.java.patch16
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch52
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java.patch15
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch80
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java.patch174
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BellBlockEntity.java.patch21
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BlockEntity.java.patch70
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch169
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BrushableBlockEntity.java.patch44
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/CampfireBlockEntity.java.patch59
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/ChestBlockEntity.java.patch67
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch112
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/CommandBlockEntity.java.patch16
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch42
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch82
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/CrafterBlockEntity.java.patch67
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java.patch62
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch54
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/HopperBlockEntity.java.patch211
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch93
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch163
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java.patch47
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch71
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/SignBlockEntity.java.patch131
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch21
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java.patch69
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch89
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/state/BlockBehaviour.java.patch15
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/border/WorldBorder.java.patch22
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/ChunkAccess.java.patch102
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/ChunkGenerator.java.patch74
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java.patch1
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/ChunkStatus.java.patch15
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/DataLayer.java.patch1
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/LevelChunk.java.patch185
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/LevelChunkSection.java.patch37
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/storage/ChunkSerializer.java.patch94
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch91
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/storage/RegionFile.java.patch1
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch72
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch46
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/entity/PersistentEntitySectionManager.java.patch110
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/gameevent/GameEventDispatcher.java.patch48
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java.patch33
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/FlatLevelSource.java.patch26
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java.patch1
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/PatrolSpawner.java.patch15
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java.patch25
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/StructurePiece.java.patch128
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/StructureStart.java.patch44
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java.patch28
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java.patch18
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java.patch27
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java.patch24
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java.patch24
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java.patch34
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java.patch33
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java.patch36
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java.patch25
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java.patch157
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/material/FlowingFluid.java.patch104
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/material/LavaFluid.java.patch58
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/portal/PortalForcer.java.patch109
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/portal/PortalInfo.java.patch31
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/portal/PortalShape.java.patch146
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/redstone/NeighborUpdater.java.patch36
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java.patch173
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/LevelStorageSource.java.patch91
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/PlayerDataStorage.java.patch70
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/PrimaryLevelData.java.patch159
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/loot/LootDataManager.java.patch16
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/loot/LootTable.java.patch55
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/loot/functions/LootingEnchantFunction.java.patch19
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/loot/parameters/LootContextParams.java.patch10
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/loot/predicates/ExplosionCondition.java.patch12
-rw-r--r--patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/loot/predicates/LootItemRandomChanceWithLootingCondition.java.patch14
523 files changed, 35426 insertions, 0 deletions
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/CrashReport.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/CrashReport.java.patch
new file mode 100644
index 0000000000..dc7d90482e
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/CrashReport.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/CrashReport.java
++++ b/net/minecraft/CrashReport.java
+@@ -33,9 +33,10 @@
+ private StackTraceElement[] uncategorizedStackTrace = new StackTraceElement[0];
+ private final SystemReport systemReport = new SystemReport();
+
+- public CrashReport(String s, Throwable throwable) {
+- this.title = s;
+- this.exception = throwable;
++ public CrashReport(String title, Throwable exception) {
++ this.title = title;
++ this.exception = exception;
++ this.systemReport.setDetail("CraftBukkit Information", new org.bukkit.craftbukkit.CraftCrashReport()); // CraftBukkit
+ }
+
+ public String getTitle() {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/advancements/AdvancementHolder.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/advancements/AdvancementHolder.java.patch
new file mode 100644
index 0000000000..29bef626ac
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/advancements/AdvancementHolder.java.patch
@@ -0,0 +1,24 @@
+--- a/net/minecraft/advancements/AdvancementHolder.java
++++ b/net/minecraft/advancements/AdvancementHolder.java
+@@ -2,6 +2,10 @@
+
+ import net.minecraft.network.FriendlyByteBuf;
+ import net.minecraft.resources.ResourceLocation;
++// CraftBukkit start
++import org.bukkit.craftbukkit.advancement.CraftAdvancement;
++import org.bukkit.craftbukkit.util.CraftNamespacedKey;
++// CraftBukkit end
+
+ public record AdvancementHolder(ResourceLocation id, Advancement value) {
+
+@@ -44,4 +45,10 @@
+ public String toString() {
+ return this.id.toString();
+ }
++
++ // CraftBukkit start
++ public final org.bukkit.advancement.Advancement toBukkit() {
++ return new CraftAdvancement(this);
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/advancements/AdvancementTree.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/advancements/AdvancementTree.java.patch
new file mode 100644
index 0000000000..a09551dbaa
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/advancements/AdvancementTree.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/advancements/AdvancementTree.java
++++ b/net/minecraft/advancements/AdvancementTree.java
+@@ -77,7 +77,7 @@
+ }
+ }
+
+- AdvancementTree.LOGGER.info("Loaded {} advancements", this.nodes.size());
++ // AdvancementTree.LOGGER.info("Loaded {} advancements", this.nodes.size()); // CraftBukkit - moved to AdvancementDataWorld#reload
+ }
+
+ private boolean tryInsert(AdvancementHolder advancementholder) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/commands/CommandSource.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/commands/CommandSource.java.patch
new file mode 100644
index 0000000000..8de3d6e279
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/commands/CommandSource.java.patch
@@ -0,0 +1,23 @@
+--- a/net/minecraft/commands/CommandSource.java
++++ b/net/minecraft/commands/CommandSource.java
+@@ -26,6 +22,13 @@
+ public boolean shouldInformAdmins() {
+ return false;
+ }
++
++ // CraftBukkit start
++ @Override
++ public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) {
++ throw new UnsupportedOperationException("Not supported yet.");
++ }
++ // CraftBukkit end
+ };
+
+ void sendSystemMessage(Component component);
+@@ -39,4 +42,6 @@
+ default boolean alwaysAccepts() {
+ return false;
+ }
++
++ org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper); // CraftBukkit
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/commands/CommandSourceStack.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/commands/CommandSourceStack.java.patch
new file mode 100644
index 0000000000..6427ceeaac
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/commands/CommandSourceStack.java.patch
@@ -0,0 +1,68 @@
+--- a/net/minecraft/commands/CommandSourceStack.java
++++ b/net/minecraft/commands/CommandSourceStack.java
+@@ -44,6 +43,7 @@
+ import net.minecraft.world.level.dimension.DimensionType;
+ import net.minecraft.world.phys.Vec2;
+ import net.minecraft.world.phys.Vec3;
++import com.mojang.brigadier.tree.CommandNode; // CraftBukkit
+
+ public class CommandSourceStack implements ExecutionCommandSource<CommandSourceStack>, SharedSuggestionProvider {
+
+@@ -64,6 +64,7 @@
+ private final Vec2 rotation;
+ private final CommandSigningContext signingContext;
+ private final TaskChainer chatMessageChainer;
++ public volatile CommandNode currentCommand; // CraftBukkit
+
+ public CommandSourceStack(CommandSource commandsource, Vec3 vec3, Vec2 vec2, ServerLevel serverlevel, int i, String s, Component component, MinecraftServer minecraftserver, @Nullable Entity entity) {
+ this(commandsource, vec3, vec2, serverlevel, i, s, component, minecraftserver, entity, false, CommandResultCallback.EMPTY, EntityAnchorArgument.Anchor.FEET, CommandSigningContext.ANONYMOUS, TaskChainer.immediate(minecraftserver));
+@@ -170,11 +170,24 @@
+ }
+
+ @Override
+- @Override
+- public boolean hasPermission(int i) {
+- return this.permissionLevel >= i;
++ public boolean hasPermission(int level) {
++ // CraftBukkit start
++ CommandNode currentCommand = this.currentCommand;
++ if (currentCommand != null) {
++ return hasPermission(level, org.bukkit.craftbukkit.command.VanillaCommandWrapper.getPermission(currentCommand));
++ }
++ // CraftBukkit end
++
++ return this.permissionLevel >= level;
+ }
+
++ // CraftBukkit start
++ public boolean hasPermission(int i, String bukkitPermission) {
++ // World is null when loading functions
++ return ((getLevel() == null || !getLevel().getCraftServer().ignoreVanillaPermissions) && this.permissionLevel >= i) || getBukkitSender().hasPermission(bukkitPermission);
++ }
++ // CraftBukkit end
++
+ public Vec3 getPosition() {
+ return this.worldPosition;
+ }
+@@ -307,8 +320,8 @@
+ while (iterator.hasNext()) {
+ ServerPlayer serverplayer = (ServerPlayer) iterator.next();
+
+- if (serverplayer != this.source && this.server.getPlayerList().isOp(serverplayer.getGameProfile())) {
+- serverplayer.sendSystemMessage(mutablecomponent);
++ if (entityplayer != this.source && entityplayer.getBukkitEntity().hasPermission("minecraft.admin.command_feedback")) { // CraftBukkit
++ entityplayer.sendSystemMessage(ichatmutablecomponent);
+ }
+ }
+ }
+@@ -413,4 +413,10 @@
+ public boolean isSilent() {
+ return this.silent;
+ }
++
++ // CraftBukkit start
++ public org.bukkit.command.CommandSender getBukkitSender() {
++ return source.getBukkitSender(this);
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/commands/Commands.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/commands/Commands.java.patch
new file mode 100644
index 0000000000..016b50a1a8
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/commands/Commands.java.patch
@@ -0,0 +1,171 @@
+--- a/net/minecraft/commands/Commands.java
++++ b/net/minecraft/commands/Commands.java
+@@ -135,6 +134,14 @@
+ import net.minecraft.world.level.GameRules;
+ import org.slf4j.Logger;
+
++// CraftBukkit start
++import com.google.common.base.Joiner;
++import java.util.Collection;
++import java.util.LinkedHashSet;
++import org.bukkit.event.player.PlayerCommandSendEvent;
++import org.bukkit.event.server.ServerCommandEvent;
++// CraftBukkit end
++
+ public class Commands {
+
+ private static final ThreadLocal<ExecutionContext<CommandSourceStack>> CURRENT_EXECUTION_CONTEXT = new ThreadLocal();
+@@ -146,7 +153,8 @@
+ public static final int LEVEL_OWNERS = 4;
+ private final CommandDispatcher<CommandSourceStack> dispatcher = new CommandDispatcher();
+
+- public Commands(Commands.CommandSelection commands_commandselection, CommandBuildContext commandbuildcontext) {
++ public Commands(Commands.CommandSelection selection, CommandBuildContext context) {
++ this(); // CraftBukkit
+ AdvancementCommands.register(this.dispatcher);
+ AttributeCommand.register(this.dispatcher, commandbuildcontext);
+ ExecuteCommand.register(this.dispatcher, commandbuildcontext);
+@@ -247,6 +255,11 @@
+ PublishCommand.register(this.dispatcher);
+ }
+
++ // CraftBukkit start
++ }
++
++ public Commands() {
++ // CraftBukkkit end
+ this.dispatcher.setConsumer(ExecutionCommandSource.resultConsumer());
+ }
+
+@@ -257,18 +270,64 @@
+ return new ParseResults(commandcontextbuilder1, parseresults.getReader(), parseresults.getExceptions());
+ }
+
+- public void performPrefixedCommand(CommandSourceStack commandsourcestack, String s) {
++ // CraftBukkit start
++ public void dispatchServerCommand(CommandSourceStack sender, String command) {
++ Joiner joiner = Joiner.on(" ");
++ if (command.startsWith("/")) {
++ command = command.substring(1);
++ }
++
++ ServerCommandEvent event = new ServerCommandEvent(sender.getBukkitSender(), command);
++ org.bukkit.Bukkit.getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ return;
++ }
++ command = event.getCommand();
++
++ String[] args = command.split(" ");
++
++ String cmd = args[0];
++ if (cmd.startsWith("minecraft:")) cmd = cmd.substring("minecraft:".length());
++ if (cmd.startsWith("bukkit:")) cmd = cmd.substring("bukkit:".length());
++
++ // Block disallowed commands
++ if (cmd.equalsIgnoreCase("stop") || cmd.equalsIgnoreCase("kick") || cmd.equalsIgnoreCase("op")
++ || cmd.equalsIgnoreCase("deop") || cmd.equalsIgnoreCase("ban") || cmd.equalsIgnoreCase("ban-ip")
++ || cmd.equalsIgnoreCase("pardon") || cmd.equalsIgnoreCase("pardon-ip") || cmd.equalsIgnoreCase("reload")) {
++ return;
++ }
++
++ // Handle vanilla commands;
++ if (sender.getLevel().getCraftServer().getCommandBlockOverride(args[0])) {
++ args[0] = "minecraft:" + args[0];
++ }
++
++ String newCommand = joiner.join(args);
++ this.performPrefixedCommand(sender, newCommand, newCommand);
++ }
++ // CraftBukkit end
++
++ public void performPrefixedCommand(CommandSourceStack commandlistenerwrapper, String s) {
++ // CraftBukkit start
++ this.performPrefixedCommand(commandlistenerwrapper, s, s);
++ }
++
++ public void performPrefixedCommand(CommandSourceStack commandlistenerwrapper, String s, String label) {
+ s = s.startsWith("/") ? s.substring(1) : s;
+- this.performCommand(this.dispatcher.parse(s, commandsourcestack), s);
++ this.performCommand(this.dispatcher.parse(s, commandlistenerwrapper), s, label);
++ // CraftBukkit end
+ }
+
+ public void performCommand(ParseResults<CommandSourceStack> parseresults, String s) {
+ CommandSourceStack commandsourcestack = (CommandSourceStack) parseresults.getContext().getSource();
+
+- commandsourcestack.getServer().getProfiler().push(() -> {
++ public void performCommand(ParseResults<CommandSourceStack> parseresults, String s, String label) { // CraftBukkit
++ CommandSourceStack commandlistenerwrapper = (CommandSourceStack) parseresults.getContext().getSource();
++
++ commandlistenerwrapper.getServer().getProfiler().push(() -> {
+ return "/" + s;
+ });
+- ContextChain contextchain = finishParsing(parseresults, s, commandsourcestack);
++ ContextChain contextchain = finishParsing(parseresults, s, commandlistenerwrapper, label); // CraftBukkit
+
+ try {
+ if (contextchain != null) {
+@@ -302,7 +362,7 @@
+ }
+
+ @Nullable
+- private static ContextChain<CommandSourceStack> finishParsing(ParseResults<CommandSourceStack> parseresults, String s, CommandSourceStack commandsourcestack) {
++ private static ContextChain<CommandSourceStack> finishParsing(ParseResults<CommandSourceStack> parseresults, String s, CommandSourceStack commandlistenerwrapper, String label) { // CraftBukkit
+ try {
+ validateParseResults(parseresults);
+ return (ContextChain) ContextChain.tryFlatten(parseresults.getContext().build(s)).orElseThrow(() -> {
+@@ -312,8 +372,8 @@
+ commandsourcestack.sendFailure(ComponentUtils.fromMessage(commandsyntaxexception.getRawMessage()));
+ if (commandsyntaxexception.getInput() != null && commandsyntaxexception.getCursor() >= 0) {
+ int i = Math.min(commandsyntaxexception.getInput().length(), commandsyntaxexception.getCursor());
+- MutableComponent mutablecomponent = Component.empty().withStyle(ChatFormatting.GRAY).withStyle((style) -> {
+- return style.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + s));
++ MutableComponent ichatmutablecomponent = Component.empty().withStyle(ChatFormatting.GRAY).withStyle((chatmodifier) -> {
++ return chatmodifier.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, label)); // CraftBukkit
+ });
+
+ if (i > 10) {
+@@ -371,13 +431,38 @@
+
+ }
+
+- public void sendCommands(ServerPlayer serverplayer) {
+- Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> map = Maps.newHashMap();
++ public void sendCommands(ServerPlayer player) {
++ // CraftBukkit start
++ // Register Vanilla commands into builtRoot as before
++ Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> map = Maps.newIdentityHashMap(); // Use identity to prevent aliasing issues
++ RootCommandNode vanillaRoot = new RootCommandNode();
++
++ RootCommandNode<CommandSourceStack> vanilla = player.server.vanillaCommandDispatcher.getDispatcher().getRoot();
++ map.put(vanilla, vanillaRoot);
++ this.fillUsableCommands(vanilla, vanillaRoot, player.createCommandSourceStack(), (Map) map);
++
++ // Now build the global commands in a second pass
+ RootCommandNode<SharedSuggestionProvider> rootcommandnode = new RootCommandNode();
+
+ map.put(this.dispatcher.getRoot(), rootcommandnode);
+- this.fillUsableCommands(this.dispatcher.getRoot(), rootcommandnode, serverplayer.createCommandSourceStack(), map);
+- serverplayer.connection.send(new ClientboundCommandsPacket(rootcommandnode));
++ this.fillUsableCommands(this.dispatcher.getRoot(), rootcommandnode, player.createCommandSourceStack(), map);
++
++ Collection<String> bukkit = new LinkedHashSet<>();
++ for (CommandNode node : rootcommandnode.getChildren()) {
++ bukkit.add(node.getName());
++ }
++
++ PlayerCommandSendEvent event = new PlayerCommandSendEvent(player.getBukkitEntity(), new LinkedHashSet<>(bukkit));
++ event.getPlayer().getServer().getPluginManager().callEvent(event);
++
++ // Remove labels that were removed during the event
++ for (String orig : bukkit) {
++ if (!event.getCommands().contains(orig)) {
++ rootcommandnode.removeCommand(orig);
++ }
++ }
++ // CraftBukkit end
++ player.connection.send(new ClientboundCommandsPacket(rootcommandnode));
+ }
+
+ private void fillUsableCommands(CommandNode<CommandSourceStack> commandnode, CommandNode<SharedSuggestionProvider> commandnode1, CommandSourceStack commandsourcestack, Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> map) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/commands/arguments/EntityArgument.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/commands/arguments/EntityArgument.java.patch
new file mode 100644
index 0000000000..41ac851839
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/commands/arguments/EntityArgument.java.patch
@@ -0,0 +1,23 @@
+--- a/net/minecraft/commands/arguments/EntityArgument.java
++++ b/net/minecraft/commands/arguments/EntityArgument.java
+@@ -93,11 +93,16 @@
+ }
+ }
+
+- @Override
+- public EntitySelector parse(StringReader stringreader) throws CommandSyntaxException {
++ public EntitySelector parse(StringReader reader) throws CommandSyntaxException {
++ // CraftBukkit start
++ return parse(reader, false);
++ }
++
++ public EntitySelector parse(StringReader stringreader, boolean overridePermissions) throws CommandSyntaxException {
++ // CraftBukkit end
+ boolean flag = false;
+- EntitySelectorParser entityselectorparser = new EntitySelectorParser(stringreader);
+- EntitySelector entityselector = entityselectorparser.parse();
++ EntitySelectorParser argumentparserselector = new EntitySelectorParser(stringreader);
++ EntitySelector entityselector = argumentparserselector.parse(overridePermissions); // CraftBukkit
+
+ if (entityselector.getMaxResults() > 1 && this.single) {
+ if (this.playersOnly) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/commands/arguments/blocks/BlockStateParser.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/commands/arguments/blocks/BlockStateParser.java.patch
new file mode 100644
index 0000000000..0f178b4599
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/commands/arguments/blocks/BlockStateParser.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/commands/arguments/blocks/BlockStateParser.java
++++ b/net/minecraft/commands/arguments/blocks/BlockStateParser.java
+@@ -68,7 +68,7 @@
+ private final StringReader reader;
+ private final boolean forTesting;
+ private final boolean allowNbt;
+- private final Map<Property<?>, Comparable<?>> properties = Maps.newHashMap();
++ private final Map<Property<?>, Comparable<?>> properties = Maps.newLinkedHashMap(); // CraftBukkit - stable
+ private final Map<String, String> vagueProperties = Maps.newHashMap();
+ private ResourceLocation id = new ResourceLocation("");
+ @Nullable
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/commands/arguments/selector/EntitySelector.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/commands/arguments/selector/EntitySelector.java.patch
new file mode 100644
index 0000000000..be7b3eafa6
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/commands/arguments/selector/EntitySelector.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/commands/arguments/selector/EntitySelector.java
++++ b/net/minecraft/commands/arguments/selector/EntitySelector.java
+@@ -93,8 +91,8 @@
+ return this.usesSelector;
+ }
+
+- private void checkPermissions(CommandSourceStack commandsourcestack) throws CommandSyntaxException {
+- if (this.usesSelector && !commandsourcestack.hasPermission(2)) {
++ private void checkPermissions(CommandSourceStack source) throws CommandSyntaxException {
++ if (this.usesSelector && !source.hasPermission(2, "minecraft.command.selector")) { // CraftBukkit
+ throw EntityArgument.ERROR_SELECTORS_NOT_ALLOWED.create();
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/commands/arguments/selector/EntitySelectorParser.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/commands/arguments/selector/EntitySelectorParser.java.patch
new file mode 100644
index 0000000000..747f003169
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/commands/arguments/selector/EntitySelectorParser.java.patch
@@ -0,0 +1,37 @@
+--- a/net/minecraft/commands/arguments/selector/EntitySelectorParser.java
++++ b/net/minecraft/commands/arguments/selector/EntitySelectorParser.java
+@@ -204,8 +204,10 @@
+ };
+ }
+
+- protected void parseSelector() throws CommandSyntaxException {
+- this.usesSelectors = true;
++ // CraftBukkit start
++ protected void parseSelector(boolean overridePermissions) throws CommandSyntaxException {
++ this.usesSelectors = !overridePermissions;
++ // CraftBukkit end
+ this.suggestions = this::suggestSelector;
+ if (!this.reader.canRead()) {
+ throw EntitySelectorParser.ERROR_MISSING_SELECTOR_TYPE.createWithContext(this.reader);
+@@ -463,6 +465,12 @@
+ }
+
+ public EntitySelector parse() throws CommandSyntaxException {
++ // CraftBukkit start
++ return parse(false);
++ }
++
++ public EntitySelector parse(boolean overridePermissions) throws CommandSyntaxException {
++ // CraftBukkit end
+ this.startPosition = this.reader.getCursor();
+ this.suggestions = this::suggestNameOrSelector;
+ if (this.reader.canRead() && this.reader.peek() == '@') {
+@@ -471,7 +479,7 @@
+ }
+
+ this.reader.skip();
+- this.parseSelector();
++ this.parseSelector(overridePermissions); // CraftBukkit
+ } else {
+ this.parseNameOrUUID();
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/core/cauldron/CauldronInteraction.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/core/cauldron/CauldronInteraction.java.patch
new file mode 100644
index 0000000000..3b9ffa86c4
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/core/cauldron/CauldronInteraction.java.patch
@@ -0,0 +1,234 @@
+--- a/net/minecraft/core/cauldron/CauldronInteraction.java
++++ b/net/minecraft/core/cauldron/CauldronInteraction.java
+@@ -33,6 +33,10 @@
+ import net.minecraft.world.level.block.state.BlockState;
+ import net.minecraft.world.level.gameevent.GameEvent;
+
++// CraftBukkit start
++import org.bukkit.event.block.CauldronLevelChangeEvent;
++// CraftBukkit end
++
+ public interface CauldronInteraction {
+
+ Map<String, CauldronInteraction.InteractionMap> INTERACTIONS = new Object2ObjectArrayMap();
+@@ -47,6 +53,8 @@
+ CauldronInteraction SHULKER_BOX;
+ CauldronInteraction BANNER;
+ CauldronInteraction DYED_ITEM;
++ */
++ // CraftBukkit end
+
+ static CauldronInteraction.InteractionMap newInteractionMap(String s) {
+ Object2ObjectOpenHashMap<Item, CauldronInteraction> object2objectopenhashmap = new Object2ObjectOpenHashMap();
+@@ -70,15 +78,20 @@
+ if (PotionUtils.getPotion(itemstack) != Potions.WATER) {
+ return InteractionResult.PASS;
+ } else {
+- if (!level.isClientSide) {
++ if (!world.isClientSide) {
++ // CraftBukkit start
++ if (!LayeredCauldronBlock.changeLevel(iblockdata, world, blockposition, Blocks.WATER_CAULDRON.defaultBlockState(), entityhuman, CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY)) {
++ return InteractionResult.SUCCESS;
++ }
++ // CraftBukkit end
+ Item item = itemstack.getItem();
+
+- player.setItemInHand(interactionhand, ItemUtils.createFilledResult(itemstack, player, new ItemStack(Items.GLASS_BOTTLE)));
+- player.awardStat(Stats.USE_CAULDRON);
+- player.awardStat(Stats.ITEM_USED.get(item));
+- level.setBlockAndUpdate(blockpos, Blocks.WATER_CAULDRON.defaultBlockState());
+- level.playSound((Player) null, blockpos, SoundEvents.BOTTLE_EMPTY, SoundSource.BLOCKS, 1.0F, 1.0F);
+- level.gameEvent((Entity) null, GameEvent.FLUID_PLACE, blockpos);
++ entityhuman.setItemInHand(enumhand, ItemUtils.createFilledResult(itemstack, entityhuman, new ItemStack(Items.GLASS_BOTTLE)));
++ entityhuman.awardStat(Stats.USE_CAULDRON);
++ entityhuman.awardStat(Stats.ITEM_USED.get(item));
++ // world.setBlockAndUpdate(blockposition, Blocks.WATER_CAULDRON.defaultBlockState()); // CraftBukkit
++ world.playSound((Player) null, blockposition, SoundEvents.BOTTLE_EMPTY, SoundSource.BLOCKS, 1.0F, 1.0F);
++ world.gameEvent((Entity) null, GameEvent.FLUID_PLACE, blockposition);
+ }
+
+ return InteractionResult.sidedSuccess(level.isClientSide);
+@@ -92,29 +105,39 @@
+ return (Integer) blockstate1.getValue(LayeredCauldronBlock.LEVEL) == 3;
+ }, SoundEvents.BUCKET_FILL);
+ });
+- map1.put(Items.GLASS_BOTTLE, (blockstate, level, blockpos, player, interactionhand, itemstack) -> {
+- if (!level.isClientSide) {
++ map1.put(Items.GLASS_BOTTLE, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> {
++ if (!world.isClientSide) {
++ // CraftBukkit start
++ if (!LayeredCauldronBlock.lowerFillLevel(iblockdata, world, blockposition, entityhuman, CauldronLevelChangeEvent.ChangeReason.BOTTLE_FILL)) {
++ return InteractionResult.SUCCESS;
++ }
++ // CraftBukkit end
+ Item item = itemstack.getItem();
+
+- player.setItemInHand(interactionhand, ItemUtils.createFilledResult(itemstack, player, PotionUtils.setPotion(new ItemStack(Items.POTION), Potions.WATER)));
+- player.awardStat(Stats.USE_CAULDRON);
+- player.awardStat(Stats.ITEM_USED.get(item));
+- LayeredCauldronBlock.lowerFillLevel(blockstate, level, blockpos);
+- level.playSound((Player) null, blockpos, SoundEvents.BOTTLE_FILL, SoundSource.BLOCKS, 1.0F, 1.0F);
+- level.gameEvent((Entity) null, GameEvent.FLUID_PICKUP, blockpos);
++ entityhuman.setItemInHand(enumhand, ItemUtils.createFilledResult(itemstack, entityhuman, PotionUtils.setPotion(new ItemStack(Items.POTION), Potions.WATER)));
++ entityhuman.awardStat(Stats.USE_CAULDRON);
++ entityhuman.awardStat(Stats.ITEM_USED.get(item));
++ // LayeredCauldronBlock.lowerFillLevel(iblockdata, world, blockposition); // CraftBukkit
++ world.playSound((Player) null, blockposition, SoundEvents.BOTTLE_FILL, SoundSource.BLOCKS, 1.0F, 1.0F);
++ world.gameEvent((Entity) null, GameEvent.FLUID_PICKUP, blockposition);
+ }
+
+ return InteractionResult.sidedSuccess(level.isClientSide);
+ });
+- map1.put(Items.POTION, (blockstate, level, blockpos, player, interactionhand, itemstack) -> {
+- if ((Integer) blockstate.getValue(LayeredCauldronBlock.LEVEL) != 3 && PotionUtils.getPotion(itemstack) == Potions.WATER) {
+- if (!level.isClientSide) {
+- player.setItemInHand(interactionhand, ItemUtils.createFilledResult(itemstack, player, new ItemStack(Items.GLASS_BOTTLE)));
+- player.awardStat(Stats.USE_CAULDRON);
+- player.awardStat(Stats.ITEM_USED.get(itemstack.getItem()));
+- level.setBlockAndUpdate(blockpos, (BlockState) blockstate.cycle(LayeredCauldronBlock.LEVEL));
+- level.playSound((Player) null, blockpos, SoundEvents.BOTTLE_EMPTY, SoundSource.BLOCKS, 1.0F, 1.0F);
+- level.gameEvent((Entity) null, GameEvent.FLUID_PLACE, blockpos);
++ map1.put(Items.POTION, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> {
++ if ((Integer) iblockdata.getValue(LayeredCauldronBlock.LEVEL) != 3 && PotionUtils.getPotion(itemstack) == Potions.WATER) {
++ if (!world.isClientSide) {
++ // CraftBukkit start
++ if (!LayeredCauldronBlock.changeLevel(iblockdata, world, blockposition, iblockdata.cycle(LayeredCauldronBlock.LEVEL), entityhuman, CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY)) {
++ return InteractionResult.SUCCESS;
++ }
++ // CraftBukkit end
++ entityhuman.setItemInHand(enumhand, ItemUtils.createFilledResult(itemstack, entityhuman, new ItemStack(Items.GLASS_BOTTLE)));
++ entityhuman.awardStat(Stats.USE_CAULDRON);
++ entityhuman.awardStat(Stats.ITEM_USED.get(itemstack.getItem()));
++ // world.setBlockAndUpdate(blockposition, (IBlockData) iblockdata.cycle(LayeredCauldronBlock.LEVEL)); // CraftBukkit
++ world.playSound((Player) null, blockposition, SoundEvents.BOTTLE_EMPTY, SoundSource.BLOCKS, 1.0F, 1.0F);
++ world.gameEvent((Entity) null, GameEvent.FLUID_PLACE, blockposition);
+ }
+
+ return InteractionResult.sidedSuccess(level.isClientSide);
+@@ -188,14 +211,19 @@
+ return InteractionResult.PASS;
+ } else {
+ if (!level.isClientSide) {
+- Item item = itemstack.getItem();
++ // CraftBukkit start
++ if (!LayeredCauldronBlock.changeLevel(blockState, level, pos, Blocks.CAULDRON.defaultBlockState(), player, CauldronLevelChangeEvent.ChangeReason.BUCKET_FILL)) {
++ return InteractionResult.SUCCESS;
++ }
++ // CraftBukkit end
++ Item item = emptyStack.getItem();
+
+ player.setItemInHand(interactionhand, ItemUtils.createFilledResult(itemstack, player, itemstack1));
+ player.awardStat(Stats.USE_CAULDRON);
+ player.awardStat(Stats.ITEM_USED.get(item));
+- level.setBlockAndUpdate(blockpos, Blocks.CAULDRON.defaultBlockState());
+- level.playSound((Player) null, blockpos, soundevent, SoundSource.BLOCKS, 1.0F, 1.0F);
+- level.gameEvent((Entity) null, GameEvent.FLUID_PICKUP, blockpos);
++ // world.setBlockAndUpdate(blockposition, Blocks.CAULDRON.defaultBlockState()); // CraftBukkit
++ level.playSound((Player) null, pos, fillSound, SoundSource.BLOCKS, 1.0F, 1.0F);
++ level.gameEvent((Entity) null, GameEvent.FLUID_PICKUP, pos);
+ }
+
+ return InteractionResult.sidedSuccess(level.isClientSide);
+@@ -204,14 +232,19 @@
+
+ static InteractionResult emptyBucket(Level level, BlockPos blockpos, Player player, InteractionHand interactionhand, ItemStack itemstack, BlockState blockstate, SoundEvent soundevent) {
+ if (!level.isClientSide) {
+- Item item = itemstack.getItem();
++ // CraftBukkit start
++ if (!LayeredCauldronBlock.changeLevel(state, level, pos, state, player, CauldronLevelChangeEvent.ChangeReason.BUCKET_EMPTY)) {
++ return InteractionResult.SUCCESS;
++ }
++ // CraftBukkit end
++ Item item = filledStack.getItem();
+
+ player.setItemInHand(interactionhand, ItemUtils.createFilledResult(itemstack, player, new ItemStack(Items.BUCKET)));
+ player.awardStat(Stats.FILL_CAULDRON);
+ player.awardStat(Stats.ITEM_USED.get(item));
+- level.setBlockAndUpdate(blockpos, blockstate);
+- level.playSound((Player) null, blockpos, soundevent, SoundSource.BLOCKS, 1.0F, 1.0F);
+- level.gameEvent((Entity) null, GameEvent.FLUID_PLACE, blockpos);
++ // world.setBlockAndUpdate(blockposition, iblockdata); // CraftBukkit
++ level.playSound((Player) null, pos, emptySound, SoundSource.BLOCKS, 1.0F, 1.0F);
++ level.gameEvent((Entity) null, GameEvent.FLUID_PLACE, pos);
+ }
+
+ return InteractionResult.sidedSuccess(level.isClientSide);
+@@ -236,22 +266,28 @@
+ FILL_POWDER_SNOW = (blockstate, level, blockpos, player, interactionhand, itemstack) -> {
+ return emptyBucket(level, blockpos, player, interactionhand, itemstack, (BlockState) Blocks.POWDER_SNOW_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, 3), SoundEvents.BUCKET_EMPTY_POWDER_SNOW);
+ };
+- SHULKER_BOX = (blockstate, level, blockpos, player, interactionhand, itemstack) -> {
++ CauldronInteraction SHULKER_BOX = (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> {
++ // CraftBukkit end
+ Block block = Block.byItem(itemstack.getItem());
+
+ if (!(block instanceof ShulkerBoxBlock)) {
+ return InteractionResult.PASS;
+ } else {
+- if (!level.isClientSide) {
++ if (!world.isClientSide) {
++ // CraftBukkit start
++ if (!LayeredCauldronBlock.lowerFillLevel(iblockdata, world, blockposition, entityhuman, CauldronLevelChangeEvent.ChangeReason.SHULKER_WASH)) {
++ return InteractionResult.SUCCESS;
++ }
++ // CraftBukkit end
+ ItemStack itemstack1 = new ItemStack(Blocks.SHULKER_BOX);
+
+ if (itemstack.hasTag()) {
+ itemstack1.setTag(itemstack.getTag().copy());
+ }
+
+- player.setItemInHand(interactionhand, itemstack1);
+- player.awardStat(Stats.CLEAN_SHULKER_BOX);
+- LayeredCauldronBlock.lowerFillLevel(blockstate, level, blockpos);
++ entityhuman.setItemInHand(enumhand, itemstack1);
++ entityhuman.awardStat(Stats.CLEAN_SHULKER_BOX);
++ // LayeredCauldronBlock.lowerFillLevel(iblockdata, world, blockposition); // CraftBukkit
+ }
+
+ return InteractionResult.sidedSuccess(level.isClientSide);
+@@ -261,7 +297,12 @@
+ if (BannerBlockEntity.getPatternCount(itemstack) <= 0) {
+ return InteractionResult.PASS;
+ } else {
+- if (!level.isClientSide) {
++ if (!world.isClientSide) {
++ // CraftBukkit start
++ if (!LayeredCauldronBlock.lowerFillLevel(iblockdata, world, blockposition, entityhuman, CauldronLevelChangeEvent.ChangeReason.BANNER_WASH)) {
++ return InteractionResult.SUCCESS;
++ }
++ // CraftBukkit end
+ ItemStack itemstack1 = itemstack.copyWithCount(1);
+
+ BannerBlockEntity.removeLastPattern(itemstack1);
+@@ -277,8 +318,8 @@
+ player.drop(itemstack1, false);
+ }
+
+- player.awardStat(Stats.CLEAN_BANNER);
+- LayeredCauldronBlock.lowerFillLevel(blockstate, level, blockpos);
++ entityhuman.awardStat(Stats.CLEAN_BANNER);
++ // LayeredCauldronBlock.lowerFillLevel(iblockdata, world, blockposition); // CraftBukkit
+ }
+
+ return InteractionResult.sidedSuccess(level.isClientSide);
+@@ -295,10 +336,15 @@
+ if (!dyeableleatheritem.hasCustomColor(itemstack)) {
+ return InteractionResult.PASS;
+ } else {
+- if (!level.isClientSide) {
+- dyeableleatheritem.clearColor(itemstack);
+- player.awardStat(Stats.CLEAN_ARMOR);
+- LayeredCauldronBlock.lowerFillLevel(blockstate, level, blockpos);
++ if (!world.isClientSide) {
++ // CraftBukkit start
++ if (!LayeredCauldronBlock.lowerFillLevel(iblockdata, world, blockposition, entityhuman, CauldronLevelChangeEvent.ChangeReason.ARMOR_WASH)) {
++ return InteractionResult.SUCCESS;
++ }
++ // CraftBukkit end
++ idyeable.clearColor(itemstack);
++ entityhuman.awardStat(Stats.CLEAN_ARMOR);
++ // LayeredCauldronBlock.lowerFillLevel(iblockdata, world, blockposition); // CraftBukkit
+ }
+
+ return InteractionResult.sidedSuccess(level.isClientSide);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/core/dispenser/AbstractProjectileDispenseBehavior.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/core/dispenser/AbstractProjectileDispenseBehavior.java.patch
new file mode 100644
index 0000000000..027b56ccbc
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/core/dispenser/AbstractProjectileDispenseBehavior.java.patch
@@ -0,0 +1,58 @@
+--- a/net/minecraft/core/dispenser/AbstractProjectileDispenseBehavior.java
++++ b/net/minecraft/core/dispenser/AbstractProjectileDispenseBehavior.java
+@@ -7,6 +6,13 @@
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.block.DispenserBlock;
++// CraftBukkit start
++import net.minecraft.world.entity.Entity;
++import net.minecraft.world.entity.projectile.Projectile;
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.block.BlockDispenseEvent;
++// CraftBukkit end
+
+ public abstract class AbstractProjectileDispenseBehavior extends DefaultDispenseItemBehavior {
+
+@@ -20,9 +25,38 @@
+ Direction direction = (Direction) blocksource.state().getValue(DispenserBlock.FACING);
+ Projectile projectile = this.getProjectile(serverlevel, position, itemstack);
+
+- projectile.shoot((double) direction.getStepX(), (double) ((float) direction.getStepY() + 0.1F), (double) direction.getStepZ(), this.getPower(), this.getUncertainty());
+- serverlevel.addFreshEntity(projectile);
+- itemstack.shrink(1);
++ // CraftBukkit start
++ // iprojectile.shoot((double) enumdirection.getStepX(), (double) ((float) enumdirection.getStepY() + 0.1F), (double) enumdirection.getStepZ(), this.getPower(), this.getUncertainty());
++ ItemStack itemstack1 = itemstack.split(1);
++ org.bukkit.block.Block block = CraftBlock.at(worldserver, sourceblock.pos());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
++
++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) enumdirection.getStepX(), (double) ((float) enumdirection.getStepY() + 0.1F), (double) enumdirection.getStepZ()));
++ if (!DispenserBlock.eventFired) {
++ worldserver.getCraftServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ itemstack.grow(1);
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ itemstack.grow(1);
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem());
++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
++ idispensebehavior.dispense(sourceblock, eventStack);
++ return itemstack;
++ }
++ }
++
++ iprojectile.shoot(event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), this.getPower(), this.getUncertainty());
++ ((Entity) iprojectile).projectileSource = new org.bukkit.craftbukkit.projectiles.CraftBlockProjectileSource(sourceblock.blockEntity());
++ // CraftBukkit end
++ worldserver.addFreshEntity(iprojectile);
++ // itemstack.shrink(1); // CraftBukkit - Handled during event processing
+ return itemstack;
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch
new file mode 100644
index 0000000000..a32509d4a3
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch
@@ -0,0 +1,61 @@
+--- a/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java
++++ b/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java
+@@ -12,6 +12,11 @@
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.level.block.DispenserBlock;
+ import net.minecraft.world.phys.Vec3;
++// CraftBukkit start
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.block.BlockDispenseEvent;
++// CraftBukkit end
+
+ public class BoatDispenseItemBehavior extends DefaultDispenseItemBehavior {
+
+@@ -52,13 +56,41 @@
+ d4 = 0.0D;
+ }
+
+- Object object = this.isChestBoat ? new ChestBoat(serverlevel, d1, d2 + d4, d3) : new Boat(serverlevel, d1, d2 + d4, d3);
++ // Object object = this.isChestBoat ? new ChestBoat(worldserver, d1, d2 + d4, d3) : new EntityBoat(worldserver, d1, d2 + d4, d3);
++ // CraftBukkit start
++ ItemStack itemstack1 = itemstack.split(1);
++ org.bukkit.block.Block block = CraftBlock.at(worldserver, sourceblock.pos());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
+
+- EntityType.createDefaultStackConfig(serverlevel, itemstack, (Player) null).accept(object);
++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d1, d2 + d4, d3));
++ if (!DispenserBlock.eventFired) {
++ worldserver.getCraftServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ itemstack.grow(1);
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ itemstack.grow(1);
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem());
++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
++ idispensebehavior.dispense(sourceblock, eventStack);
++ return itemstack;
++ }
++ }
++
++ Boat object = this.isChestBoat ? new ChestBoat(worldserver, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ()) : new Boat(worldserver, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ());
++ // CraftBukkit end
++
++ EntityType.createDefaultStackConfig(worldserver, itemstack, (Player) null).accept(object);
+ ((Boat) object).setVariant(this.type);
+- ((Boat) object).setYRot(direction.toYRot());
+- serverlevel.addFreshEntity((Entity) object);
+- itemstack.shrink(1);
++ ((Boat) object).setYRot(enumdirection.toYRot());
++ if (!worldserver.addFreshEntity((Entity) object)) itemstack.grow(1); // CraftBukkit
++ // itemstack.shrink(1); // CraftBukkit - handled during event processing
+ return itemstack;
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch
new file mode 100644
index 0000000000..0243bac917
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch
@@ -0,0 +1,113 @@
+--- a/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java
++++ b/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java
+@@ -6,9 +6,23 @@
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.block.DispenserBlock;
++// CraftBukkit start
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.craftbukkit.util.CraftVector;
++import org.bukkit.event.block.BlockDispenseEvent;
++// CraftBukkit end
+
+ public class DefaultDispenseItemBehavior implements DispenseItemBehavior {
+
++ // CraftBukkit start
++ private boolean dropper;
++
++ public DefaultDispenseItemBehavior(boolean dropper) {
++ this.dropper = dropper;
++ }
++ // CraftBukkit end
++
+ public DefaultDispenseItemBehavior() {}
+
+ @Override
+@@ -26,16 +39,27 @@
+ Position position = DispenserBlock.getDispensePosition(blocksource);
+ ItemStack itemstack1 = itemstack.split(1);
+
+- spawnItem(blocksource.level(), itemstack1, 6, direction, position);
++ // CraftBukkit start
++ if (!spawnItem(sourceblock.level(), itemstack1, 6, enumdirection, sourceblock, dropper)) {
++ itemstack.grow(1);
++ }
++ // CraftBukkit end
+ return itemstack;
+ }
+
+- public static void spawnItem(Level level, ItemStack itemstack, int i, Direction direction, Position position) {
+- double d0 = position.x();
+- double d1 = position.y();
+- double d2 = position.z();
++ public static void spawnItem(Level level, ItemStack stack, int speed, Direction facing, IPosition position) {
++ // CraftBukkit start
++ ItemEntity entityitem = prepareItem(level, stack, speed, facing, position);
++ level.addFreshEntity(entityitem);
++ }
+
+- if (direction.getAxis() == Direction.Axis.Y) {
++ private static ItemEntity prepareItem(Level world, ItemStack itemstack, int i, Direction enumdirection, IPosition iposition) {
++ // CraftBukkit end
++ double d0 = iposition.x();
++ double d1 = iposition.y();
++ double d2 = iposition.z();
++
++ if (enumdirection.getAxis() == Direction.Axis.Y) {
+ d1 -= 0.125D;
+ } else {
+ d1 -= 0.15625D;
+@@ -44,12 +68,48 @@
+ ItemEntity itementity = new ItemEntity(level, d0, d1, d2, itemstack);
+ double d3 = level.random.nextDouble() * 0.1D + 0.2D;
+
+- itementity.setDeltaMovement(level.random.triangle((double) direction.getStepX() * d3, 0.0172275D * (double) i), level.random.triangle(0.2D, 0.0172275D * (double) i), level.random.triangle((double) direction.getStepZ() * d3, 0.0172275D * (double) i));
+- level.addFreshEntity(itementity);
++ entityitem.setDeltaMovement(world.random.triangle((double) enumdirection.getStepX() * d3, 0.0172275D * (double) i), world.random.triangle(0.2D, 0.0172275D * (double) i), world.random.triangle((double) enumdirection.getStepZ() * d3, 0.0172275D * (double) i));
++ // CraftBukkit start
++ return entityitem;
+ }
+
+- protected void playSound(BlockSource blocksource) {
+- blocksource.level().levelEvent(1000, blocksource.pos(), 0);
++ // CraftBukkit - void -> boolean return, IPosition -> ISourceBlock last argument, dropper
++ public static boolean spawnItem(Level world, ItemStack itemstack, int i, Direction enumdirection, SourceBlock sourceblock, boolean dropper) {
++ if (itemstack.isEmpty()) return true;
++ IPosition iposition = DispenserBlock.getDispensePosition(sourceblock);
++ ItemEntity entityitem = prepareItem(world, itemstack, i, enumdirection, iposition);
++
++ org.bukkit.block.Block block = CraftBlock.at(world, sourceblock.pos());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack);
++
++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), CraftVector.toBukkit(entityitem.getDeltaMovement()));
++ if (!DispenserBlock.eventFired) {
++ world.getCraftServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ return false;
++ }
++
++ entityitem.setItem(CraftItemStack.asNMSCopy(event.getItem()));
++ entityitem.setDeltaMovement(CraftVector.toNMS(event.getVelocity()));
++
++ if (!dropper && !event.getItem().getType().equals(craftItem.getType())) {
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem());
++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior.getClass() != DefaultDispenseItemBehavior.class) {
++ idispensebehavior.dispense(sourceblock, eventStack);
++ } else {
++ world.addFreshEntity(entityitem);
++ }
++ return false;
++ }
++
++ world.addFreshEntity(entityitem);
++
++ return true;
++ // CraftBukkit end
+ }
+
+ protected void playAnimation(BlockSource blocksource, Direction direction) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch
new file mode 100644
index 0000000000..4cbfcb072a
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch
@@ -0,0 +1,687 @@
+--- a/net/minecraft/core/dispenser/DispenseItemBehavior.java
++++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java
+@@ -76,6 +79,17 @@
+ import net.minecraft.world.phys.BlockHitResult;
+ import net.minecraft.world.phys.Vec3;
+ import org.slf4j.Logger;
++import org.bukkit.Location;
++import org.bukkit.TreeType;
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.craftbukkit.util.CraftLocation;
++import org.bukkit.craftbukkit.util.DummyGeneratorAccess;
++import org.bukkit.event.block.BlockDispenseArmorEvent;
++import org.bukkit.event.block.BlockDispenseEvent;
++import org.bukkit.event.block.BlockFertilizeEvent;
++import org.bukkit.event.world.StructureGrowEvent;
++// CraftBukkit end
+
+ public interface DispenseItemBehavior {
+
+@@ -218,6 +215,33 @@
+ Direction direction = (Direction) blocksource.state().getValue(DispenserBlock.FACING);
+ EntityType entitytype = ((SpawnEggItem) itemstack.getItem()).getType(itemstack.getTag());
+
++ // CraftBukkit start
++ ServerLevel worldserver = sourceblock.level();
++ ItemStack itemstack1 = itemstack.split(1);
++ org.bukkit.block.Block block = CraftBlock.at(worldserver, sourceblock.pos());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
++
++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
++ if (!DispenserBlock.eventFired) {
++ worldserver.getCraftServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ itemstack.grow(1);
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ itemstack.grow(1);
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem());
++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
++ idispensebehavior.dispense(sourceblock, eventStack);
++ return itemstack;
++ }
++ }
++
+ try {
+ entitytype.spawn(blocksource.level(), itemstack, (Player) null, blocksource.pos().relative(direction), MobSpawnType.DISPENSER, direction != Direction.UP, false);
+ } catch (Exception exception) {
+@@ -225,8 +249,9 @@
+ return ItemStack.EMPTY;
+ }
+
+- itemstack.shrink(1);
+- blocksource.level().gameEvent((Entity) null, GameEvent.ENTITY_PLACE, blocksource.pos());
++ // itemstack.shrink(1); // Handled during event processing
++ // CraftBukkit end
++ sourceblock.level().gameEvent((Entity) null, GameEvent.ENTITY_PLACE, sourceblock.pos());
+ return itemstack;
+ }
+ };
+@@ -250,10 +270,42 @@
+ }, serverlevel, itemstack, (Player) null);
+ ArmorStand armorstand = (ArmorStand) EntityType.ARMOR_STAND.spawn(serverlevel, itemstack.getTag(), consumer, blockpos, MobSpawnType.DISPENSER, false, false);
+
+- if (armorstand != null) {
+- itemstack.shrink(1);
++ // CraftBukkit start
++ ItemStack itemstack1 = itemstack.split(1);
++ org.bukkit.block.Block block = CraftBlock.at(worldserver, sourceblock.pos());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
++
++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
++ if (!DispenserBlock.eventFired) {
++ worldserver.getCraftServer().getPluginManager().callEvent(event);
+ }
+
++ if (event.isCancelled()) {
++ itemstack.grow(1);
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ itemstack.grow(1);
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem());
++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
++ idispensebehavior.dispense(sourceblock, eventStack);
++ return itemstack;
++ }
++ }
++ // CraftBukkit end
++
++ Consumer<ArmorStand> consumer = EntityType.appendDefaultStackConfig((entityarmorstand) -> {
++ entityarmorstand.setYRot(enumdirection.toYRot());
++ }, worldserver, itemstack, (Player) null);
++ ArmorStand entityarmorstand = (ArmorStand) EntityType.ARMOR_STAND.spawn(worldserver, itemstack.getTag(), consumer, blockposition, EnumMobSpawn.DISPENSER, false, false);
++
++ if (entityarmorstand != null) {
++ // itemstack.shrink(1); // CraftBukkit - Handled during event processing
++ }
++
+ return itemstack;
+ }
+ });
+@@ -273,8 +324,35 @@
+ });
+
+ if (!list.isEmpty()) {
++ // CraftBukkit start
++ ItemStack itemstack1 = itemstack.split(1);
++ Level world = sourceblock.level();
++ org.bukkit.block.Block block = CraftBlock.at(world, sourceblock.pos());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
++
++ BlockDispenseArmorEvent event = new BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) list.get(0).getBukkitEntity());
++ if (!DispenserBlock.eventFired) {
++ world.getCraftServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ itemstack.grow(1);
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ itemstack.grow(1);
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem());
++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != ArmorItem.DISPENSE_ITEM_BEHAVIOR) {
++ idispensebehavior.dispense(sourceblock, eventStack);
++ return itemstack;
++ }
++ }
++ // CraftBukkit end
+ ((Saddleable) list.get(0)).equipSaddle(SoundSource.BLOCKS);
+- itemstack.shrink(1);
++ // itemstack.shrink(1); // CraftBukkit - handled above
+ this.setSuccess(true);
+ return itemstack;
+ } else {
+@@ -302,7 +379,35 @@
+ abstracthorse = (AbstractHorse) iterator1.next();
+ } while (!abstracthorse.isArmor(itemstack) || abstracthorse.isWearingArmor() || !abstracthorse.isTamed());
+
+- abstracthorse.getSlot(401).set(itemstack.split(1));
++ // CraftBukkit start
++ ItemStack itemstack1 = itemstack.split(1);
++ Level world = sourceblock.level();
++ org.bukkit.block.Block block = CraftBlock.at(world, sourceblock.pos());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
++
++ BlockDispenseArmorEvent event = new BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) entityhorseabstract.getBukkitEntity());
++ if (!DispenserBlock.eventFired) {
++ world.getCraftServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ itemstack.grow(1);
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ itemstack.grow(1);
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem());
++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != ArmorItem.DISPENSE_ITEM_BEHAVIOR) {
++ idispensebehavior.dispense(sourceblock, eventStack);
++ return itemstack;
++ }
++ }
++
++ entityhorseabstract.getSlot(401).set(CraftItemStack.asNMSCopy(event.getItem()));
++ // CraftBukkit end
+ this.setSuccess(true);
+ return itemstack;
+ }
+@@ -345,25 +449,79 @@
+ return super.execute(blocksource, itemstack);
+ }
+
+- abstractchestedhorse = (AbstractChestedHorse) iterator1.next();
+- } while (!abstractchestedhorse.isTamed() || !abstractchestedhorse.getSlot(499).set(itemstack));
++ entityhorsechestedabstract = (AbstractChestedHorse) iterator1.next();
++ // CraftBukkit start
++ } while (!entityhorsechestedabstract.isTamed());
++ ItemStack itemstack1 = itemstack.split(1);
++ Level world = sourceblock.level();
++ org.bukkit.block.Block block = CraftBlock.at(world, sourceblock.pos());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
+
+- itemstack.shrink(1);
++ BlockDispenseArmorEvent event = new BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) entityhorsechestedabstract.getBukkitEntity());
++ if (!DispenserBlock.eventFired) {
++ world.getCraftServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem());
++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != ArmorItem.DISPENSE_ITEM_BEHAVIOR) {
++ idispensebehavior.dispense(sourceblock, eventStack);
++ return itemstack;
++ }
++ }
++ entityhorsechestedabstract.getSlot(499).set(CraftItemStack.asNMSCopy(event.getItem()));
++ // CraftBukkit end
++
++ // itemstack.shrink(1); // CraftBukkit - handled above
+ this.setSuccess(true);
+ return itemstack;
+ }
+ });
+ DispenserBlock.registerBehavior(Items.FIREWORK_ROCKET, new DefaultDispenseItemBehavior() {
+ @Override
+- @Override
+- public ItemStack execute(BlockSource blocksource, ItemStack itemstack) {
+- Direction direction = (Direction) blocksource.state().getValue(DispenserBlock.FACING);
+- Vec3 vec3 = DispenseItemBehavior.getEntityPokingOutOfBlockPos(blocksource, EntityType.FIREWORK_ROCKET, direction);
+- FireworkRocketEntity fireworkrocketentity = new FireworkRocketEntity(blocksource.level(), itemstack, vec3.x(), vec3.y(), vec3.z(), true);
++ public ItemStack execute(SourceBlock sourceblock, ItemStack itemstack) {
++ Direction enumdirection = (Direction) sourceblock.state().getValue(DispenserBlock.FACING);
++ // CraftBukkit start
++ ServerLevel worldserver = sourceblock.level();
++ ItemStack itemstack1 = itemstack.split(1);
++ org.bukkit.block.Block block = CraftBlock.at(worldserver, sourceblock.pos());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
+
+- fireworkrocketentity.shoot((double) direction.getStepX(), (double) direction.getStepY(), (double) direction.getStepZ(), 0.5F, 1.0F);
+- blocksource.level().addFreshEntity(fireworkrocketentity);
+- itemstack.shrink(1);
++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(enumdirection.getStepX(), enumdirection.getStepY(), enumdirection.getStepZ()));
++ if (!DispenserBlock.eventFired) {
++ worldserver.getCraftServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ itemstack.grow(1);
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ itemstack.grow(1);
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem());
++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
++ idispensebehavior.dispense(sourceblock, eventStack);
++ return itemstack;
++ }
++ }
++
++ itemstack1 = CraftItemStack.asNMSCopy(event.getItem());
++ Vec3 vec3d = DispenseItemBehavior.getEntityPokingOutOfBlockPos(sourceblock, EntityType.FIREWORK_ROCKET, enumdirection);
++ FireworkRocketEntity entityfireworks = new FireworkRocketEntity(sourceblock.level(), itemstack, vec3d.x(), vec3d.y(), vec3d.z(), true);
++
++ entityfireworks.shoot((double) enumdirection.getStepX(), (double) enumdirection.getStepY(), (double) enumdirection.getStepZ(), 0.5F, 1.0F);
++ sourceblock.level().addFreshEntity(entityfireworks);
++ // itemstack.shrink(1); // Handled during event processing
++ // CraftBukkit end
+ return itemstack;
+ }
+
+@@ -389,10 +544,39 @@
+ double d5 = randomsource.triangle((double) direction.getStepZ(), 0.11485000000000001D);
+ SmallFireball smallfireball = new SmallFireball(serverlevel, d0, d1, d2, d3, d4, d5);
+
+- serverlevel.addFreshEntity((Entity) Util.make(smallfireball, (smallfireball1) -> {
+- smallfireball1.setItem(itemstack);
+- }));
+- itemstack.shrink(1);
++ // CraftBukkit start
++ ItemStack itemstack1 = itemstack.split(1);
++ org.bukkit.block.Block block = CraftBlock.at(worldserver, sourceblock.pos());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
++
++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(d3, d4, d5));
++ if (!DispenserBlock.eventFired) {
++ worldserver.getCraftServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ itemstack.grow(1);
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ itemstack.grow(1);
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem());
++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
++ idispensebehavior.dispense(sourceblock, eventStack);
++ return itemstack;
++ }
++ }
++
++ SmallFireball entitysmallfireball = new SmallFireball(worldserver, d0, d1, d2, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ());
++ entitysmallfireball.setItem(itemstack1);
++ entitysmallfireball.projectileSource = new org.bukkit.craftbukkit.projectiles.CraftBlockProjectileSource(sourceblock.blockEntity());
++
++ worldserver.addFreshEntity(entitysmallfireball);
++ // itemstack.shrink(1); // Handled during event processing
++ // CraftBukkit end
+ return itemstack;
+ }
+
+@@ -430,9 +612,51 @@
+ BlockPos blockpos = blocksource.pos().relative((Direction) blocksource.state().getValue(DispenserBlock.FACING));
+ ServerLevel serverlevel = blocksource.level();
+
+- if (dispensiblecontaineritem.emptyContents((Player) null, serverlevel, blockpos, (BlockHitResult) null)) {
+- dispensiblecontaineritem.checkExtraContent((Player) null, serverlevel, itemstack, blockpos);
+- return new ItemStack(Items.BUCKET);
++ // CraftBukkit start
++ int x = blockposition.getX();
++ int y = blockposition.getY();
++ int z = blockposition.getZ();
++ IBlockData iblockdata = worldserver.getBlockState(blockposition);
++ if (iblockdata.isAir() || iblockdata.canBeReplaced() || (dispensiblecontaineritem instanceof BucketItem && iblockdata.getBlock() instanceof LiquidBlockContainer && ((LiquidBlockContainer) iblockdata.getBlock()).canPlaceLiquid((Player) null, worldserver, blockposition, iblockdata, ((BucketItem) dispensiblecontaineritem).content))) {
++ org.bukkit.block.Block block = CraftBlock.at(worldserver, sourceblock.pos());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack);
++
++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(x, y, z));
++ if (!DispenserBlock.eventFired) {
++ worldserver.getCraftServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem());
++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
++ idispensebehavior.dispense(sourceblock, eventStack);
++ return itemstack;
++ }
++ }
++
++ dispensiblecontaineritem = (DispensibleContainerItem) CraftItemStack.asNMSCopy(event.getItem()).getItem();
++ }
++ // CraftBukkit end
++
++ if (dispensiblecontaineritem.emptyContents((Player) null, worldserver, blockposition, (BlockHitResult) null)) {
++ dispensiblecontaineritem.checkExtraContent((Player) null, worldserver, itemstack, blockposition);
++ // CraftBukkit start - Handle stacked buckets
++ Item item = Items.BUCKET;
++ itemstack.shrink(1);
++ if (itemstack.isEmpty()) {
++ itemstack.setItem(Items.BUCKET);
++ itemstack.setCount(1);
++ } else if (sourceblock.blockEntity().addItem(new ItemStack(item)) < 0) {
++ this.defaultDispenseItemBehavior.dispense(sourceblock, new ItemStack(item));
++ }
++ return itemstack;
++ // CraftBukkit end
+ } else {
+ return this.defaultDispenseItemBehavior.dispense(blocksource, itemstack);
+ }
+@@ -460,8 +683,8 @@
+ Block block = blockstate.getBlock();
+
+ if (block instanceof BucketPickup) {
+- BucketPickup bucketpickup = (BucketPickup) block;
+- ItemStack itemstack1 = bucketpickup.pickupBlock((Player) null, serverlevel, blockpos, blockstate);
++ BucketPickup ifluidsource = (BucketPickup) block;
++ ItemStack itemstack1 = ifluidsource.pickupBlock((Player) null, DummyGeneratorAccess.INSTANCE, blockposition, iblockdata); // CraftBukkit
+
+ if (itemstack1.isEmpty()) {
+ return super.execute(blocksource, itemstack);
+@@ -469,6 +692,32 @@
+ serverlevel.gameEvent((Entity) null, GameEvent.FLUID_PICKUP, blockpos);
+ Item item = itemstack1.getItem();
+
++ // CraftBukkit start
++ org.bukkit.block.Block bukkitBlock = CraftBlock.at(worldserver, sourceblock.pos());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack);
++
++ BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ()));
++ if (!DispenserBlock.eventFired) {
++ worldserver.getCraftServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem());
++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
++ idispensebehavior.dispense(sourceblock, eventStack);
++ return itemstack;
++ }
++ }
++
++ itemstack1 = ifluidsource.pickupBlock((Player) null, worldserver, blockposition, iblockdata); // From above
++ // CraftBukkit end
++
+ itemstack.shrink(1);
+ if (itemstack.isEmpty()) {
+ return new ItemStack(item);
+@@ -491,18 +739,46 @@
+ protected ItemStack execute(BlockSource blocksource, ItemStack itemstack) {
+ ServerLevel serverlevel = blocksource.level();
+
++ // CraftBukkit start
++ org.bukkit.block.Block bukkitBlock = CraftBlock.at(worldserver, sourceblock.pos());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack);
++
++ BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
++ if (!DispenserBlock.eventFired) {
++ worldserver.getCraftServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem());
++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
++ idispensebehavior.dispense(sourceblock, eventStack);
++ return itemstack;
++ }
++ }
++ // CraftBukkit end
++
+ this.setSuccess(true);
+ Direction direction = (Direction) blocksource.state().getValue(DispenserBlock.FACING);
+ BlockPos blockpos = blocksource.pos().relative(direction);
+ BlockState blockstate = serverlevel.getBlockState(blockpos);
+
+- if (BaseFireBlock.canBePlacedAt(serverlevel, blockpos, direction)) {
+- serverlevel.setBlockAndUpdate(blockpos, BaseFireBlock.getState(serverlevel, blockpos));
+- serverlevel.gameEvent((Entity) null, GameEvent.BLOCK_PLACE, blockpos);
+- } else if (!CampfireBlock.canLight(blockstate) && !CandleBlock.canLight(blockstate) && !CandleCakeBlock.canLight(blockstate)) {
+- if (blockstate.getBlock() instanceof TntBlock) {
+- TntBlock.explode(serverlevel, blockpos);
+- serverlevel.removeBlock(blockpos, false);
++ if (BaseFireBlock.canBePlacedAt(worldserver, blockposition, enumdirection)) {
++ // CraftBukkit start - Ignition by dispensing flint and steel
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(worldserver, blockposition, sourceblock.pos()).isCancelled()) {
++ worldserver.setBlockAndUpdate(blockposition, BaseFireBlock.getState(worldserver, blockposition));
++ worldserver.gameEvent((Entity) null, GameEvent.BLOCK_PLACE, blockposition);
++ }
++ // CraftBukkit end
++ } else if (!CampfireBlock.canLight(iblockdata) && !CandleBlock.canLight(iblockdata) && !CandleCakeBlock.canLight(iblockdata)) {
++ if (iblockdata.getBlock() instanceof TntBlock && org.bukkit.craftbukkit.event.CraftEventFactory.callTNTPrimeEvent(worldserver, blockposition, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.DISPENSER, null, sourceblock.pos())) { // CraftBukkit - TNTPrimeEvent
++ TntBlock.explode(worldserver, blockposition);
++ worldserver.removeBlock(blockposition, false);
+ } else {
+ this.setSuccess(false);
+ }
+@@ -523,30 +798,108 @@
+ @Override
+ protected ItemStack execute(BlockSource blocksource, ItemStack itemstack) {
+ this.setSuccess(true);
+- ServerLevel serverlevel = blocksource.level();
+- BlockPos blockpos = blocksource.pos().relative((Direction) blocksource.state().getValue(DispenserBlock.FACING));
++ ServerLevel worldserver = sourceblock.level();
++ BlockPos blockposition = sourceblock.pos().relative((Direction) sourceblock.state().getValue(DispenserBlock.FACING));
++ // CraftBukkit start
++ org.bukkit.block.Block block = CraftBlock.at(worldserver, sourceblock.pos());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack);
+
+- if (!BoneMealItem.growCrop(itemstack, serverlevel, blockpos) && !BoneMealItem.growWaterPlant(itemstack, serverlevel, blockpos, (Direction) null)) {
++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector(0, 0, 0));
++ if (!DispenserBlock.eventFired) {
++ worldserver.getCraftServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem());
++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
++ idispensebehavior.dispense(sourceblock, eventStack);
++ return itemstack;
++ }
++ }
++
++ worldserver.captureTreeGeneration = true;
++ // CraftBukkit end
++
++ if (!BoneMealItem.growCrop(itemstack, worldserver, blockposition) && !BoneMealItem.growWaterPlant(itemstack, worldserver, blockposition, (Direction) null)) {
+ this.setSuccess(false);
+ } else if (!serverlevel.isClientSide) {
+ serverlevel.levelEvent(1505, blockpos, 0);
+ }
++ // CraftBukkit start
++ worldserver.captureTreeGeneration = false;
++ if (worldserver.capturedBlockStates.size() > 0) {
++ TreeType treeType = SaplingBlock.treeType;
++ SaplingBlock.treeType = null;
++ Location location = CraftLocation.toBukkit(blockposition, worldserver.getWorld());
++ List<org.bukkit.block.BlockState> blocks = new java.util.ArrayList<>(worldserver.capturedBlockStates.values());
++ worldserver.capturedBlockStates.clear();
++ StructureGrowEvent structureEvent = null;
++ if (treeType != null) {
++ structureEvent = new StructureGrowEvent(location, treeType, false, null, blocks);
++ org.bukkit.Bukkit.getPluginManager().callEvent(structureEvent);
++ }
+
++ BlockFertilizeEvent fertilizeEvent = new BlockFertilizeEvent(location.getBlock(), null, blocks);
++ fertilizeEvent.setCancelled(structureEvent != null && structureEvent.isCancelled());
++ org.bukkit.Bukkit.getPluginManager().callEvent(fertilizeEvent);
++
++ if (!fertilizeEvent.isCancelled()) {
++ for (org.bukkit.block.BlockState blockstate : blocks) {
++ blockstate.update(true);
++ }
++ }
++ }
++ // CraftBukkit end
++
+ return itemstack;
+ }
+ });
+ DispenserBlock.registerBehavior(Blocks.TNT, new DefaultDispenseItemBehavior() {
+ @Override
+- @Override
+- protected ItemStack execute(BlockSource blocksource, ItemStack itemstack) {
+- ServerLevel serverlevel = blocksource.level();
+- BlockPos blockpos = blocksource.pos().relative((Direction) blocksource.state().getValue(DispenserBlock.FACING));
+- PrimedTnt primedtnt = new PrimedTnt(serverlevel, (double) blockpos.getX() + 0.5D, (double) blockpos.getY(), (double) blockpos.getZ() + 0.5D, (LivingEntity) null);
++ protected ItemStack execute(SourceBlock sourceblock, ItemStack itemstack) {
++ ServerLevel worldserver = sourceblock.level();
++ BlockPos blockposition = sourceblock.pos().relative((Direction) sourceblock.state().getValue(DispenserBlock.FACING));
++ // CraftBukkit start
++ // EntityTNTPrimed entitytntprimed = new EntityTNTPrimed(worldserver, (double) blockposition.getX() + 0.5D, (double) blockposition.getY(), (double) blockposition.getZ() + 0.5D, (EntityLiving) null);
+
+- serverlevel.addFreshEntity(primedtnt);
+- serverlevel.playSound((Player) null, primedtnt.getX(), primedtnt.getY(), primedtnt.getZ(), SoundEvents.TNT_PRIMED, SoundSource.BLOCKS, 1.0F, 1.0F);
+- serverlevel.gameEvent((Entity) null, GameEvent.ENTITY_PLACE, blockpos);
+- itemstack.shrink(1);
++ ItemStack itemstack1 = itemstack.split(1);
++ org.bukkit.block.Block block = CraftBlock.at(worldserver, sourceblock.pos());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
++
++ BlockDispenseEvent event = new BlockDispenseEvent(block, craftItem.clone(), new org.bukkit.util.Vector((double) blockposition.getX() + 0.5D, (double) blockposition.getY(), (double) blockposition.getZ() + 0.5D));
++ if (!DispenserBlock.eventFired) {
++ worldserver.getCraftServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ itemstack.grow(1);
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ itemstack.grow(1);
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem());
++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
++ idispensebehavior.dispense(sourceblock, eventStack);
++ return itemstack;
++ }
++ }
++
++ PrimedTnt entitytntprimed = new PrimedTnt(worldserver, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), (LivingEntity) null);
++ // CraftBukkit end
++
++ worldserver.addFreshEntity(entitytntprimed);
++ worldserver.playSound((Player) null, entitytntprimed.getX(), entitytntprimed.getY(), entitytntprimed.getZ(), SoundEvents.TNT_PRIMED, SoundSource.BLOCKS, 1.0F, 1.0F);
++ worldserver.gameEvent((Entity) null, GameEvent.ENTITY_PLACE, blockposition);
++ // itemstack.shrink(1); // CraftBukkit - handled above
+ return itemstack;
+ }
+ });
+@@ -573,14 +924,15 @@
+ Direction direction = (Direction) blocksource.state().getValue(DispenserBlock.FACING);
+ BlockPos blockpos = blocksource.pos().relative(direction);
+
+- if (serverlevel.isEmptyBlock(blockpos) && WitherSkullBlock.canSpawnMob(serverlevel, blockpos, itemstack)) {
+- serverlevel.setBlock(blockpos, (BlockState) Blocks.WITHER_SKELETON_SKULL.defaultBlockState().setValue(SkullBlock.ROTATION, RotationSegment.convertToSegment(direction)), 3);
+- serverlevel.gameEvent((Entity) null, GameEvent.BLOCK_PLACE, blockpos);
+- BlockEntity blockentity = serverlevel.getBlockEntity(blockpos);
++ // CraftBukkit start
++ org.bukkit.block.Block bukkitBlock = CraftBlock.at(worldserver, sourceblock.pos());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack);
+
+ if (blockentity instanceof SkullBlockEntity) {
+ WitherSkullBlock.checkSpawn(serverlevel, blockpos, (SkullBlockEntity) blockentity);
+ }
++ }
++ // CraftBukkit end
+
+ itemstack.shrink(1);
+ this.setSuccess(true);
+@@ -599,11 +973,29 @@
+ BlockPos blockpos = blocksource.pos().relative((Direction) blocksource.state().getValue(DispenserBlock.FACING));
+ CarvedPumpkinBlock carvedpumpkinblock = (CarvedPumpkinBlock) Blocks.CARVED_PUMPKIN;
+
+- if (serverlevel.isEmptyBlock(blockpos) && carvedpumpkinblock.canSpawnGolem(serverlevel, blockpos)) {
+- if (!serverlevel.isClientSide) {
+- serverlevel.setBlock(blockpos, carvedpumpkinblock.defaultBlockState(), 3);
+- serverlevel.gameEvent((Entity) null, GameEvent.BLOCK_PLACE, blockpos);
++ // CraftBukkit start
++ org.bukkit.block.Block bukkitBlock = CraftBlock.at(worldserver, sourceblock.pos());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack);
++
++ BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ()));
++ if (!DispenserBlock.eventFired) {
++ worldserver.getCraftServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem());
++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
++ idispensebehavior.dispense(sourceblock, eventStack);
++ return itemstack;
+ }
++ }
++ // CraftBukkit end
+
+ itemstack.shrink(1);
+ this.setSuccess(true);
+@@ -649,10 +1046,34 @@
+ BlockPos blockpos = blocksource.pos().relative((Direction) blocksource.state().getValue(DispenserBlock.FACING));
+ BlockState blockstate = serverlevel.getBlockState(blockpos);
+
+- if (blockstate.is(BlockTags.BEEHIVES, (blockbehaviour_blockstatebase) -> {
+- return blockbehaviour_blockstatebase.hasProperty(BeehiveBlock.HONEY_LEVEL) && blockbehaviour_blockstatebase.getBlock() instanceof BeehiveBlock;
+- }) && (Integer) blockstate.getValue(BeehiveBlock.HONEY_LEVEL) >= 5) {
+- ((BeehiveBlock) blockstate.getBlock()).releaseBeesAndResetHoneyLevel(serverlevel, blockstate, blockpos, (Player) null, BeehiveBlockEntity.BeeReleaseStatus.BEE_RELEASED);
++ // CraftBukkit start
++ org.bukkit.block.Block bukkitBlock = CraftBlock.at(worldserver, sourceblock.pos());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack);
++
++ BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ()));
++ if (!DispenserBlock.eventFired) {
++ worldserver.getCraftServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem());
++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
++ idispensebehavior.dispense(sourceblock, eventStack);
++ return itemstack;
++ }
++ }
++ // CraftBukkit end
++
++ if (iblockdata.is(BlockTags.BEEHIVES, (blockbase_blockdata) -> {
++ return blockbase_blockdata.hasProperty(BeehiveBlock.HONEY_LEVEL) && blockbase_blockdata.getBlock() instanceof BeehiveBlock;
++ }) && (Integer) iblockdata.getValue(BeehiveBlock.HONEY_LEVEL) >= 5) {
++ ((BeehiveBlock) iblockdata.getBlock()).releaseBeesAndResetHoneyLevel(worldserver, iblockdata, blockposition, (Player) null, BeehiveBlockEntity.ReleaseStatus.BEE_RELEASED);
+ this.setSuccess(true);
+ return this.takeLiquid(blocksource, itemstack, new ItemStack(Items.HONEY_BOTTLE));
+ } else if (serverlevel.getFluidState(blockpos).is(FluidTags.WATER)) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java.patch
new file mode 100644
index 0000000000..d7fb8a82cc
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java.patch
@@ -0,0 +1,84 @@
+--- a/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java
++++ b/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java
+@@ -21,21 +21,47 @@
+ import net.minecraft.world.level.block.state.BlockState;
+ import net.minecraft.world.level.gameevent.GameEvent;
+ import net.minecraft.world.phys.AABB;
++// CraftBukkit start
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.block.BlockDispenseEvent;
++// CraftBukkit end
+
+ public class ShearsDispenseItemBehavior extends OptionalDispenseItemBehavior {
+
+ public ShearsDispenseItemBehavior() {}
+
+ @Override
+- @Override
+- protected ItemStack execute(BlockSource blocksource, ItemStack itemstack) {
+- ServerLevel serverlevel = blocksource.level();
++ protected ItemStack execute(SourceBlock sourceblock, ItemStack itemstack) {
++ ServerLevel worldserver = sourceblock.level();
++ // CraftBukkit start
++ org.bukkit.block.Block bukkitBlock = CraftBlock.at(worldserver, sourceblock.pos());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack);
+
+ if (!serverlevel.isClientSide()) {
+ BlockPos blockpos = blocksource.pos().relative((Direction) blocksource.state().getValue(DispenserBlock.FACING));
+
+- this.setSuccess(tryShearBeehive(serverlevel, blockpos) || tryShearLivingEntity(serverlevel, blockpos));
+- if (this.isSuccess() && itemstack.hurt(1, serverlevel.getRandom(), (ServerPlayer) null)) {
++ if (event.isCancelled()) {
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem());
++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
++ idispensebehavior.dispense(sourceblock, eventStack);
++ return itemstack;
++ }
++ }
++ // CraftBukkit end
++
++ if (!worldserver.isClientSide()) {
++ BlockPos blockposition = sourceblock.pos().relative((Direction) sourceblock.state().getValue(DispenserBlock.FACING));
++
++ this.setSuccess(tryShearBeehive(worldserver, blockposition) || tryShearLivingEntity(worldserver, blockposition, bukkitBlock, craftItem)); // CraftBukkit
++ if (this.isSuccess() && itemstack.hurt(1, worldserver.getRandom(), (ServerPlayer) null)) {
+ itemstack.setCount(0);
+ }
+ }
+@@ -63,8 +91,8 @@
+ return false;
+ }
+
+- private static boolean tryShearLivingEntity(ServerLevel serverlevel, BlockPos blockpos) {
+- List<LivingEntity> list = serverlevel.getEntitiesOfClass(LivingEntity.class, new AABB(blockpos), EntitySelector.NO_SPECTATORS);
++ private static boolean tryShearLivingEntity(ServerLevel worldserver, BlockPos blockposition, org.bukkit.block.Block bukkitBlock, CraftItemStack craftItem) { // CraftBukkit - add args
++ List<LivingEntity> list = worldserver.getEntitiesOfClass(LivingEntity.class, new AABB(blockposition), EntitySelector.NO_SPECTATORS);
+ Iterator iterator = list.iterator();
+
+ while (iterator.hasNext()) {
+@@ -73,9 +101,14 @@
+ if (livingentity instanceof Shearable) {
+ Shearable shearable = (Shearable) livingentity;
+
+- if (shearable.readyForShearing()) {
+- shearable.shear(SoundSource.BLOCKS);
+- serverlevel.gameEvent((Entity) null, GameEvent.SHEAR, blockpos);
++ if (ishearable.readyForShearing()) {
++ // CraftBukkit start
++ if (CraftEventFactory.callBlockShearEntityEvent(entityliving, bukkitBlock, craftItem).isCancelled()) {
++ continue;
++ }
++ // CraftBukkit end
++ ishearable.shear(SoundSource.BLOCKS);
++ worldserver.gameEvent((Entity) null, GameEvent.SHEAR, blockposition);
+ return true;
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java.patch
new file mode 100644
index 0000000000..6fe4c3518e
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java.patch
@@ -0,0 +1,46 @@
+--- a/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java
++++ b/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java
+@@ -10,6 +10,12 @@
+ import net.minecraft.world.level.block.DispenserBlock;
+ import org.slf4j.Logger;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.block.BlockDispenseEvent;
++// CraftBukkit end
++
+ public class ShulkerBoxDispenseBehavior extends OptionalDispenseItemBehavior {
+
+ private static final Logger LOGGER = LogUtils.getLogger();
+@@ -27,6 +32,30 @@
+ BlockPos blockpos = blocksource.pos().relative(direction);
+ Direction direction1 = blocksource.level().isEmptyBlock(blockpos.below()) ? direction : Direction.UP;
+
++ // CraftBukkit start
++ org.bukkit.block.Block bukkitBlock = CraftBlock.at(sourceblock.level(), sourceblock.pos());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack);
++
++ BlockDispenseEvent event = new BlockDispenseEvent(bukkitBlock, craftItem.clone(), new org.bukkit.util.Vector(blockposition.getX(), blockposition.getY(), blockposition.getZ()));
++ if (!DispenserBlock.eventFired) {
++ sourceblock.level().getCraftServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem());
++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
++ idispensebehavior.dispense(sourceblock, eventStack);
++ return itemstack;
++ }
++ }
++ // CraftBukkit end
++
+ try {
+ this.setSuccess(((BlockItem) item).place(new DirectionalPlaceContext(blocksource.level(), blockpos, direction, itemstack, direction1)).consumesAction());
+ } catch (Exception exception) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/nbt/ByteArrayTag.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/nbt/ByteArrayTag.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/nbt/ByteArrayTag.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/nbt/IntArrayTag.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/nbt/IntArrayTag.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/nbt/IntArrayTag.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/nbt/NbtIo.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/nbt/NbtIo.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/nbt/NbtIo.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/network/Connection.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/network/Connection.java.patch
new file mode 100644
index 0000000000..b0791435a2
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/network/Connection.java.patch
@@ -0,0 +1,10 @@
+--- a/net/minecraft/network/Connection.java
++++ b/net/minecraft/network/Connection.java
+@@ -105,6 +105,7 @@
+ private volatile Component delayedDisconnect;
+ @Nullable
+ BandwidthDebugMonitor bandwidthDebugMonitor;
++ public String hostname = ""; // CraftBukkit - add field
+
+ public Connection(PacketFlow packetflow) {
+ this.receiving = packetflow;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/network/FriendlyByteBuf.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/network/FriendlyByteBuf.java.patch
new file mode 100644
index 0000000000..ce4b89510c
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/network/FriendlyByteBuf.java.patch
@@ -0,0 +1,43 @@
+--- a/net/minecraft/network/FriendlyByteBuf.java
++++ b/net/minecraft/network/FriendlyByteBuf.java
+@@ -81,6 +81,8 @@
+ import org.joml.Quaternionf;
+ import org.joml.Vector3f;
+
++import org.bukkit.craftbukkit.inventory.CraftItemStack; // CraftBukkit
++
+ public class FriendlyByteBuf extends ByteBuf {
+
+ public static final int DEFAULT_NBT_QUOTA = 2097152;
+@@ -584,7 +586,7 @@
+ try {
+ NbtIo.writeAnyTag((Tag) tag, new ByteBufOutputStream(this));
+ return this;
+- } catch (IOException ioexception) {
++ } catch (Exception ioexception) { // CraftBukkit - IOException -> Exception
+ throw new EncoderException(ioexception);
+ }
+ }
+@@ -611,8 +613,8 @@
+ }
+ }
+
+- public FriendlyByteBuf writeItem(ItemStack itemstack) {
+- if (itemstack.isEmpty()) {
++ public FriendlyByteBuf writeItem(ItemStack stack) {
++ if (stack.isEmpty() || stack.getItem() == null) { // CraftBukkit - NPE fix itemstack.getItem()
+ this.writeBoolean(false);
+ } else {
+ this.writeBoolean(true);
+@@ -641,6 +643,11 @@
+ ItemStack itemstack = new ItemStack(item, b0);
+
+ itemstack.setTag(this.readNbt());
++ // CraftBukkit start
++ if (itemstack.getTag() != null) {
++ CraftItemStack.setItemMeta(itemstack, CraftItemStack.getItemMeta(itemstack));
++ }
++ // CraftBukkit end
+ return itemstack;
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/network/chat/Component.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/network/chat/Component.java.patch
new file mode 100644
index 0000000000..ca45aba148
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/network/chat/Component.java.patch
@@ -0,0 +1,27 @@
+--- a/net/minecraft/network/chat/Component.java
++++ b/net/minecraft/network/chat/Component.java
+@@ -34,9 +34,23 @@
+ import net.minecraft.resources.ResourceLocation;
+ import net.minecraft.util.FormattedCharSequence;
+ import net.minecraft.world.level.ChunkPos;
++// CraftBukkit start
++import java.util.stream.Stream;
++// CraftBukkit end
+
+-public interface Component extends Message, FormattedText {
++public interface Component extends Message, FormattedText, Iterable<Component> { // CraftBukkit
+
++ // CraftBukkit start
++ default Stream<Component> stream() {
++ return com.google.common.collect.Streams.concat(new Stream[]{Stream.of(this), this.getSiblings().stream().flatMap(Component::stream)});
++ }
++
++ @Override
++ default Iterator<Component> iterator() {
++ return this.stream().iterator();
++ }
++ // CraftBukkit end
++
+ Style getStyle();
+
+ ComponentContents getContents();
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/network/chat/TextColor.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/network/chat/TextColor.java.patch
new file mode 100644
index 0000000000..02e5b1bb3b
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/network/chat/TextColor.java.patch
@@ -0,0 +1,32 @@
+--- a/net/minecraft/network/chat/TextColor.java
++++ b/net/minecraft/network/chat/TextColor.java
+@@ -16,15 +16,18 @@
+
+ private static final String CUSTOM_COLOR_PREFIX = "#";
+ public static final Codec<TextColor> CODEC = Codec.STRING.comapFlatMap(TextColor::parseColor, TextColor::serialize);
+- private static final Map<ChatFormatting, TextColor> LEGACY_FORMAT_TO_COLOR = (Map) Stream.of(ChatFormatting.values()).filter(ChatFormatting::isColor).collect(ImmutableMap.toImmutableMap(Function.identity(), (chatformatting) -> {
+- return new TextColor(chatformatting.getColor(), chatformatting.getName());
++ private static final Map<ChatFormatting, TextColor> LEGACY_FORMAT_TO_COLOR = (Map) Stream.of(ChatFormatting.values()).filter(ChatFormatting::isColor).collect(ImmutableMap.toImmutableMap(Function.identity(), (enumchatformat) -> {
++ return new TextColor(enumchatformat.getColor(), enumchatformat.getName(), enumchatformat); // CraftBukkit
+ }));
+ private static final Map<String, TextColor> NAMED_COLORS = (Map) TextColor.LEGACY_FORMAT_TO_COLOR.values().stream().collect(ImmutableMap.toImmutableMap((textcolor) -> {
+ return textcolor.name;
+ }, Function.identity()));
+ private final int value;
+ @Nullable
+- private final String name;
++ public final String name;
++ // CraftBukkit start
++ @Nullable
++ public final ChatFormatting format;
+
+ private TextColor(int i, String s) {
+ this.value = i & 16777215;
+@@ -35,6 +40,7 @@
+ this.value = i & 16777215;
+ this.name = null;
+ }
++ // CraftBukkit end
+
+ public int getValue() {
+ return this.value;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/PacketUtils.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/PacketUtils.java.patch
new file mode 100644
index 0000000000..f2c12852ca
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/PacketUtils.java.patch
@@ -0,0 +1,47 @@
+--- a/net/minecraft/network/protocol/PacketUtils.java
++++ b/net/minecraft/network/protocol/PacketUtils.java
+@@ -4,8 +4,14 @@
+ import net.minecraft.CrashReport;
+ import net.minecraft.ReportedException;
+ import net.minecraft.network.PacketListener;
+-import net.minecraft.server.RunningOnDifferentThreadException;
++import net.minecraft.server.CancelledPacketHandleException;
++import org.slf4j.Logger;
++
++// CraftBukkit start
++import net.minecraft.server.MinecraftServer;
+ import net.minecraft.server.level.ServerLevel;
++import net.minecraft.server.network.ServerCommonPacketListenerImpl;
++// CraftBukkit end
+ import net.minecraft.util.thread.BlockableEventLoop;
+ import org.slf4j.Logger;
+
+@@ -19,10 +24,11 @@
+ ensureRunningOnSameThread(packet, t0, (BlockableEventLoop) serverlevel.getServer());
+ }
+
+- public static <T extends PacketListener> void ensureRunningOnSameThread(Packet<T> packet, T t0, BlockableEventLoop<?> blockableeventloop) throws RunningOnDifferentThreadException {
+- if (!blockableeventloop.isSameThread()) {
+- blockableeventloop.executeIfPossible(() -> {
+- if (t0.shouldHandleMessage(packet)) {
++ public static <T extends PacketListener> void ensureRunningOnSameThread(Packet<T> packet, T processor, BlockableEventLoop<?> executor) throws CancelledPacketHandleException {
++ if (!executor.isSameThread()) {
++ executor.executeIfPossible(() -> {
++ if (MinecraftServer.getServer().hasStopped() || (processor instanceof ServerCommonPacketListenerImpl && ((ServerCommonPacketListenerImpl) processor).processedDisconnect)) return; // CraftBukkit, MC-142590
++ if (processor.shouldHandleMessage(packet)) {
+ try {
+ packet.handle(t0);
+ } catch (Exception exception) {
+@@ -59,7 +65,11 @@
+ }
+
+ });
+- throw RunningOnDifferentThreadException.RUNNING_ON_DIFFERENT_THREAD;
++ throw CancelledPacketHandleException.RUNNING_ON_DIFFERENT_THREAD;
++ // CraftBukkit start - SPIGOT-5477, MC-142590
++ } else if (MinecraftServer.getServer().hasStopped() || (processor instanceof ServerCommonPacketListenerImpl && ((ServerCommonPacketListenerImpl) processor).processedDisconnect)) {
++ throw CancelledPacketHandleException.RUNNING_ON_DIFFERENT_THREAD;
++ // CraftBukkit end
+ }
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch
new file mode 100644
index 0000000000..c95bd4e06e
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch
@@ -0,0 +1,44 @@
+--- a/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java
++++ b/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java
+@@ -12,7 +12,7 @@
+ public record ServerboundCustomPayloadPacket(CustomPacketPayload payload) implements Packet<ServerCommonPacketListener> {
+
+ private static final int MAX_PAYLOAD_SIZE = 32767;
+- private static final Map<ResourceLocation, FriendlyByteBuf.Reader<? extends CustomPacketPayload>> KNOWN_TYPES = ImmutableMap.builder().put(BrandPayload.ID, BrandPayload::new).build();
++ private static final Map<ResourceLocation, FriendlyByteBuf.a<? extends CustomPacketPayload>> KNOWN_TYPES = ImmutableMap.<ResourceLocation, FriendlyByteBuf.a<? extends CustomPacketPayload>>builder().build(); // CraftBukkit - no special handling
+
+ public ServerboundCustomPayloadPacket(FriendlyByteBuf friendlybytebuf) {
+ this(readPayload(friendlybytebuf.readResourceLocation(), friendlybytebuf));
+@@ -24,12 +24,13 @@
+ return (CustomPacketPayload) (friendlybytebuf_reader != null ? (CustomPacketPayload) friendlybytebuf_reader.apply(friendlybytebuf) : readUnknownPayload(resourcelocation, friendlybytebuf));
+ }
+
+- private static DiscardedPayload readUnknownPayload(ResourceLocation resourcelocation, FriendlyByteBuf friendlybytebuf) {
+- int i = friendlybytebuf.readableBytes();
++ private static UnknownPayload readUnknownPayload(ResourceLocation minecraftkey, FriendlyByteBuf packetdataserializer) { // CraftBukkit
++ int i = packetdataserializer.readableBytes();
+
+ if (i >= 0 && i <= 32767) {
+- friendlybytebuf.skipBytes(i);
+- return new DiscardedPayload(resourcelocation);
++ // CraftBukkit start
++ return new UnknownPayload(minecraftkey, packetdataserializer.readBytes(i));
++ // CraftBukkit end
+ } else {
+ throw new IllegalArgumentException("Payload may not be larger than 32767 bytes");
+ }
+@@ -46,4 +45,14 @@
+ public void handle(ServerCommonPacketListener servercommonpacketlistener) {
+ servercommonpacketlistener.handleCustomPayload(this);
+ }
++
++ // CraftBukkit start
++ public record UnknownPayload(ResourceLocation id, io.netty.buffer.ByteBuf data) implements CustomPacketPayload {
++
++ @Override
++ public void write(FriendlyByteBuf packetdataserializer) {
++ packetdataserializer.writeBytes(data);
++ }
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/game/ClientboundInitializeBorderPacket.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/game/ClientboundInitializeBorderPacket.java.patch
new file mode 100644
index 0000000000..294bfa4ffa
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/game/ClientboundInitializeBorderPacket.java.patch
@@ -0,0 +1,29 @@
+--- a/net/minecraft/network/protocol/game/ClientboundInitializeBorderPacket.java
++++ b/net/minecraft/network/protocol/game/ClientboundInitializeBorderPacket.java
+@@ -26,15 +26,17 @@
+ this.warningTime = friendlybytebuf.readVarInt();
+ }
+
+- public ClientboundInitializeBorderPacket(WorldBorder worldborder) {
+- this.newCenterX = worldborder.getCenterX();
+- this.newCenterZ = worldborder.getCenterZ();
+- this.oldSize = worldborder.getSize();
+- this.newSize = worldborder.getLerpTarget();
+- this.lerpTime = worldborder.getLerpRemainingTime();
+- this.newAbsoluteMaxSize = worldborder.getAbsoluteMaxSize();
+- this.warningBlocks = worldborder.getWarningBlocks();
+- this.warningTime = worldborder.getWarningTime();
++ public ClientboundInitializeBorderPacket(WorldBorder worldBorder) {
++ // CraftBukkit start - multiply out nether border
++ this.newCenterX = worldBorder.getCenterX() * worldBorder.world.dimensionType().coordinateScale();
++ this.newCenterZ = worldBorder.getCenterZ() * worldBorder.world.dimensionType().coordinateScale();
++ // CraftBukkit end
++ this.oldSize = worldBorder.getSize();
++ this.newSize = worldBorder.getLerpTarget();
++ this.lerpTime = worldBorder.getLerpRemainingTime();
++ this.newAbsoluteMaxSize = worldBorder.getAbsoluteMaxSize();
++ this.warningBlocks = worldBorder.getWarningBlocks();
++ this.warningTime = worldBorder.getWarningTime();
+ }
+
+ @Override
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java.patch
new file mode 100644
index 0000000000..0f91165711
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java.patch
@@ -0,0 +1,25 @@
+--- a/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java
++++ b/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java
+@@ -30,14 +30,18 @@
+ short short0 = (Short) shortiterator.next();
+
+ this.positions[j] = short0;
+- this.states[j] = levelchunksection.getBlockState(SectionPos.sectionRelativeX(short0), SectionPos.sectionRelativeY(short0), SectionPos.sectionRelativeZ(short0));
++ this.states[j] = (section != null) ? section.getBlockState(SectionPos.sectionRelativeX(short0), SectionPos.sectionRelativeY(short0), SectionPos.sectionRelativeZ(short0)) : net.minecraft.world.level.block.Blocks.AIR.defaultBlockState(); // CraftBukkit - SPIGOT-6076, Mojang bug when empty chunk section notified
+ }
+
+ }
+
+- public ClientboundSectionBlocksUpdatePacket(FriendlyByteBuf friendlybytebuf) {
+- this.sectionPos = SectionPos.of(friendlybytebuf.readLong());
+- int i = friendlybytebuf.readVarInt();
++ // CraftBukkit start - Add constructor
++ public ClientboundSectionBlocksUpdatePacket(SectionPos sectionposition, ShortSet shortset, IBlockData[] states) {
++ this.sectionPos = sectionposition;
++ this.positions = shortset.toShortArray();
++ this.states = states;
++ }
++ // CraftBukkit end
+
+ this.positions = new short[i];
+ this.states = new BlockState[i];
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/game/ClientboundSetBorderCenterPacket.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/game/ClientboundSetBorderCenterPacket.java.patch
new file mode 100644
index 0000000000..3439ec4759
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/game/ClientboundSetBorderCenterPacket.java.patch
@@ -0,0 +1,17 @@
+--- a/net/minecraft/network/protocol/game/ClientboundSetBorderCenterPacket.java
++++ b/net/minecraft/network/protocol/game/ClientboundSetBorderCenterPacket.java
+@@ -9,9 +9,11 @@
+ private final double newCenterX;
+ private final double newCenterZ;
+
+- public ClientboundSetBorderCenterPacket(WorldBorder worldborder) {
+- this.newCenterX = worldborder.getCenterX();
+- this.newCenterZ = worldborder.getCenterZ();
++ public ClientboundSetBorderCenterPacket(WorldBorder worldBorder) {
++ // CraftBukkit start - multiply out nether border
++ this.newCenterX = worldBorder.getCenterX() * (worldBorder.world != null ? worldBorder.world.dimensionType().coordinateScale() : 1.0);
++ this.newCenterZ = worldBorder.getCenterZ() * (worldBorder.world != null ? worldBorder.world.dimensionType().coordinateScale() : 1.0);
++ // CraftBukkit end
+ }
+
+ public ClientboundSetBorderCenterPacket(FriendlyByteBuf friendlybytebuf) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/game/ServerboundUseItemOnPacket.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/game/ServerboundUseItemOnPacket.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/game/ServerboundUseItemOnPacket.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/game/ServerboundUseItemPacket.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/game/ServerboundUseItemPacket.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/game/ServerboundUseItemPacket.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/network/syncher/SynchedEntityData.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/network/syncher/SynchedEntityData.java.patch
new file mode 100644
index 0000000000..319a688f64
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/network/syncher/SynchedEntityData.java.patch
@@ -0,0 +1,35 @@
+--- a/net/minecraft/network/syncher/SynchedEntityData.java
++++ b/net/minecraft/network/syncher/SynchedEntityData.java
+@@ -146,6 +148,13 @@
+
+ }
+
++ // CraftBukkit start - add method from above
++ public <T> void markDirty(EntityDataAccessor<T> datawatcherobject) {
++ this.getItem(datawatcherobject).setDirty(true);
++ this.isDirty = true;
++ }
++ // CraftBukkit end
++
+ public boolean isDirty() {
+ return this.isDirty;
+ }
+@@ -235,6 +244,18 @@
+ return this.itemsById.isEmpty();
+ }
+
++ // CraftBukkit start
++ public void refresh(ServerPlayer to) {
++ if (!this.isEmpty()) {
++ List<SynchedEntityData.DataValue<?>> list = this.getNonDefaultValues();
++
++ if (list != null) {
++ to.connection.send(new ClientboundSetEntityDataPacket(this.entity.getId(), list));
++ }
++ }
++ }
++ // CraftBukkit end
++
+ public static class DataItem<T> {
+
+ final EntityDataAccessor<T> accessor;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/Bootstrap.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/Bootstrap.java.patch
new file mode 100644
index 0000000000..aa93e3c2f3
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/Bootstrap.java.patch
@@ -0,0 +1,96 @@
+--- a/net/minecraft/server/Bootstrap.java
++++ b/net/minecraft/server/Bootstrap.java
+@@ -41,6 +44,23 @@
+
+ public static void bootStrap() {
+ if (!Bootstrap.isBootstrapped) {
++ // CraftBukkit start
++ String name = Bootstrap.class.getSimpleName();
++ switch (name) {
++ case "DispenserRegistry":
++ break;
++ case "Bootstrap":
++ System.err.println("***************************************************************************");
++ System.err.println("*** WARNING: This server jar may only be used for development purposes. ***");
++ System.err.println("***************************************************************************");
++ break;
++ default:
++ System.err.println("**********************************************************************");
++ System.err.println("*** WARNING: This server jar is unsupported, use at your own risk. ***");
++ System.err.println("**********************************************************************");
++ break;
++ }
++ // CraftBukkit end
+ Bootstrap.isBootstrapped = true;
+ Instant instant = Instant.now();
+
+@@ -61,6 +81,69 @@
+ wrapStreams();
+ Bootstrap.bootstrapDuration.set(Duration.between(instant, Instant.now()).toMillis());
+ }
++ // CraftBukkit start - easier than fixing the decompile
++ BlockStateData.register(1008, "{Name:'minecraft:oak_sign',Properties:{rotation:'0'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'0'}}");
++ BlockStateData.register(1009, "{Name:'minecraft:oak_sign',Properties:{rotation:'1'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'1'}}");
++ BlockStateData.register(1010, "{Name:'minecraft:oak_sign',Properties:{rotation:'2'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'2'}}");
++ BlockStateData.register(1011, "{Name:'minecraft:oak_sign',Properties:{rotation:'3'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'3'}}");
++ BlockStateData.register(1012, "{Name:'minecraft:oak_sign',Properties:{rotation:'4'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'4'}}");
++ BlockStateData.register(1013, "{Name:'minecraft:oak_sign',Properties:{rotation:'5'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'5'}}");
++ BlockStateData.register(1014, "{Name:'minecraft:oak_sign',Properties:{rotation:'6'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'6'}}");
++ BlockStateData.register(1015, "{Name:'minecraft:oak_sign',Properties:{rotation:'7'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'7'}}");
++ BlockStateData.register(1016, "{Name:'minecraft:oak_sign',Properties:{rotation:'8'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'8'}}");
++ BlockStateData.register(1017, "{Name:'minecraft:oak_sign',Properties:{rotation:'9'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'9'}}");
++ BlockStateData.register(1018, "{Name:'minecraft:oak_sign',Properties:{rotation:'10'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'10'}}");
++ BlockStateData.register(1019, "{Name:'minecraft:oak_sign',Properties:{rotation:'11'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'11'}}");
++ BlockStateData.register(1020, "{Name:'minecraft:oak_sign',Properties:{rotation:'12'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'12'}}");
++ BlockStateData.register(1021, "{Name:'minecraft:oak_sign',Properties:{rotation:'13'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'13'}}");
++ BlockStateData.register(1022, "{Name:'minecraft:oak_sign',Properties:{rotation:'14'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'14'}}");
++ BlockStateData.register(1023, "{Name:'minecraft:oak_sign',Properties:{rotation:'15'}}", "{Name:'minecraft:standing_sign',Properties:{rotation:'15'}}");
++ ItemIdFix.ITEM_NAMES.put(323, "minecraft:oak_sign");
++
++ BlockStateData.register(1440, "{Name:\'minecraft:portal\',Properties:{axis:\'x\'}}", new String[]{"{Name:\'minecraft:portal\',Properties:{axis:\'x\'}}"});
++
++ ItemIdFix.ITEM_NAMES.put(409, "minecraft:prismarine_shard");
++ ItemIdFix.ITEM_NAMES.put(410, "minecraft:prismarine_crystals");
++ ItemIdFix.ITEM_NAMES.put(411, "minecraft:rabbit");
++ ItemIdFix.ITEM_NAMES.put(412, "minecraft:cooked_rabbit");
++ ItemIdFix.ITEM_NAMES.put(413, "minecraft:rabbit_stew");
++ ItemIdFix.ITEM_NAMES.put(414, "minecraft:rabbit_foot");
++ ItemIdFix.ITEM_NAMES.put(415, "minecraft:rabbit_hide");
++ ItemIdFix.ITEM_NAMES.put(416, "minecraft:armor_stand");
++
++ ItemIdFix.ITEM_NAMES.put(423, "minecraft:mutton");
++ ItemIdFix.ITEM_NAMES.put(424, "minecraft:cooked_mutton");
++ ItemIdFix.ITEM_NAMES.put(425, "minecraft:banner");
++ ItemIdFix.ITEM_NAMES.put(426, "minecraft:end_crystal");
++ ItemIdFix.ITEM_NAMES.put(427, "minecraft:spruce_door");
++ ItemIdFix.ITEM_NAMES.put(428, "minecraft:birch_door");
++ ItemIdFix.ITEM_NAMES.put(429, "minecraft:jungle_door");
++ ItemIdFix.ITEM_NAMES.put(430, "minecraft:acacia_door");
++ ItemIdFix.ITEM_NAMES.put(431, "minecraft:dark_oak_door");
++ ItemIdFix.ITEM_NAMES.put(432, "minecraft:chorus_fruit");
++ ItemIdFix.ITEM_NAMES.put(433, "minecraft:chorus_fruit_popped");
++ ItemIdFix.ITEM_NAMES.put(434, "minecraft:beetroot");
++ ItemIdFix.ITEM_NAMES.put(435, "minecraft:beetroot_seeds");
++ ItemIdFix.ITEM_NAMES.put(436, "minecraft:beetroot_soup");
++ ItemIdFix.ITEM_NAMES.put(437, "minecraft:dragon_breath");
++ ItemIdFix.ITEM_NAMES.put(438, "minecraft:splash_potion");
++ ItemIdFix.ITEM_NAMES.put(439, "minecraft:spectral_arrow");
++ ItemIdFix.ITEM_NAMES.put(440, "minecraft:tipped_arrow");
++ ItemIdFix.ITEM_NAMES.put(441, "minecraft:lingering_potion");
++ ItemIdFix.ITEM_NAMES.put(442, "minecraft:shield");
++ ItemIdFix.ITEM_NAMES.put(443, "minecraft:elytra");
++ ItemIdFix.ITEM_NAMES.put(444, "minecraft:spruce_boat");
++ ItemIdFix.ITEM_NAMES.put(445, "minecraft:birch_boat");
++ ItemIdFix.ITEM_NAMES.put(446, "minecraft:jungle_boat");
++ ItemIdFix.ITEM_NAMES.put(447, "minecraft:acacia_boat");
++ ItemIdFix.ITEM_NAMES.put(448, "minecraft:dark_oak_boat");
++ ItemIdFix.ITEM_NAMES.put(449, "minecraft:totem_of_undying");
++ ItemIdFix.ITEM_NAMES.put(450, "minecraft:shulker_shell");
++ ItemIdFix.ITEM_NAMES.put(452, "minecraft:iron_nugget");
++ ItemIdFix.ITEM_NAMES.put(453, "minecraft:knowledge_book");
++
++ ItemSpawnEggFix.ID_TO_ENTITY[23] = "Arrow";
++ // CraftBukkit end
+ }
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/Main.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/Main.java.patch
new file mode 100644
index 0000000000..b3319f07a7
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/Main.java.patch
@@ -0,0 +1,194 @@
+--- a/net/minecraft/server/Main.java
++++ b/net/minecraft/server/Main.java
+@@ -60,6 +60,14 @@
+ import net.minecraft.world.level.storage.WorldData;
+ import org.slf4j.Logger;
+
++// CraftBukkit start
++import com.google.common.base.Charsets;
++import java.io.InputStreamReader;
++import java.util.concurrent.atomic.AtomicReference;
++import net.minecraft.SharedConstants;
++import org.bukkit.configuration.file.YamlConfiguration;
++// CraftBukkit end
++
+ public class Main {
+
+ private static final Logger LOGGER = LogUtils.getLogger();
+@@ -67,8 +75,9 @@
+ public Main() {}
+
+ @DontObfuscate
+- public static void main(String[] astring) {
++ public static void main(final OptionSet optionset) { // CraftBukkit - replaces main(String[] astring)
+ SharedConstants.tryDetectVersion();
++ /* CraftBukkit start - Replace everything
+ OptionParser optionparser = new OptionParser();
+ OptionSpec<Void> optionspec = optionparser.accepts("nogui");
+ OptionSpec<Void> optionspec1 = optionparser.accepts("initSettings", "Initializes 'server.properties' and 'eula.txt', then quits");
+@@ -93,15 +102,18 @@
+ optionparser.printHelpOn(System.err);
+ return;
+ }
++ */ // CraftBukkit end
+
+ Path path = (Path) optionset.valueOf(optionspec13);
+
++ Path path = (Path) optionset.valueOf("pidFile"); // CraftBukkit
++
+ if (path != null) {
+ writePidFile(path);
+ }
+
+ CrashReport.preload();
+- if (optionset.has(optionspec12)) {
++ if (optionset.has("jfrProfile")) { // CraftBukkit
+ JvmProfiler.INSTANCE.start(Environment.SERVER);
+ }
+
+@@ -109,13 +121,26 @@
+ Bootstrap.validate();
+ Util.startTimerHackThread();
+ Path path1 = Paths.get("server.properties");
+- DedicatedServerSettings dedicatedserversettings = new DedicatedServerSettings(path1);
++ DedicatedServerSettings dedicatedserversettings = new DedicatedServerSettings(optionset); // CraftBukkit - CLI argument support
+
+ dedicatedserversettings.forceSave();
+ Path path2 = Paths.get("eula.txt");
+ Eula eula = new Eula(path2);
+
+- if (optionset.has(optionspec1)) {
++ if (optionset.has("initSettings")) { // CraftBukkit
++ // CraftBukkit start - SPIGOT-5761: Create bukkit.yml and commands.yml if not present
++ File configFile = (File) optionset.valueOf("bukkit-settings");
++ YamlConfiguration configuration = YamlConfiguration.loadConfiguration(configFile);
++ configuration.options().copyDefaults(true);
++ configuration.setDefaults(YamlConfiguration.loadConfiguration(new InputStreamReader(Main.class.getClassLoader().getResourceAsStream("configurations/bukkit.yml"), Charsets.UTF_8)));
++ configuration.save(configFile);
++
++ File commandFile = (File) optionset.valueOf("commands-settings");
++ YamlConfiguration commandsConfiguration = YamlConfiguration.loadConfiguration(commandFile);
++ commandsConfiguration.options().copyDefaults(true);
++ commandsConfiguration.setDefaults(YamlConfiguration.loadConfiguration(new InputStreamReader(Main.class.getClassLoader().getResourceAsStream("configurations/commands.yml"), Charsets.UTF_8)));
++ commandsConfiguration.save(commandFile);
++ // CraftBukkit end
+ Main.LOGGER.info("Initialized '{}' and '{}'", path1.toAbsolutePath(), path2.toAbsolutePath());
+ return;
+ }
+@@ -125,11 +150,13 @@
+ return;
+ }
+
+- File file = new File((String) optionset.valueOf(optionspec8));
++ File file = (File) optionset.valueOf("universe"); // CraftBukkit
+ Services services = Services.create(new YggdrasilAuthenticationService(Proxy.NO_PROXY), file);
+- String s = (String) Optional.ofNullable((String) optionset.valueOf(optionspec9)).orElse(dedicatedserversettings.getProperties().levelName);
+- LevelStorageSource levelstoragesource = LevelStorageSource.createDefault(file.toPath());
+- LevelStorageSource.LevelStorageAccess levelstoragesource_levelstorageaccess = levelstoragesource.validateAndCreateAccess(s);
++ // CraftBukkit start
++ String s = (String) Optional.ofNullable((String) optionset.valueOf("world")).orElse(dedicatedserversettings.getProperties().levelName);
++ LevelStorageSource convertable = LevelStorageSource.createDefault(file.toPath());
++ LevelStorageSource.LevelStorageAccess convertable_conversionsession = convertable.validateAndCreateAccess(s, LevelStem.OVERWORLD);
++ // CraftBukkit end
+ Dynamic dynamic;
+
+ if (levelstoragesource_levelstorageaccess.hasWorldData()) {
+@@ -170,13 +197,31 @@
+ }
+
+ Dynamic<?> dynamic1 = dynamic;
+- boolean flag = optionset.has(optionspec6);
++ boolean flag = optionset.has("safeMode"); // CraftBukkit
+
+ if (flag) {
+ Main.LOGGER.warn("Safe mode active, only vanilla datapack will be loaded");
+ }
+
+- PackRepository packrepository = ServerPacksSource.createPackRepository(levelstoragesource_levelstorageaccess);
++ PackRepository resourcepackrepository = ServerPacksSource.createPackRepository(convertable_conversionsession);
++ // CraftBukkit start
++ File bukkitDataPackFolder = new File(convertable_conversionsession.getLevelPath(LevelResource.DATAPACK_DIR).toFile(), "bukkit");
++ if (!bukkitDataPackFolder.exists()) {
++ bukkitDataPackFolder.mkdirs();
++ }
++ File mcMeta = new File(bukkitDataPackFolder, "pack.mcmeta");
++ try {
++ com.google.common.io.Files.write("{\n"
++ + " \"pack\": {\n"
++ + " \"description\": \"Data pack for resources provided by Bukkit plugins\",\n"
++ + " \"pack_format\": " + SharedConstants.getCurrentVersion().getPackVersion(PackType.SERVER_DATA) + "\n"
++ + " }\n"
++ + "}\n", mcMeta, com.google.common.base.Charsets.UTF_8);
++ } catch (java.io.IOException ex) {
++ throw new RuntimeException("Could not initialize Bukkit datapack", ex);
++ }
++ AtomicReference<WorldLoader.a> worldLoader = new AtomicReference<>();
++ // CraftBukkit end
+
+ WorldStem worldstem;
+
+@@ -184,8 +229,9 @@
+ WorldLoader.InitConfig worldloader_initconfig = loadOrCreateConfig(dedicatedserversettings.getProperties(), dynamic1, flag, packrepository);
+
+ worldstem = (WorldStem) Util.blockUntilDone((executor) -> {
+- return WorldLoader.load(worldloader_initconfig, (worldloader_dataloadcontext) -> {
+- Registry<LevelStem> registry = worldloader_dataloadcontext.datapackDimensions().registryOrThrow(Registries.LEVEL_STEM);
++ return WorldLoader.load(worldloader_c, (worldloader_a) -> {
++ worldLoader.set(worldloader_a); // CraftBukkit
++ Registry<LevelStem> iregistry = worldloader_a.datapackDimensions().registryOrThrow(Registries.LEVEL_STEM);
+
+ if (dynamic1 != null) {
+ LevelDataAndDimensions leveldataanddimensions = LevelStorageSource.getLevelDataAndDimensions(dynamic1, worldloader_dataloadcontext.dataConfiguration(), registry, worldloader_dataloadcontext.datapackWorldgen());
+@@ -197,16 +243,16 @@
+ WorldOptions worldoptions;
+ WorldDimensions worlddimensions;
+
+- if (optionset.has(optionspec2)) {
+- levelsettings = MinecraftServer.DEMO_SETTINGS;
++ if (optionset.has("demo")) { // CraftBukkit
++ worldsettings = MinecraftServer.DEMO_SETTINGS;
+ worldoptions = WorldOptions.DEMO_OPTIONS;
+ worlddimensions = WorldPresets.createNormalWorldDimensions(worldloader_dataloadcontext.datapackWorldgen());
+ } else {
+ DedicatedServerProperties dedicatedserverproperties = dedicatedserversettings.getProperties();
+
+- levelsettings = new LevelSettings(dedicatedserverproperties.levelName, dedicatedserverproperties.gamemode, dedicatedserverproperties.hardcore, dedicatedserverproperties.difficulty, false, new GameRules(), worldloader_dataloadcontext.dataConfiguration());
+- worldoptions = optionset.has(optionspec3) ? dedicatedserverproperties.worldOptions.withBonusChest(true) : dedicatedserverproperties.worldOptions;
+- worlddimensions = dedicatedserverproperties.createDimensions(worldloader_dataloadcontext.datapackWorldgen());
++ worldsettings = new LevelSettings(dedicatedserverproperties.levelName, dedicatedserverproperties.gamemode, dedicatedserverproperties.hardcore, dedicatedserverproperties.difficulty, false, new GameRules(), worldloader_a.dataConfiguration());
++ worldoptions = optionset.has("bonusChest") ? dedicatedserverproperties.worldOptions.withBonusChest(true) : dedicatedserverproperties.worldOptions; // CraftBukkit
++ worlddimensions = dedicatedserverproperties.createDimensions(worldloader_a.datapackWorldgen());
+ }
+
+ WorldDimensions.Complete worlddimensions_complete = worlddimensions.bake(registry);
+@@ -246,6 +303,7 @@
+
+ return dedicatedserver1;
+ });
++ /* CraftBukkit start
+ Thread thread = new Thread("Server Shutdown Thread") {
+ @Override
+ public void run() {
+@@ -255,6 +312,7 @@
+
+ thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(Main.LOGGER));
+ Runtime.getRuntime().addShutdownHook(thread);
++ */ // CraftBukkit end
+ } catch (Exception exception1) {
+ Main.LOGGER.error(LogUtils.FATAL_MARKER, "Failed to start the minecraft server", exception1);
+ }
+@@ -290,10 +348,10 @@
+ return new WorldLoader.InitConfig(worldloader_packconfig, Commands.CommandSelection.DEDICATED, dedicatedserverproperties.functionPermissionLevel);
+ }
+
+- private static void forceUpgrade(LevelStorageSource.LevelStorageAccess levelstoragesource_levelstorageaccess, DataFixer datafixer, boolean flag, BooleanSupplier booleansupplier, Registry<LevelStem> registry) {
+- Main.LOGGER.info("Forcing world upgrade!");
+- WorldUpgrader worldupgrader = new WorldUpgrader(levelstoragesource_levelstorageaccess, datafixer, registry, flag);
+- Component component = null;
++ public static void forceUpgrade(LevelStorageSource.LevelStorageAccess levelStorage, DataFixer dataFixer, boolean eraseCache, BooleanSupplier upgradeWorld, Registry<LevelStem> dimesions) {
++ Main.LOGGER.info("Forcing world upgrade! {}", levelStorage.getLevelId()); // CraftBukkit
++ WorldUpgrader worldupgrader = new WorldUpgrader(levelStorage, dataFixer, dimesions, eraseCache);
++ Component ichatbasecomponent = null;
+
+ while (!worldupgrader.isFinished()) {
+ Component component1 = worldupgrader.getStatus();
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/MinecraftServer.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/MinecraftServer.java.patch
new file mode 100644
index 0000000000..3879c6d86f
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/MinecraftServer.java.patch
@@ -0,0 +1,682 @@
+--- a/net/minecraft/server/MinecraftServer.java
++++ b/net/minecraft/server/MinecraftServer.java
+@@ -151,6 +150,23 @@
+ import net.minecraft.world.level.levelgen.WorldOptions;
+ import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
+ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
++import net.minecraft.world.level.storage.WorldData;
++import net.minecraft.world.level.storage.loot.LootDataManager;
++import org.slf4j.Logger;
++
++// CraftBukkit start
++import com.mojang.serialization.Dynamic;
++import com.mojang.serialization.Lifecycle;
++import java.util.Random;
++import jline.console.ConsoleReader;
++import joptsimple.OptionSet;
++import net.minecraft.nbt.NbtException;
++import net.minecraft.nbt.ReportedNbtException;
++import net.minecraft.server.bossevents.CustomBossEvents;
++import net.minecraft.server.dedicated.DedicatedServer;
++import net.minecraft.server.dedicated.DedicatedServerProperties;
++import net.minecraft.world.level.levelgen.WorldDimensions;
++import net.minecraft.world.level.levelgen.presets.WorldPresets;
+ import net.minecraft.world.level.storage.CommandStorage;
+ import net.minecraft.world.level.storage.DerivedLevelData;
+ import net.minecraft.world.level.storage.DimensionDataStorage;
+@@ -163,7 +180,11 @@
+ import net.minecraft.world.level.storage.loot.LootDataManager;
+ import net.minecraft.world.phys.Vec2;
+ import net.minecraft.world.phys.Vec3;
+-import org.slf4j.Logger;
++import org.bukkit.Bukkit;
++import org.bukkit.craftbukkit.CraftServer;
++import org.bukkit.craftbukkit.Main;
++import org.bukkit.event.server.ServerLoadEvent;
++// CraftBukkit end
+
+ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTask> implements ServerInfo, CommandSource, AutoCloseable {
+
+@@ -171,7 +192,7 @@
+ public static final String VANILLA_BRAND = "vanilla";
+ private static final float AVERAGE_TICK_TIME_SMOOTHING = 0.8F;
+ private static final int TICK_STATS_SPAN = 100;
+- private static final long OVERLOADED_THRESHOLD_NANOS = 20L * TimeUtil.NANOSECONDS_PER_SECOND / 20L;
++ private static final long OVERLOADED_THRESHOLD_NANOS = 30L * TimeUtil.NANOSECONDS_PER_SECOND / 20L; // CraftBukkit
+ private static final int OVERLOADED_TICKS_THRESHOLD = 20;
+ private static final long OVERLOADED_WARNING_INTERVAL_NANOS = 10L * TimeUtil.NANOSECONDS_PER_SECOND;
+ private static final int OVERLOADED_TICKS_WARNING_INTERVAL = 100;
+@@ -254,7 +275,20 @@
+ protected final WorldData worldData;
+ private volatile boolean isSaving;
+
+- public static <S extends MinecraftServer> S spin(Function<Thread, S> function) {
++ // CraftBukkit start
++ public final WorldLoader.a worldLoader;
++ public org.bukkit.craftbukkit.CraftServer server;
++ public OptionSet options;
++ public org.bukkit.command.ConsoleCommandSender console;
++ public ConsoleReader reader;
++ public static int currentTick = (int) (System.currentTimeMillis() / 50);
++ public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
++ public int autosavePeriod;
++ public Commands vanillaCommandDispatcher;
++ private boolean forceTicks;
++ // CraftBukkit end
++
++ public static <S extends MinecraftServer> S spin(Function<Thread, S> threadFunction) {
+ AtomicReference<S> atomicreference = new AtomicReference();
+ Thread thread = new Thread(() -> {
+ ((MinecraftServer) atomicreference.get()).runServer();
+@@ -295,7 +329,7 @@
+ this.customBossEvents = new CustomBossEvents();
+ this.registries = worldstem.registries();
+ this.worldData = worldstem.worldData();
+- if (!this.registries.compositeAccess().registryOrThrow(Registries.LEVEL_STEM).containsKey(LevelStem.OVERWORLD)) {
++ if (false && !this.registries.compositeAccess().registryOrThrow(Registries.LEVEL_STEM).containsKey(LevelStem.OVERWORLD)) { // CraftBukkit - initialised later
+ throw new IllegalStateException("Missing Overworld dimension data");
+ } else {
+ this.proxy = proxy;
+@@ -319,6 +353,33 @@
+ this.serverThread = thread;
+ this.executor = Util.backgroundExecutor();
+ }
++ // CraftBukkit start
++ this.options = options;
++ this.worldLoader = worldLoader;
++ this.vanillaCommandDispatcher = worldstem.dataPackResources().commands; // CraftBukkit
++ // Try to see if we're actually running in a terminal, disable jline if not
++ if (System.console() == null && System.getProperty("jline.terminal") == null) {
++ System.setProperty("jline.terminal", "jline.UnsupportedTerminal");
++ Main.useJline = false;
++ }
++
++ try {
++ reader = new ConsoleReader(System.in, System.out);
++ reader.setExpandEvents(false); // Avoid parsing exceptions for uncommonly used event designators
++ } catch (Throwable e) {
++ try {
++ // Try again with jline disabled for Windows users without C++ 2008 Redistributable
++ System.setProperty("jline.terminal", "jline.UnsupportedTerminal");
++ System.setProperty("user.language", "en");
++ Main.useJline = false;
++ reader = new ConsoleReader(System.in, System.out);
++ reader.setExpandEvents(false);
++ } catch (IOException ex) {
++ LOGGER.warn((String) null, ex);
++ }
++ }
++ Runtime.getRuntime().addShutdownHook(new org.bukkit.craftbukkit.util.ServerShutdownThread(this));
++ // CraftBukkit end
+ }
+
+ private void readScoreboard(DimensionDataStorage dimensiondatastorage) {
+@@ -327,7 +388,7 @@
+
+ protected abstract boolean initServer() throws IOException;
+
+- protected void loadLevel() {
++ protected void loadLevel(String s) { // CraftBukkit
+ if (!JvmProfiler.INSTANCE.isRunning()) {
+ ;
+ }
+@@ -335,8 +396,7 @@
+ boolean flag = false;
+ ProfiledDuration profiledduration = JvmProfiler.INSTANCE.onWorldLoadedStarted();
+
+- this.worldData.setModdedInfo(this.getServerModName(), this.getModdedStatus().shouldReportAsModified());
+- ChunkProgressListener chunkprogresslistener = this.progressListenerFactory.create(11);
++ loadWorld0(s); // CraftBukkit
+
+ this.createLevels(chunkprogresslistener);
+ this.forceDifficulty();
+@@ -357,16 +414,9 @@
+
+ protected void forceDifficulty() {}
+
+- protected void createLevels(ChunkProgressListener chunkprogresslistener) {
+- ServerLevelData serverleveldata = this.worldData.overworldData();
+- boolean flag = this.worldData.isDebugWorld();
+- Registry<LevelStem> registry = this.registries.compositeAccess().registryOrThrow(Registries.LEVEL_STEM);
+- WorldOptions worldoptions = this.worldData.worldGenOptions();
+- long i = worldoptions.seed();
+- long j = BiomeManager.obfuscateSeed(i);
+- List<CustomSpawner> list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(serverleveldata));
+- LevelStem levelstem = (LevelStem) registry.get(LevelStem.OVERWORLD);
+- ServerLevel serverlevel = new ServerLevel(this, this.executor, this.storageSource, serverleveldata, Level.OVERWORLD, levelstem, chunkprogresslistener, flag, j, list, true, (RandomSequences) null);
++ // CraftBukkit start
++ private void loadWorld0(String s) {
++ LevelStorageSource.LevelStorageAccess worldSession = this.storageSource;
+
+ this.levels.put(Level.OVERWORLD, serverlevel);
+ DimensionDataStorage dimensiondatastorage = serverlevel.getDataStorage();
+@@ -375,7 +425,207 @@
+ this.commandStorage = new CommandStorage(dimensiondatastorage);
+ WorldBorder worldborder = serverlevel.getWorldBorder();
+
+- if (!serverleveldata.isInitialized()) {
++ if (dimensionKey == LevelStem.NETHER) {
++ if (isNetherEnabled()) {
++ dimension = -1;
++ } else {
++ continue;
++ }
++ } else if (dimensionKey == LevelStem.END) {
++ if (server.getAllowEnd()) {
++ dimension = 1;
++ } else {
++ continue;
++ }
++ } else if (dimensionKey != LevelStem.OVERWORLD) {
++ dimension = -999;
++ }
++
++ String worldType = (dimension == -999) ? dimensionKey.location().getNamespace() + "_" + dimensionKey.location().getPath() : org.bukkit.World.Environment.getEnvironment(dimension).toString().toLowerCase();
++ String name = (dimensionKey == LevelStem.OVERWORLD) ? s : s + "_" + worldType;
++ if (dimension != 0) {
++ File newWorld = LevelStorageSource.getStorageFolder(new File(name).toPath(), dimensionKey).toFile();
++ File oldWorld = LevelStorageSource.getStorageFolder(new File(s).toPath(), dimensionKey).toFile();
++ File oldLevelDat = new File(new File(s), "level.dat"); // The data folders exist on first run as they are created in the PersistentCollection constructor above, but the level.dat won't
++
++ if (!newWorld.isDirectory() && oldWorld.isDirectory() && oldLevelDat.isFile()) {
++ MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder required ----");
++ MinecraftServer.LOGGER.info("Unfortunately due to the way that Minecraft implemented multiworld support in 1.6, Bukkit requires that you move your " + worldType + " folder to a new location in order to operate correctly.");
++ MinecraftServer.LOGGER.info("We will move this folder for you, but it will mean that you need to move it back should you wish to stop using Bukkit in the future.");
++ MinecraftServer.LOGGER.info("Attempting to move " + oldWorld + " to " + newWorld + "...");
++
++ if (newWorld.exists()) {
++ MinecraftServer.LOGGER.warn("A file or folder already exists at " + newWorld + "!");
++ MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder failed ----");
++ } else if (newWorld.getParentFile().mkdirs()) {
++ if (oldWorld.renameTo(newWorld)) {
++ MinecraftServer.LOGGER.info("Success! To restore " + worldType + " in the future, simply move " + newWorld + " to " + oldWorld);
++ // Migrate world data too.
++ try {
++ com.google.common.io.Files.copy(oldLevelDat, new File(new File(name), "level.dat"));
++ org.apache.commons.io.FileUtils.copyDirectory(new File(new File(s), "data"), new File(new File(name), "data"));
++ } catch (IOException exception) {
++ MinecraftServer.LOGGER.warn("Unable to migrate world data.");
++ }
++ MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder complete ----");
++ } else {
++ MinecraftServer.LOGGER.warn("Could not move folder " + oldWorld + " to " + newWorld + "!");
++ MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder failed ----");
++ }
++ } else {
++ MinecraftServer.LOGGER.warn("Could not create path for " + newWorld + "!");
++ MinecraftServer.LOGGER.info("---- Migration of old " + worldType + " folder failed ----");
++ }
++ }
++
++ try {
++ worldSession = LevelStorageSource.createDefault(server.getWorldContainer().toPath()).validateAndCreateAccess(name, dimensionKey);
++ } catch (IOException | ContentValidationException ex) {
++ throw new RuntimeException(ex);
++ }
++ }
++
++ Dynamic<?> dynamic;
++ if (worldSession.hasWorldData()) {
++ LevelSummary worldinfo;
++
++ try {
++ dynamic = worldSession.getDataTag();
++ worldinfo = worldSession.getSummary(dynamic);
++ } catch (NbtException | ReportedNbtException | IOException ioexception) {
++ LevelStorageSource.LevelDirectory convertable_b = worldSession.getLevelDirectory();
++
++ MinecraftServer.LOGGER.warn("Failed to load world data from {}", convertable_b.dataFile(), ioexception);
++ MinecraftServer.LOGGER.info("Attempting to use fallback");
++
++ try {
++ dynamic = worldSession.getDataTagFallback();
++ worldinfo = worldSession.getSummary(dynamic);
++ } catch (NbtException | ReportedNbtException | IOException ioexception1) {
++ MinecraftServer.LOGGER.error("Failed to load world data from {}", convertable_b.oldDataFile(), ioexception1);
++ MinecraftServer.LOGGER.error("Failed to load world data from {} and {}. World files may be corrupted. Shutting down.", convertable_b.dataFile(), convertable_b.oldDataFile());
++ return;
++ }
++
++ worldSession.restoreLevelDataFromOld();
++ }
++
++ if (worldinfo.requiresManualConversion()) {
++ MinecraftServer.LOGGER.info("This world must be opened in an older version (like 1.6.4) to be safely converted");
++ return;
++ }
++
++ if (!worldinfo.isCompatible()) {
++ MinecraftServer.LOGGER.info("This world was created by an incompatible version.");
++ return;
++ }
++ } else {
++ dynamic = null;
++ }
++
++ org.bukkit.generator.ChunkGenerator gen = this.server.getGenerator(name);
++ org.bukkit.generator.BiomeProvider biomeProvider = this.server.getBiomeProvider(name);
++
++ PrimaryLevelData worlddata;
++ WorldLoader.a worldloader_a = this.worldLoader;
++ Registry<LevelStem> iregistry = worldloader_a.datapackDimensions().registryOrThrow(Registries.LEVEL_STEM);
++ if (dynamic != null) {
++ LevelDataAndDimensions leveldataanddimensions = LevelStorageSource.getLevelDataAndDimensions(dynamic, worldloader_a.dataConfiguration(), iregistry, worldloader_a.datapackWorldgen());
++
++ worlddata = (PrimaryLevelData) leveldataanddimensions.worldData();
++ } else {
++ LevelSettings worldsettings;
++ WorldOptions worldoptions;
++ WorldDimensions worlddimensions;
++
++ if (this.isDemo()) {
++ worldsettings = MinecraftServer.DEMO_SETTINGS;
++ worldoptions = WorldOptions.DEMO_OPTIONS;
++ worlddimensions = WorldPresets.createNormalWorldDimensions(worldloader_a.datapackWorldgen());
++ } else {
++ DedicatedServerProperties dedicatedserverproperties = ((DedicatedServer) this).getProperties();
++
++ worldsettings = new LevelSettings(dedicatedserverproperties.levelName, dedicatedserverproperties.gamemode, dedicatedserverproperties.hardcore, dedicatedserverproperties.difficulty, false, new GameRules(), worldloader_a.dataConfiguration());
++ worldoptions = options.has("bonusChest") ? dedicatedserverproperties.worldOptions.withBonusChest(true) : dedicatedserverproperties.worldOptions;
++ worlddimensions = dedicatedserverproperties.createDimensions(worldloader_a.datapackWorldgen());
++ }
++
++ WorldDimensions.b worlddimensions_b = worlddimensions.bake(iregistry);
++ Lifecycle lifecycle = worlddimensions_b.lifecycle().add(worldloader_a.datapackWorldgen().allRegistriesLifecycle());
++
++ worlddata = new PrimaryLevelData(worldsettings, worldoptions, worlddimensions_b.specialWorldProperty(), lifecycle);
++ }
++ worlddata.checkName(name); // CraftBukkit - Migration did not rewrite the level.dat; This forces 1.8 to take the last loaded world as respawn (in this case the end)
++ if (options.has("forceUpgrade")) {
++ net.minecraft.server.Main.forceUpgrade(worldSession, DataFixers.getDataFixer(), options.has("eraseCache"), () -> {
++ return true;
++ }, dimensions);
++ }
++
++ PrimaryLevelData iworlddataserver = worlddata;
++ boolean flag = worlddata.isDebugWorld();
++ WorldOptions worldoptions = worlddata.worldGenOptions();
++ long i = worldoptions.seed();
++ long j = BiomeManager.obfuscateSeed(i);
++ List<CustomSpawner> list = ImmutableList.of(new MobSpawnerPhantom(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(iworlddataserver));
++ LevelStem worlddimension = (LevelStem) dimensions.get(dimensionKey);
++
++ org.bukkit.generator.WorldInfo worldInfo = new org.bukkit.craftbukkit.generator.CraftWorldInfo(iworlddataserver, worldSession, org.bukkit.World.Environment.getEnvironment(dimension), worlddimension.type().value());
++ if (biomeProvider == null && gen != null) {
++ biomeProvider = gen.getDefaultBiomeProvider(worldInfo);
++ }
++
++ ResourceKey<Level> worldKey = ResourceKey.create(Registries.DIMENSION, dimensionKey.location());
++
++ if (dimensionKey == LevelStem.OVERWORLD) {
++ this.worldData = worlddata;
++ this.worldData.setGameType(((DedicatedServer) this).getProperties().gamemode); // From DedicatedServer.init
++
++ ChunkProgressListener worldloadlistener = this.progressListenerFactory.create(11);
++
++ world = new ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, list, true, (RandomSequences) null, org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider);
++ DimensionDataStorage worldpersistentdata = world.getDataStorage();
++ this.readScoreboard(worldpersistentdata);
++ this.server.scoreboardManager = new org.bukkit.craftbukkit.scoreboard.CraftScoreboardManager(this, world.getScoreboard());
++ this.commandStorage = new CommandStorage(worldpersistentdata);
++ } else {
++ ChunkProgressListener worldloadlistener = this.progressListenerFactory.create(11);
++ world = new ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, ImmutableList.of(), true, this.overworld().getRandomSequences(), org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider);
++ }
++
++ worlddata.setModdedInfo(this.getServerModName(), this.getModdedStatus().shouldReportAsModified());
++ this.initWorld(world, worlddata, worldData, worldoptions);
++
++ this.addLevel(world);
++ this.getPlayerList().addWorldborderListener(world);
++
++ if (worlddata.getCustomBossEvents() != null) {
++ this.getCustomBossEvents().load(worlddata.getCustomBossEvents());
++ }
++ }
++ this.forceDifficulty();
++ for (ServerLevel worldserver : this.getAllLevels()) {
++ this.prepareLevels(worldserver.getChunkSource().chunkMap.progressListener, worldserver);
++ worldserver.entityManager.tick(); // SPIGOT-6526: Load pending entities so they are available to the API
++ this.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldLoadEvent(worldserver.getWorld()));
++ }
++
++ this.server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.POSTWORLD);
++ this.server.getPluginManager().callEvent(new ServerLoadEvent(ServerLoadEvent.LoadType.STARTUP));
++ this.connection.acceptConnections();
++ }
++
++ public void initWorld(ServerLevel worldserver, ServerLevelData iworlddataserver, WorldData saveData, WorldOptions worldoptions) {
++ boolean flag = saveData.isDebugWorld();
++ // CraftBukkit start
++ if (worldserver.generator != null) {
++ worldserver.getWorld().getPopulators().addAll(worldserver.generator.getDefaultPopulators(worldserver.getWorld()));
++ }
++ WorldBorder worldborder = worldserver.getWorldBorder();
++ worldborder.applySettings(iworlddataserver.getWorldBorder()); // CraftBukkit - move up so that WorldBorder is set during WorldInitEvent
++ this.server.getPluginManager().callEvent(new org.bukkit.event.world.WorldInitEvent(worldserver.getWorld())); // CraftBukkit - SPIGOT-5569: Call WorldInitEvent before any chunks are generated
++
++ if (!iworlddataserver.isInitialized()) {
+ try {
+ setInitialSpawn(serverlevel, serverleveldata, worldoptions.generateBonusChest(), flag);
+ serverleveldata.setInitialized(true);
+@@ -421,17 +648,30 @@
+
+ worldborder.applySettings(serverleveldata.getWorldBorder());
+ }
++ // CraftBukkit end
+
+ private static void setInitialSpawn(ServerLevel serverlevel, ServerLevelData serverleveldata, boolean flag, boolean flag1) {
+ if (flag1) {
+ serverleveldata.setSpawn(BlockPos.ZERO.above(80), 0.0F);
+ } else {
+- ServerChunkCache serverchunkcache = serverlevel.getChunkSource();
+- ChunkPos chunkpos = new ChunkPos(serverchunkcache.randomState().sampler().findSpawnPosition());
+- int i = serverchunkcache.getGenerator().getSpawnHeight(serverlevel);
++ ServerChunkCache chunkproviderserver = level.getChunkSource();
++ ChunkPos chunkcoordintpair = new ChunkPos(chunkproviderserver.randomState().sampler().findSpawnPosition());
++ // CraftBukkit start
++ if (level.generator != null) {
++ Random rand = new Random(level.getSeed());
++ org.bukkit.Location spawn = level.generator.getFixedSpawnLocation(level.getWorld(), rand);
+
+- if (i < serverlevel.getMinBuildHeight()) {
+- BlockPos blockpos = chunkpos.getWorldPosition();
++ if (spawn != null) {
++ if (spawn.getWorld() != level.getWorld()) {
++ throw new IllegalStateException("Cannot set spawn point for " + levelData.getLevelName() + " to be in another world (" + spawn.getWorld().getName() + ")");
++ } else {
++ levelData.setSpawn(new BlockPos(spawn.getBlockX(), spawn.getBlockY(), spawn.getBlockZ()), spawn.getYaw());
++ return;
++ }
++ }
++ }
++ // CraftBukkit end
++ int i = chunkproviderserver.getGenerator().getSpawnHeight(level);
+
+ i = serverlevel.getHeight(Heightmap.Types.WORLD_SURFACE, blockpos.getX() + 8, blockpos.getZ() + 8);
+ }
+@@ -487,8 +730,11 @@
+ serverleveldata.setGameType(GameType.SPECTATOR);
+ }
+
+- private void prepareLevels(ChunkProgressListener chunkprogresslistener) {
+- ServerLevel serverlevel = this.overworld();
++ // CraftBukkit start
++ public void prepareLevels(ChunkProgressListener worldloadlistener, ServerLevel worldserver) {
++ // WorldServer worldserver = this.overworld();
++ this.forceTicks = true;
++ // CraftBukkit end
+
+ MinecraftServer.LOGGER.info("Preparing start region for dimension {}", serverlevel.dimension().location());
+ BlockPos blockpos = serverlevel.getSharedSpawnPos();
+@@ -497,7 +743,9 @@
+ ServerChunkCache serverchunkcache = serverlevel.getChunkSource();
+
+ this.nextTickTimeNanos = Util.getNanos();
+- serverchunkcache.addRegionTicket(TicketType.START, new ChunkPos(blockpos), 11, Unit.INSTANCE);
++ // CraftBukkit start
++ if (worldserver.getWorld().getKeepSpawnInMemory()) {
++ chunkproviderserver.addRegionTicket(TicketType.START, new ChunkPos(blockposition), 11, Unit.INSTANCE);
+
+ while (serverchunkcache.getTickingGenerated() != 441) {
+ this.nextTickTimeNanos = Util.getNanos() + MinecraftServer.PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
+@@ -508,9 +757,10 @@
+ this.waitUntilNextTick();
+ Iterator iterator = this.levels.values().iterator();
+
+- while (iterator.hasNext()) {
+- ServerLevel serverlevel1 = (ServerLevel) iterator.next();
+- ForcedChunksSavedData forcedchunkssaveddata = (ForcedChunksSavedData) serverlevel1.getDataStorage().get(ForcedChunksSavedData.factory(), "chunks");
++ if (true) {
++ ServerLevel worldserver1 = worldserver;
++ // CraftBukkit end
++ ForcedChunksSavedData forcedchunk = (ForcedChunksSavedData) worldserver1.getDataStorage().get(ForcedChunksSavedData.factory(), "chunks");
+
+ if (forcedchunkssaveddata != null) {
+ LongIterator longiterator = forcedchunkssaveddata.getChunks().iterator();
+@@ -524,10 +774,17 @@
+ }
+ }
+
+- this.nextTickTimeNanos = Util.getNanos() + MinecraftServer.PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
+- this.waitUntilNextTick();
+- chunkprogresslistener.stop();
+- this.updateMobSpawningFlags();
++ // CraftBukkit start
++ // this.nextTickTimeNanos = SystemUtils.getNanos() + MinecraftServer.PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
++ this.executeModerately();
++ // CraftBukkit end
++ worldloadlistener.stop();
++ // CraftBukkit start
++ // this.updateMobSpawningFlags();
++ worldserver.setSpawnSettings(this.isSpawningMonsters(), this.isSpawningAnimals());
++
++ this.forceTicks = false;
++ // CraftBukkit end
+ }
+
+ public GameType getDefaultGameType() {
+@@ -557,13 +814,17 @@
+ serverlevel.save((ProgressListener) null, flag1, serverlevel.noSave && !flag2);
+ }
+
+- ServerLevel serverlevel1 = this.overworld();
+- ServerLevelData serverleveldata = this.worldData.overworldData();
++ // CraftBukkit start - moved to WorldServer.save
++ /*
++ WorldServer worldserver1 = this.overworld();
++ IWorldDataServer iworlddataserver = this.worldData.overworldData();
+
+ serverleveldata.setWorldBorder(serverlevel1.getWorldBorder().createSettings());
+ this.worldData.setCustomBossEvents(this.getCustomBossEvents().save());
+ this.storageSource.saveDataTag(this.registryAccess(), this.worldData, this.getPlayerList().getSingleplayerData());
+- if (flag1) {
++ */
++ // CraftBukkit end
++ if (flush) {
+ Iterator iterator1 = this.getAllLevels().iterator();
+
+ while (iterator1.hasNext()) {
+@@ -598,18 +858,40 @@
+ this.stopServer();
+ }
+
++ // CraftBukkit start
++ private boolean hasStopped = false;
++ private final Object stopLock = new Object();
++ public final boolean hasStopped() {
++ synchronized (stopLock) {
++ return hasStopped;
++ }
++ }
++ // CraftBukkit end
++
+ public void stopServer() {
++ // CraftBukkit start - prevent double stopping on multiple threads
++ synchronized(stopLock) {
++ if (hasStopped) return;
++ hasStopped = true;
++ }
++ // CraftBukkit end
+ if (this.metricsRecorder.isRecording()) {
+ this.cancelRecordingMetrics();
+ }
+
+ MinecraftServer.LOGGER.info("Stopping server");
++ // CraftBukkit start
++ if (this.server != null) {
++ this.server.disablePlugins();
++ }
++ // CraftBukkit end
+ this.getConnection().stop();
+ this.isSaving = true;
+ if (this.playerList != null) {
+ MinecraftServer.LOGGER.info("Saving players");
+ this.playerList.saveAll();
+ this.playerList.removeAll();
++ try { Thread.sleep(100); } catch (InterruptedException ex) {} // CraftBukkit - SPIGOT-625 - give server at least a chance to send packets
+ }
+
+ MinecraftServer.LOGGER.info("Saving worlds");
+@@ -714,6 +996,7 @@
+ if (j > MinecraftServer.OVERLOADED_THRESHOLD_NANOS + 20L * i && this.nextTickTimeNanos - this.lastOverloadWarningNanos >= MinecraftServer.OVERLOADED_WARNING_INTERVAL_NANOS + 100L * i) {
+ long k = j / i;
+
++ if (server.getWarnOnOverload()) // CraftBukkit
+ MinecraftServer.LOGGER.warn("Can't keep up! Is the server overloaded? Running {}ms or {} ticks behind", j / TimeUtil.NANOSECONDS_PER_MILLISECOND, k);
+ this.nextTickTimeNanos += k * i;
+ this.lastOverloadWarningNanos = this.nextTickTimeNanos;
+@@ -727,6 +1010,7 @@
+ this.debugCommandProfiler = new MinecraftServer.TimeProfiler(Util.getNanos(), this.tickCount);
+ }
+
++ MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit
+ this.nextTickTimeNanos += i;
+ this.startMetricsRecordingTick();
+ this.profiler.push("tick");
+@@ -771,6 +1055,12 @@
+ this.services.profileCache().clearExecutor();
+ }
+
++ // CraftBukkit start - Restore terminal to original settings
++ try {
++ reader.getTerminal().restore();
++ } catch (Exception ignored) {
++ }
++ // CraftBukkit end
+ this.onServerExit();
+ }
+
+@@ -804,9 +1094,16 @@
+ }
+
+ private boolean haveTime() {
+- return this.runningTask() || Util.getNanos() < (this.mayHaveDelayedTasks ? this.delayedTasksMaxNextTickTimeNanos : this.nextTickTimeNanos);
++ // CraftBukkit start
++ return this.forceTicks || this.runningTask() || Util.getNanos() < (this.mayHaveDelayedTasks ? this.delayedTasksMaxNextTickTimeNanos : this.nextTickTimeNanos);
+ }
+
++ private void executeModerately() {
++ this.runAllTasks();
++ java.util.concurrent.locks.LockSupport.parkNanos("executing tasks", 1000L);
++ // CraftBukkit end
++ }
++
+ protected void waitUntilNextTick() {
+ this.runAllTasks();
+ this.managedBlock(() -> {
+@@ -914,8 +1207,10 @@
+ }
+
+ --this.ticksUntilAutosave;
+- if (this.ticksUntilAutosave <= 0) {
+- this.ticksUntilAutosave = this.computeNextAutosaveInterval();
++ // CraftBukkit start
++ if (this.autosavePeriod > 0 && this.ticksUntilAutosave <= 0) {
++ this.ticksUntilAutosave = this.autosavePeriod;
++ // CraftBukkit end
+ MinecraftServer.LOGGER.debug("Autosave started");
+ this.profiler.push("save");
+ this.saveEverything(true, false, false);
+@@ -996,11 +1291,26 @@
+ this.getPlayerList().getPlayers().forEach((serverplayer) -> {
+ serverplayer.connection.suspendFlushing();
+ });
++ this.server.getScheduler().mainThreadHeartbeat(this.tickCount); // CraftBukkit
+ this.profiler.push("commandFunctions");
+ this.getFunctions().tick();
+ this.profiler.popPush("levels");
+ Iterator iterator = this.getAllLevels().iterator();
+
++ // CraftBukkit start
++ // Run tasks that are waiting on processing
++ while (!processQueue.isEmpty()) {
++ processQueue.remove().run();
++ }
++
++ // Send time updates to everyone, it will get the right time from the world the player is in.
++ if (this.tickCount % 20 == 0) {
++ for (int i = 0; i < this.getPlayerList().players.size(); ++i) {
++ ServerPlayer entityplayer = (ServerPlayer) this.getPlayerList().players.get(i);
++ entityplayer.connection.send(new ClientboundSetTimePacket(entityplayer.level().getGameTime(), entityplayer.getPlayerTime(), entityplayer.level().getGameRules().getBoolean(GameRules.RULE_DAYLIGHT))); // Add support for per player time
++ }
++ }
++
+ while (iterator.hasNext()) {
+ ServerLevel serverlevel = (ServerLevel) iterator.next();
+
+@@ -1012,6 +1323,7 @@
+ this.synchronizeTime(serverlevel);
+ this.profiler.pop();
+ }
++ // CraftBukkit end */
+
+ this.profiler.push("tick");
+
+@@ -1101,6 +1413,22 @@
+ return (ServerLevel) this.levels.get(resourcekey);
+ }
+
++ // CraftBukkit start
++ public void addLevel(ServerLevel level) {
++ Map<ResourceKey<Level>, ServerLevel> oldLevels = this.levels;
++ Map<ResourceKey<Level>, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels);
++ newLevels.put(level.dimension(), level);
++ this.levels = Collections.unmodifiableMap(newLevels);
++ }
++
++ public void removeLevel(ServerLevel level) {
++ Map<ResourceKey<Level>, ServerLevel> oldLevels = this.levels;
++ Map<ResourceKey<Level>, ServerLevel> newLevels = Maps.newLinkedHashMap(oldLevels);
++ newLevels.remove(level.dimension());
++ this.levels = Collections.unmodifiableMap(newLevels);
++ }
++ // CraftBukkit end
++
+ public Set<ResourceKey<Level>> levelKeys() {
+ return this.levels.keySet();
+ }
+@@ -1133,7 +1458,7 @@
+
+ @DontObfuscate
+ public String getServerModName() {
+- return "vanilla";
++ return server.getName(); // CraftBukkit - cb > vanilla!
+ }
+
+ public SystemReport fillSystemReport(SystemReport systemreport) {
+@@ -1920,6 +2237,22 @@
+
+ }
+
++ // CraftBukkit start
++ @Override
++ public boolean isSameThread() {
++ return super.isSameThread() || this.isStopped(); // CraftBukkit - MC-142590
++ }
++
++ public boolean isDebugging() {
++ return false;
++ }
++
++ @Deprecated
++ public static MinecraftServer getServer() {
++ return (Bukkit.getServer() instanceof CraftServer) ? ((CraftServer) Bukkit.getServer()).getServer() : null;
++ }
++ // CraftBukkit end
++
+ private void startMetricsRecordingTick() {
+ if (this.willStartRecordingMetrics) {
+ this.metricsRecorder = ActiveMetricsRecorder.createStarted(new ServerMetricsSamplersProvider(Util.timeSource, this.isDedicatedServer()), Util.timeSource, Util.ioPool(), new MetricsPersister("server"), this.onMetricsRecordingStopped, (path) -> {
+@@ -2046,6 +2379,11 @@
+
+ }
+
++ // CraftBukkit start
++ public final java.util.concurrent.ExecutorService chatExecutor = java.util.concurrent.Executors.newCachedThreadPool(
++ new com.google.common.util.concurrent.ThreadFactoryBuilder().setDaemon(true).setNameFormat("Async Chat Thread - #%d").build());
++ // CraftBukkit end
++
+ public ChatDecorator getChatDecorator() {
+ return ChatDecorator.PLAIN;
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/PlayerAdvancements.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/PlayerAdvancements.java.patch
new file mode 100644
index 0000000000..51cacd75d0
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/PlayerAdvancements.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/server/PlayerAdvancements.java
++++ b/net/minecraft/server/PlayerAdvancements.java
+@@ -196,7 +196,8 @@
+ AdvancementHolder advancementholder = serveradvancementmanager.get(resourcelocation);
+
+ if (advancementholder == null) {
+- PlayerAdvancements.LOGGER.warn("Ignored advancement '{}' in progress file {} - it doesn't exist anymore?", resourcelocation, this.playerSavePath);
++ if (!minecraftkey.getNamespace().equals("minecraft")) return; // CraftBukkit
++ PlayerAdvancements.LOGGER.warn("Ignored advancement '{}' in progress file {} - it doesn't exist anymore?", minecraftkey, this.playerSavePath);
+ } else {
+ this.startProgress(advancementholder, advancementprogress);
+ this.progressChanged.add(advancementholder);
+@@ -227,6 +228,7 @@
+ this.progressChanged.add(advancementholder);
+ flag = true;
+ if (!flag1 && advancementprogress.isDone()) {
++ this.player.level().getCraftServer().getPluginManager().callEvent(new org.bukkit.event.player.PlayerAdvancementDoneEvent(this.player.getBukkitEntity(), advancementholder.toBukkit())); // CraftBukkit
+ advancementholder.value().rewards().grant(this.player);
+ advancementholder.value().display().ifPresent((displayinfo) -> {
+ if (displayinfo.shouldAnnounceChat() && this.player.level().getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/ServerAdvancementManager.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/ServerAdvancementManager.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/ServerAdvancementManager.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/ServerFunctionManager.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/ServerFunctionManager.java.patch
new file mode 100644
index 0000000000..f9706cc8ca
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/ServerFunctionManager.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/server/ServerFunctionManager.java
++++ b/net/minecraft/server/ServerFunctionManager.java
+@@ -37,7 +36,7 @@
+ }
+
+ public CommandDispatcher<CommandSourceStack> getDispatcher() {
+- return this.server.getCommands().getDispatcher();
++ return this.server.vanillaCommandDispatcher.getDispatcher(); // CraftBukkit
+ }
+
+ public void tick() {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/ServerScoreboard.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/ServerScoreboard.java.patch
new file mode 100644
index 0000000000..73ccec84ac
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/ServerScoreboard.java.patch
@@ -0,0 +1,176 @@
+--- a/net/minecraft/server/ServerScoreboard.java
++++ b/net/minecraft/server/ServerScoreboard.java
+@@ -37,11 +37,10 @@
+ }
+
+ @Override
+- @Override
+- protected void onScoreChanged(ScoreHolder scoreholder, Objective objective, Score score) {
+- super.onScoreChanged(scoreholder, objective, score);
+- if (this.trackedObjectives.contains(objective)) {
+- this.server.getPlayerList().broadcastAll(new ClientboundSetScorePacket(scoreholder.getScoreboardName(), objective.getName(), score.value(), score.display(), score.numberFormat()));
++ protected void onScoreChanged(ScoreHolder scoreholder, Objective scoreboardobjective, Score scoreboardscore) {
++ super.onScoreChanged(scoreholder, scoreboardobjective, scoreboardscore);
++ if (this.trackedObjectives.contains(scoreboardobjective)) {
++ this.broadcastAll(new ClientboundSetScorePacket(scoreholder.getScoreboardName(), scoreboardobjective.getName(), scoreboardscore.value(), scoreboardscore.display(), scoreboardscore.numberFormat())); // CraftBukkit
+ }
+
+ this.setDirty();
+@@ -58,16 +55,15 @@
+ @Override
+ public void onPlayerRemoved(ScoreHolder scoreholder) {
+ super.onPlayerRemoved(scoreholder);
+- this.server.getPlayerList().broadcastAll(new ClientboundResetScorePacket(scoreholder.getScoreboardName(), (String) null));
++ this.broadcastAll(new ClientboundResetScorePacket(scoreholder.getScoreboardName(), (String) null)); // CraftBukkit
+ this.setDirty();
+ }
+
+ @Override
+- @Override
+- public void onPlayerScoreRemoved(ScoreHolder scoreholder, Objective objective) {
+- super.onPlayerScoreRemoved(scoreholder, objective);
+- if (this.trackedObjectives.contains(objective)) {
+- this.server.getPlayerList().broadcastAll(new ClientboundResetScorePacket(scoreholder.getScoreboardName(), objective.getName()));
++ public void onPlayerScoreRemoved(ScoreHolder scoreholder, Objective scoreboardobjective) {
++ super.onPlayerScoreRemoved(scoreholder, scoreboardobjective);
++ if (this.trackedObjectives.contains(scoreboardobjective)) {
++ this.broadcastAll(new ClientboundResetScorePacket(scoreholder.getScoreboardName(), scoreboardobjective.getName())); // CraftBukkit
+ }
+
+ this.setDirty();
+@@ -78,18 +73,18 @@
+ public void setDisplayObjective(DisplaySlot displayslot, @Nullable Objective objective) {
+ Objective objective1 = this.getDisplayObjective(displayslot);
+
+- super.setDisplayObjective(displayslot, objective);
+- if (objective1 != objective && objective1 != null) {
+- if (this.getObjectiveDisplaySlotCount(objective1) > 0) {
+- this.server.getPlayerList().broadcastAll(new ClientboundSetDisplayObjectivePacket(displayslot, objective));
++ super.setDisplayObjective(displayslot, scoreboardobjective);
++ if (scoreboardobjective1 != scoreboardobjective && scoreboardobjective1 != null) {
++ if (this.getObjectiveDisplaySlotCount(scoreboardobjective1) > 0) {
++ this.broadcastAll(new ClientboundSetDisplayObjectivePacket(displayslot, scoreboardobjective)); // CraftBukkit
+ } else {
+ this.stopTrackingObjective(objective1);
+ }
+ }
+
+- if (objective != null) {
+- if (this.trackedObjectives.contains(objective)) {
+- this.server.getPlayerList().broadcastAll(new ClientboundSetDisplayObjectivePacket(displayslot, objective));
++ if (scoreboardobjective != null) {
++ if (this.trackedObjectives.contains(scoreboardobjective)) {
++ this.broadcastAll(new ClientboundSetDisplayObjectivePacket(displayslot, scoreboardobjective)); // CraftBukkit
+ } else {
+ this.startTrackingObjective(objective);
+ }
+@@ -99,10 +94,9 @@
+ }
+
+ @Override
+- @Override
+- public boolean addPlayerToTeam(String s, PlayerTeam playerteam) {
+- if (super.addPlayerToTeam(s, playerteam)) {
+- this.server.getPlayerList().broadcastAll(ClientboundSetPlayerTeamPacket.createPlayerPacket(playerteam, s, ClientboundSetPlayerTeamPacket.Action.ADD));
++ public boolean addPlayerToTeam(String playerName, PlayerTeam team) {
++ if (super.addPlayerToTeam(playerName, team)) {
++ this.broadcastAll(ClientboundSetPlayerTeamPacket.createPlayerPacket(team, playerName, ClientboundSetPlayerTeamPacket.a.ADD)); // CraftBukkit
+ this.setDirty();
+ return true;
+ } else {
+@@ -111,10 +105,9 @@
+ }
+
+ @Override
+- @Override
+- public void removePlayerFromTeam(String s, PlayerTeam playerteam) {
+- super.removePlayerFromTeam(s, playerteam);
+- this.server.getPlayerList().broadcastAll(ClientboundSetPlayerTeamPacket.createPlayerPacket(playerteam, s, ClientboundSetPlayerTeamPacket.Action.REMOVE));
++ public void removePlayerFromTeam(String username, PlayerTeam playerTeam) {
++ super.removePlayerFromTeam(username, playerTeam);
++ this.broadcastAll(ClientboundSetPlayerTeamPacket.createPlayerPacket(playerTeam, username, ClientboundSetPlayerTeamPacket.a.REMOVE)); // CraftBukkit
+ this.setDirty();
+ }
+
+@@ -130,7 +121,7 @@
+ public void onObjectiveChanged(Objective objective) {
+ super.onObjectiveChanged(objective);
+ if (this.trackedObjectives.contains(objective)) {
+- this.server.getPlayerList().broadcastAll(new ClientboundSetObjectivePacket(objective, 2));
++ this.broadcastAll(new ClientboundSetObjectivePacket(objective, 2)); // CraftBukkit
+ }
+
+ this.setDirty();
+@@ -148,26 +138,23 @@
+ }
+
+ @Override
+- @Override
+- public void onTeamAdded(PlayerTeam playerteam) {
+- super.onTeamAdded(playerteam);
+- this.server.getPlayerList().broadcastAll(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(playerteam, true));
++ public void onTeamAdded(PlayerTeam playerTeam) {
++ super.onTeamAdded(playerTeam);
++ this.broadcastAll(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(playerTeam, true)); // CraftBukkit
+ this.setDirty();
+ }
+
+ @Override
+- @Override
+- public void onTeamChanged(PlayerTeam playerteam) {
+- super.onTeamChanged(playerteam);
+- this.server.getPlayerList().broadcastAll(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(playerteam, false));
++ public void onTeamChanged(PlayerTeam playerTeam) {
++ super.onTeamChanged(playerTeam);
++ this.broadcastAll(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(playerTeam, false)); // CraftBukkit
+ this.setDirty();
+ }
+
+ @Override
+- @Override
+- public void onTeamRemoved(PlayerTeam playerteam) {
+- super.onTeamRemoved(playerteam);
+- this.server.getPlayerList().broadcastAll(ClientboundSetPlayerTeamPacket.createRemovePacket(playerteam));
++ public void onTeamRemoved(PlayerTeam playerTeam) {
++ super.onTeamRemoved(playerTeam);
++ this.broadcastAll(ClientboundSetPlayerTeamPacket.createRemovePacket(playerTeam)); // CraftBukkit
+ this.setDirty();
+ }
+
+@@ -217,7 +204,8 @@
+ Iterator iterator = this.server.getPlayerList().getPlayers().iterator();
+
+ while (iterator.hasNext()) {
+- ServerPlayer serverplayer = (ServerPlayer) iterator.next();
++ ServerPlayer entityplayer = (ServerPlayer) iterator.next();
++ if (entityplayer.getBukkitEntity().getScoreboard().getHandle() != this) continue; // CraftBukkit - Only players on this board
+ Iterator iterator1 = list.iterator();
+
+ while (iterator1.hasNext()) {
+@@ -253,7 +241,8 @@
+ Iterator iterator = this.server.getPlayerList().getPlayers().iterator();
+
+ while (iterator.hasNext()) {
+- ServerPlayer serverplayer = (ServerPlayer) iterator.next();
++ ServerPlayer entityplayer = (ServerPlayer) iterator.next();
++ if (entityplayer.getBukkitEntity().getScoreboard().getHandle() != this) continue; // CraftBukkit - Only players on this board
+ Iterator iterator1 = list.iterator();
+
+ while (iterator1.hasNext()) {
+@@ -298,7 +287,15 @@
+ return this.createData().load(compoundtag);
+ }
+
+- public static enum Method {
++ // CraftBukkit start - Send to players
++ private void broadcastAll(Packet packet) {
++ for (ServerPlayer entityplayer : (List<ServerPlayer>) this.server.getPlayerList().players) {
++ if (entityplayer.getBukkitEntity().getScoreboard().getHandle() == this) {
++ entityplayer.connection.send(packet);
++ }
++ }
++ }
++ // CraftBukkit end
+
+ CHANGE, REMOVE;
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/ServerTickRateManager.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/ServerTickRateManager.java.patch
new file mode 100644
index 0000000000..ba057f3e79
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/ServerTickRateManager.java.patch
@@ -0,0 +1,53 @@
+--- a/net/minecraft/server/ServerTickRateManager.java
++++ b/net/minecraft/server/ServerTickRateManager.java
+@@ -60,8 +59,14 @@
+ }
+
+ public boolean stopSprinting() {
++ // CraftBukkit start, add sendLog parameter
++ return stopSprinting(true);
++ }
++
++ public boolean stopSprinting(boolean sendLog) {
++ // CraftBukkit end
+ if (this.remainingSprintTicks > 0L) {
+- this.finishTickSprint();
++ this.finishTickSprint(sendLog); // CraftBukkit - add sendLog parameter
+ return true;
+ } else {
+ return false;
+@@ -79,7 +84,7 @@
+ return flag;
+ }
+
+- private void finishTickSprint() {
++ private void finishTickSprint(boolean sendLog) { // CraftBukkit - add sendLog parameter
+ long i = this.scheduledCurrentSprintTicks - this.remainingSprintTicks;
+ double d0 = Math.max(1.0D, (double) this.sprintTimeSpend) / (double) TimeUtil.NANOSECONDS_PER_MILLISECOND;
+ int j = (int) ((double) (TimeUtil.MILLISECONDS_PER_SECOND * i) / d0);
+@@ -87,9 +92,13 @@
+
+ this.scheduledCurrentSprintTicks = 0L;
+ this.sprintTimeSpend = 0L;
+- this.server.createCommandSourceStack().sendSuccess(() -> {
+- return Component.translatable("commands.tick.sprint.report", j, s);
+- }, true);
++ // CraftBukkit start - add sendLog parameter
++ if (sendLog) {
++ this.server.createCommandSourceStack().sendSuccess(() -> {
++ return Component.translatable("commands.tick.sprint.report", j, s);
++ }, true);
++ }
++ // CraftBukkit end
+ this.remainingSprintTicks = 0L;
+ this.setFrozen(this.previousIsFrozen);
+ this.server.onTickRateChanged();
+@@ -103,7 +112,7 @@
+ --this.remainingSprintTicks;
+ return true;
+ } else {
+- this.finishTickSprint();
++ this.finishTickSprint(true); // CraftBukkit - add sendLog parameter
+ return false;
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/bossevents/CustomBossEvent.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/bossevents/CustomBossEvent.java.patch
new file mode 100644
index 0000000000..ef8a8c4064
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/bossevents/CustomBossEvent.java.patch
@@ -0,0 +1,37 @@
+--- a/net/minecraft/server/bossevents/CustomBossEvent.java
++++ b/net/minecraft/server/bossevents/CustomBossEvent.java
+@@ -17,6 +17,10 @@
+ import net.minecraft.server.level.ServerPlayer;
+ import net.minecraft.util.Mth;
+ import net.minecraft.world.BossEvent;
++// CraftBukkit start
++import org.bukkit.boss.KeyedBossBar;
++import org.bukkit.craftbukkit.boss.CraftKeyedBossbar;
++// CraftBukkit end
+
+ public class CustomBossEvent extends ServerBossEvent {
+
+@@ -24,10 +28,20 @@
+ private final Set<UUID> players = Sets.newHashSet();
+ private int value;
+ private int max = 100;
++ // CraftBukkit start
++ private KeyedBossBar bossBar;
+
+- public CustomBossEvent(ResourceLocation resourcelocation, Component component) {
+- super(component, BossEvent.BossBarColor.WHITE, BossEvent.BossBarOverlay.PROGRESS);
+- this.id = resourcelocation;
++ public KeyedBossBar getBukkitEntity() {
++ if (bossBar == null) {
++ bossBar = new CraftKeyedBossbar(this);
++ }
++ return bossBar;
++ }
++ // CraftBukkit end
++
++ public CustomBossEvent(ResourceLocation id, Component name) {
++ super(name, BossEvent.BossBarColor.WHITE, BossEvent.BossBarOverlay.PROGRESS);
++ this.id = id;
+ this.setProgress(0.0F);
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/DifficultyCommand.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/DifficultyCommand.java.patch
new file mode 100644
index 0000000000..9781bb1783
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/DifficultyCommand.java.patch
@@ -0,0 +1,23 @@
+--- a/net/minecraft/server/commands/DifficultyCommand.java
++++ b/net/minecraft/server/commands/DifficultyCommand.java
+@@ -43,14 +42,15 @@
+ }));
+ }
+
+- public static int setDifficulty(CommandSourceStack commandsourcestack, Difficulty difficulty) throws CommandSyntaxException {
+- MinecraftServer minecraftserver = commandsourcestack.getServer();
++ public static int setDifficulty(CommandSourceStack source, Difficulty difficulty) throws CommandSyntaxException {
++ MinecraftServer minecraftserver = source.getServer();
++ net.minecraft.server.level.ServerLevel worldServer = source.getLevel(); // CraftBukkit
+
+- if (minecraftserver.getWorldData().getDifficulty() == difficulty) {
++ if (worldServer.getDifficulty() == difficulty) { // CraftBukkit
+ throw DifficultyCommand.ERROR_ALREADY_DIFFICULT.create(difficulty.getKey());
+ } else {
+- minecraftserver.setDifficulty(difficulty, true);
+- commandsourcestack.sendSuccess(() -> {
++ worldServer.serverLevelData.setDifficulty(difficulty); // CraftBukkit
++ source.sendSuccess(() -> {
+ return Component.translatable("commands.difficulty.success", difficulty.getDisplayName());
+ }, true);
+ return 0;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/EffectCommands.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/EffectCommands.java.patch
new file mode 100644
index 0000000000..98d850e86a
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/EffectCommands.java.patch
@@ -0,0 +1,29 @@
+--- a/net/minecraft/server/commands/EffectCommands.java
++++ b/net/minecraft/server/commands/EffectCommands.java
+@@ -85,7 +84,7 @@
+ if (entity instanceof LivingEntity) {
+ MobEffectInstance mobeffectinstance = new MobEffectInstance(mobeffect, k, i, false, flag);
+
+- if (((LivingEntity) entity).addEffect(mobeffectinstance, commandsourcestack.getEntity())) {
++ if (((LivingEntity) entity).addEffect(mobeffect, source.getEntity(), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND)) { // CraftBukkit
+ ++j;
+ }
+ }
+@@ -115,7 +114,7 @@
+ while (iterator.hasNext()) {
+ Entity entity = (Entity) iterator.next();
+
+- if (entity instanceof LivingEntity && ((LivingEntity) entity).removeAllEffects()) {
++ if (entity instanceof LivingEntity && ((LivingEntity) entity).removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND)) { // CraftBukkit
+ ++i;
+ }
+ }
+@@ -145,7 +144,7 @@
+ while (iterator.hasNext()) {
+ Entity entity = (Entity) iterator.next();
+
+- if (entity instanceof LivingEntity && ((LivingEntity) entity).removeEffect(mobeffect)) {
++ if (entity instanceof LivingEntity && ((LivingEntity) entity).removeEffect(mobeffectlist, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND)) { // CraftBukkit
+ ++i;
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/GameRuleCommand.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/GameRuleCommand.java.patch
new file mode 100644
index 0000000000..f87d54ce97
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/GameRuleCommand.java.patch
@@ -0,0 +1,26 @@
+--- a/net/minecraft/server/commands/GameRuleCommand.java
++++ b/net/minecraft/server/commands/GameRuleCommand.java
+@@ -31,9 +29,9 @@
+ commanddispatcher.register(literalargumentbuilder);
+ }
+
+- static <T extends GameRules.Value<T>> int setRule(CommandContext<CommandSourceStack> commandcontext, GameRules.Key<T> gamerules_key) {
+- CommandSourceStack commandsourcestack = (CommandSourceStack) commandcontext.getSource();
+- T t0 = commandsourcestack.getServer().getGameRules().getRule(gamerules_key);
++ static <T extends GameRules.Value<T>> int setRule(CommandContext<CommandSourceStack> source, GameRules.Key<T> gameRule) {
++ CommandSourceStack commandlistenerwrapper = (CommandSourceStack) source.getSource();
++ T t0 = commandlistenerwrapper.getLevel().getGameRules().getRule(gameRule); // CraftBukkit
+
+ t0.setFromArgument(commandcontext, "value");
+ commandsourcestack.sendSuccess(() -> {
+@@ -42,8 +40,8 @@
+ return t0.getCommandResult();
+ }
+
+- static <T extends GameRules.Value<T>> int queryRule(CommandSourceStack commandsourcestack, GameRules.Key<T> gamerules_key) {
+- T t0 = commandsourcestack.getServer().getGameRules().getRule(gamerules_key);
++ static <T extends GameRules.Value<T>> int queryRule(CommandSourceStack source, GameRules.Key<T> gameRule) {
++ T t0 = source.getLevel().getGameRules().getRule(gameRule); // CraftBukkit
+
+ commandsourcestack.sendSuccess(() -> {
+ return Component.translatable("commands.gamerule.query", gamerules_key.getId(), t0.toString());
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/GiveCommand.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/GiveCommand.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/GiveCommand.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/ListPlayersCommand.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/ListPlayersCommand.java.patch
new file mode 100644
index 0000000000..da16b8ee43
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/ListPlayersCommand.java.patch
@@ -0,0 +1,24 @@
+--- a/net/minecraft/server/commands/ListPlayersCommand.java
++++ b/net/minecraft/server/commands/ListPlayersCommand.java
+@@ -34,10 +33,17 @@
+ });
+ }
+
+- private static int format(CommandSourceStack commandsourcestack, Function<ServerPlayer, Component> function) {
+- PlayerList playerlist = commandsourcestack.getServer().getPlayerList();
+- List<ServerPlayer> list = playerlist.getPlayers();
+- Component component = ComponentUtils.formatList(list, function);
++ private static int format(CommandSourceStack source, Function<ServerPlayer, Component> nameExtractor) {
++ PlayerList playerlist = source.getServer().getPlayerList();
++ // CraftBukkit start
++ List<ServerPlayer> players = playerlist.getPlayers();
++ if (source.getBukkitSender() instanceof org.bukkit.entity.Player) {
++ org.bukkit.entity.Player sender = (org.bukkit.entity.Player) source.getBukkitSender();
++ players = players.stream().filter((ep) -> sender.canSee(ep.getBukkitEntity())).collect(java.util.stream.Collectors.toList());
++ }
++ List<ServerPlayer> list = players;
++ // CraftBukkit end
++ Component ichatbasecomponent = ComponentUtils.formatList(list, nameExtractor);
+
+ commandsourcestack.sendSuccess(() -> {
+ return Component.translatable("commands.list.players", list.size(), playerlist.getMaxPlayers(), component);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/LootCommand.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/LootCommand.java.patch
new file mode 100644
index 0000000000..0f00292435
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/LootCommand.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/server/commands/LootCommand.java
++++ b/net/minecraft/server/commands/LootCommand.java
+@@ -248,8 +247,9 @@
+ private static int dropInWorld(CommandSourceStack commandsourcestack, Vec3 vec3, List<ItemStack> list, LootCommand.Callback lootcommand_callback) throws CommandSyntaxException {
+ ServerLevel serverlevel = commandsourcestack.getLevel();
+
+- list.forEach((itemstack) -> {
+- ItemEntity itementity = new ItemEntity(serverlevel, vec3.x, vec3.y, vec3.z, itemstack.copy());
++ items.removeIf(ItemStack::isEmpty); // CraftBukkit - SPIGOT-6959 Remove empty items for avoid throw an error in new EntityItem
++ items.forEach((itemstack) -> {
++ ItemEntity entityitem = new ItemEntity(worldserver, pos.x, pos.y, pos.z, itemstack.copy());
+
+ itementity.setDefaultPickUpDelay();
+ serverlevel.addFreshEntity(itementity);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/PlaceCommand.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/PlaceCommand.java.patch
new file mode 100644
index 0000000000..7df5e5ebb8
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/PlaceCommand.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/server/commands/PlaceCommand.java
++++ b/net/minecraft/server/commands/PlaceCommand.java
+@@ -131,9 +130,10 @@
+ if (!structurestart.isValid()) {
+ throw PlaceCommand.ERROR_STRUCTURE_FAILED.create();
+ } else {
+- BoundingBox boundingbox = structurestart.getBoundingBox();
+- ChunkPos chunkpos = new ChunkPos(SectionPos.blockToSectionCoord(boundingbox.minX()), SectionPos.blockToSectionCoord(boundingbox.minZ()));
+- ChunkPos chunkpos1 = new ChunkPos(SectionPos.blockToSectionCoord(boundingbox.maxX()), SectionPos.blockToSectionCoord(boundingbox.maxZ()));
++ structurestart.generationEventCause = org.bukkit.event.world.AsyncStructureGenerateEvent.Cause.COMMAND; // CraftBukkit - set AsyncStructureGenerateEvent.Cause.COMMAND as generation cause
++ BoundingBox structureboundingbox = structurestart.getBoundingBox();
++ ChunkPos chunkcoordintpair = new ChunkPos(SectionPos.blockToSectionCoord(structureboundingbox.minX()), SectionPos.blockToSectionCoord(structureboundingbox.minZ()));
++ ChunkPos chunkcoordintpair1 = new ChunkPos(SectionPos.blockToSectionCoord(structureboundingbox.maxX()), SectionPos.blockToSectionCoord(structureboundingbox.maxZ()));
+
+ checkLoaded(serverlevel, chunkpos, chunkpos1);
+ ChunkPos.rangeClosed(chunkpos, chunkpos1).forEach((chunkpos2) -> {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/ReloadCommand.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/ReloadCommand.java.patch
new file mode 100644
index 0000000000..ebd0f16eed
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/ReloadCommand.java.patch
@@ -0,0 +1,25 @@
+--- a/net/minecraft/server/commands/ReloadCommand.java
++++ b/net/minecraft/server/commands/ReloadCommand.java
+@@ -45,9 +44,19 @@
+ return collection1;
+ }
+
+- public static void register(CommandDispatcher<CommandSourceStack> commanddispatcher) {
+- commanddispatcher.register((LiteralArgumentBuilder) ((LiteralArgumentBuilder) Commands.literal("reload").requires((commandsourcestack) -> {
+- return commandsourcestack.hasPermission(2);
++ // CraftBukkit start
++ public static void reload(MinecraftServer minecraftserver) {
++ PackRepository resourcepackrepository = minecraftserver.getPackRepository();
++ WorldData savedata = minecraftserver.getWorldData();
++ Collection<String> collection = resourcepackrepository.getSelectedIds();
++ Collection<String> collection1 = discoverNewPacks(resourcepackrepository, savedata, collection);
++ minecraftserver.reloadResources(collection1);
++ }
++ // CraftBukkit end
++
++ public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
++ dispatcher.register((LiteralArgumentBuilder) ((LiteralArgumentBuilder) net.minecraft.commands.Commands.literal("reload").requires((commandlistenerwrapper) -> {
++ return commandlistenerwrapper.hasPermission(2);
+ })).executes((commandcontext) -> {
+ CommandSourceStack commandsourcestack = (CommandSourceStack) commandcontext.getSource();
+ MinecraftServer minecraftserver = commandsourcestack.getServer();
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/ScheduleCommand.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/ScheduleCommand.java.patch
new file mode 100644
index 0000000000..f34503edd1
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/ScheduleCommand.java.patch
@@ -0,0 +1,15 @@
+--- a/net/minecraft/server/commands/ScheduleCommand.java
++++ b/net/minecraft/server/commands/ScheduleCommand.java
+@@ -55,9 +53,9 @@
+ if (i == 0) {
+ throw ScheduleCommand.ERROR_SAME_TICK.create();
+ } else {
+- long j = commandsourcestack.getLevel().getGameTime() + (long) i;
+- ResourceLocation resourcelocation = (ResourceLocation) pair.getFirst();
+- TimerQueue<MinecraftServer> timerqueue = commandsourcestack.getServer().getWorldData().overworldData().getScheduledEvents();
++ long j = source.getLevel().getGameTime() + (long) time;
++ ResourceLocation minecraftkey = (ResourceLocation) function.getFirst();
++ TimerQueue<MinecraftServer> customfunctioncallbacktimerqueue = source.getLevel().serverLevelData.overworldData().getScheduledEvents(); // CraftBukkit - SPIGOT-6667: Use world specific function timer
+
+ ((Either) pair.getSecond()).ifLeft((commandfunction) -> {
+ String s = resourcelocation.toString();
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/SetSpawnCommand.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/SetSpawnCommand.java.patch
new file mode 100644
index 0000000000..57fa9887c4
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/SetSpawnCommand.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/server/commands/SetSpawnCommand.java
++++ b/net/minecraft/server/commands/SetSpawnCommand.java
+@@ -42,7 +41,7 @@
+ while (iterator.hasNext()) {
+ ServerPlayer serverplayer = (ServerPlayer) iterator.next();
+
+- serverplayer.setRespawnPosition(resourcekey, blockpos, f, true, false);
++ entityplayer.setRespawnPosition(resourcekey, pos, angle, true, false, org.bukkit.event.player.PlayerSpawnChangeEvent.Cause.COMMAND); // CraftBukkit
+ }
+
+ String s = resourcekey.location().toString();
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/SpreadPlayersCommand.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/SpreadPlayersCommand.java.patch
new file mode 100644
index 0000000000..7812ce47c7
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/SpreadPlayersCommand.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/server/commands/SpreadPlayersCommand.java
++++ b/net/minecraft/server/commands/SpreadPlayersCommand.java
+@@ -204,7 +203,7 @@
+ spreadplayerscommand_position = aspreadplayerscommand_position[j++];
+ }
+
+- entity.teleportTo(serverlevel, (double) Mth.floor(spreadplayerscommand_position.x) + 0.5D, (double) spreadplayerscommand_position.getSpawnY(serverlevel, i), (double) Mth.floor(spreadplayerscommand_position.z) + 0.5D, Set.of(), entity.getYRot(), entity.getXRot());
++ entity.teleportTo(level, (double) Mth.floor(commandspreadplayers_a.x) + 0.5D, (double) commandspreadplayers_a.getSpawnY(level, maxHeight), (double) Mth.floor(commandspreadplayers_a.z) + 0.5D, Set.of(), entity.getYRot(), entity.getXRot(), org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.COMMAND); // CraftBukkit - handle teleport reason
+ d1 = Double.MAX_VALUE;
+ SpreadPlayersCommand.Position[] aspreadplayerscommand_position1 = aspreadplayerscommand_position;
+ int k = aspreadplayerscommand_position.length;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/SummonCommand.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/SummonCommand.java.patch
new file mode 100644
index 0000000000..ecd57e2c6e
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/SummonCommand.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/server/commands/SummonCommand.java
++++ b/net/minecraft/server/commands/SummonCommand.java
+@@ -68,7 +67,7 @@
+ ((Mob) entity).finalizeSpawn(commandsourcestack.getLevel(), commandsourcestack.getLevel().getCurrentDifficultyAt(entity.blockPosition()), MobSpawnType.COMMAND, (SpawnGroupData) null, (CompoundTag) null);
+ }
+
+- if (!serverlevel.tryAddFreshEntityWithPassengers(entity)) {
++ if (!worldserver.tryAddFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.COMMAND)) { // CraftBukkit - pass a spawn reason of "COMMAND"
+ throw SummonCommand.ERROR_DUPLICATE_UUID.create();
+ } else {
+ return entity;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/TeleportCommand.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/TeleportCommand.java.patch
new file mode 100644
index 0000000000..193801f2b2
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/TeleportCommand.java.patch
@@ -0,0 +1,53 @@
+--- a/net/minecraft/server/commands/TeleportCommand.java
++++ b/net/minecraft/server/commands/TeleportCommand.java
+@@ -33,6 +32,12 @@
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.phys.Vec2;
+ import net.minecraft.world.phys.Vec3;
++// CraftBukkit start
++import org.bukkit.Location;
++import org.bukkit.craftbukkit.CraftWorld;
++import org.bukkit.event.entity.EntityTeleportEvent;
++import org.bukkit.event.player.PlayerTeleportEvent;
++// CraftBukkit end
+
+ public class TeleportCommand {
+
+@@ -156,11 +161,34 @@
+ float f2 = Mth.wrapDegrees(f);
+ float f3 = Mth.wrapDegrees(f1);
+
+- if (entity.teleportTo(serverlevel, d0, d1, d2, set, f2, f3)) {
+- if (teleportcommand_lookat != null) {
+- teleportcommand_lookat.perform(commandsourcestack, entity);
++ // CraftBukkit start - Teleport event
++ boolean result;
++ if (entity instanceof ServerPlayer player) {
++ result = player.teleportTo(level, x, d1, y, set, f2, f3, PlayerTeleportEvent.TeleportCause.COMMAND);
++ } else {
++ Location to = new Location(level.getWorld(), x, d1, y, f2, f3);
++ EntityTeleportEvent event = new EntityTeleportEvent(entity.getBukkitEntity(), entity.getBukkitEntity().getLocation(), to);
++ level.getCraftServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ return;
+ }
+
++ x = to.getX();
++ d1 = to.getY();
++ y = to.getZ();
++ f2 = to.getYaw();
++ f3 = to.getPitch();
++ level = ((CraftWorld) to.getWorld()).getHandle();
++
++ result = entity.teleportTo(level, x, d1, y, set, f2, f3);
++ }
++
++ if (result) {
++ // CraftBukkit end
++ if (relativeList != null) {
++ relativeList.perform(source, entity);
++ }
++
+ label23:
+ {
+ if (entity instanceof LivingEntity) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/TimeCommand.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/TimeCommand.java.patch
new file mode 100644
index 0000000000..ad1b09041f
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/TimeCommand.java.patch
@@ -0,0 +1,59 @@
+--- a/net/minecraft/server/commands/TimeCommand.java
++++ b/net/minecraft/server/commands/TimeCommand.java
+@@ -9,6 +8,10 @@
+ import net.minecraft.commands.arguments.TimeArgument;
+ import net.minecraft.network.chat.Component;
+ import net.minecraft.server.level.ServerLevel;
++// CraftBukkit start
++import org.bukkit.Bukkit;
++import org.bukkit.event.world.TimeSkipEvent;
++// CraftBukkit end
+
+ public class TimeCommand {
+
+@@ -49,13 +52,19 @@
+ return i;
+ }
+
+- public static int setTime(CommandSourceStack commandsourcestack, int i) {
+- Iterator iterator = commandsourcestack.getServer().getAllLevels().iterator();
++ public static int setTime(CommandSourceStack source, int time) {
++ Iterator iterator = com.google.common.collect.Iterators.singletonIterator(source.getLevel()); // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in
+
+ while (iterator.hasNext()) {
+ ServerLevel serverlevel = (ServerLevel) iterator.next();
+
+- serverlevel.setDayTime((long) i);
++ // CraftBukkit start
++ TimeSkipEvent event = new TimeSkipEvent(worldserver.getWorld(), TimeSkipEvent.SkipReason.COMMAND, time - worldserver.getDayTime());
++ Bukkit.getPluginManager().callEvent(event);
++ if (!event.isCancelled()) {
++ worldserver.setDayTime((long) worldserver.getDayTime() + event.getSkipAmount());
++ }
++ // CraftBukkit end
+ }
+
+ commandsourcestack.sendSuccess(() -> {
+@@ -64,13 +73,19 @@
+ return getDayTime(commandsourcestack.getLevel());
+ }
+
+- public static int addTime(CommandSourceStack commandsourcestack, int i) {
+- Iterator iterator = commandsourcestack.getServer().getAllLevels().iterator();
++ public static int addTime(CommandSourceStack source, int amount) {
++ Iterator iterator = com.google.common.collect.Iterators.singletonIterator(source.getLevel()); // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in
+
+ while (iterator.hasNext()) {
+ ServerLevel serverlevel = (ServerLevel) iterator.next();
+
+- serverlevel.setDayTime(serverlevel.getDayTime() + (long) i);
++ // CraftBukkit start
++ TimeSkipEvent event = new TimeSkipEvent(worldserver.getWorld(), TimeSkipEvent.SkipReason.COMMAND, amount);
++ Bukkit.getPluginManager().callEvent(event);
++ if (!event.isCancelled()) {
++ worldserver.setDayTime(worldserver.getDayTime() + event.getSkipAmount());
++ }
++ // CraftBukkit end
+ }
+
+ int j = getDayTime(commandsourcestack.getLevel());
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/WorldBorderCommand.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/WorldBorderCommand.java.patch
new file mode 100644
index 0000000000..ff27db18c2
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/commands/WorldBorderCommand.java.patch
@@ -0,0 +1,79 @@
+--- a/net/minecraft/server/commands/WorldBorderCommand.java
++++ b/net/minecraft/server/commands/WorldBorderCommand.java
+@@ -57,8 +56,8 @@
+ })))));
+ }
+
+- private static int setDamageBuffer(CommandSourceStack commandsourcestack, float f) throws CommandSyntaxException {
+- WorldBorder worldborder = commandsourcestack.getServer().overworld().getWorldBorder();
++ private static int setDamageBuffer(CommandSourceStack source, float distance) throws CommandSyntaxException {
++ WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit
+
+ if (worldborder.getDamageSafeZone() == (double) f) {
+ throw WorldBorderCommand.ERROR_SAME_DAMAGE_BUFFER.create();
+@@ -71,8 +70,8 @@
+ }
+ }
+
+- private static int setDamageAmount(CommandSourceStack commandsourcestack, float f) throws CommandSyntaxException {
+- WorldBorder worldborder = commandsourcestack.getServer().overworld().getWorldBorder();
++ private static int setDamageAmount(CommandSourceStack source, float damagePerBlock) throws CommandSyntaxException {
++ WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit
+
+ if (worldborder.getDamagePerBlock() == (double) f) {
+ throw WorldBorderCommand.ERROR_SAME_DAMAGE_AMOUNT.create();
+@@ -85,8 +84,8 @@
+ }
+ }
+
+- private static int setWarningTime(CommandSourceStack commandsourcestack, int i) throws CommandSyntaxException {
+- WorldBorder worldborder = commandsourcestack.getServer().overworld().getWorldBorder();
++ private static int setWarningTime(CommandSourceStack source, int time) throws CommandSyntaxException {
++ WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit
+
+ if (worldborder.getWarningTime() == i) {
+ throw WorldBorderCommand.ERROR_SAME_WARNING_TIME.create();
+@@ -99,8 +98,8 @@
+ }
+ }
+
+- private static int setWarningDistance(CommandSourceStack commandsourcestack, int i) throws CommandSyntaxException {
+- WorldBorder worldborder = commandsourcestack.getServer().overworld().getWorldBorder();
++ private static int setWarningDistance(CommandSourceStack source, int distance) throws CommandSyntaxException {
++ WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit
+
+ if (worldborder.getWarningBlocks() == i) {
+ throw WorldBorderCommand.ERROR_SAME_WARNING_DISTANCE.create();
+@@ -113,8 +112,8 @@
+ }
+ }
+
+- private static int getSize(CommandSourceStack commandsourcestack) {
+- double d0 = commandsourcestack.getServer().overworld().getWorldBorder().getSize();
++ private static int getSize(CommandSourceStack source) {
++ double d0 = source.getLevel().getWorldBorder().getSize(); // CraftBukkit
+
+ commandsourcestack.sendSuccess(() -> {
+ return Component.translatable("commands.worldborder.get", String.format(Locale.ROOT, "%.0f", d0));
+@@ -122,8 +121,8 @@
+ return Mth.floor(d0 + 0.5D);
+ }
+
+- private static int setCenter(CommandSourceStack commandsourcestack, Vec2 vec2) throws CommandSyntaxException {
+- WorldBorder worldborder = commandsourcestack.getServer().overworld().getWorldBorder();
++ private static int setCenter(CommandSourceStack source, Vec2 pos) throws CommandSyntaxException {
++ WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit
+
+ if (worldborder.getCenterX() == (double) vec2.x && worldborder.getCenterZ() == (double) vec2.y) {
+ throw WorldBorderCommand.ERROR_SAME_CENTER.create();
+@@ -138,8 +137,8 @@
+ }
+ }
+
+- private static int setSize(CommandSourceStack commandsourcestack, double d0, long i) throws CommandSyntaxException {
+- WorldBorder worldborder = commandsourcestack.getServer().overworld().getWorldBorder();
++ private static int setSize(CommandSourceStack source, double newSize, long i) throws CommandSyntaxException {
++ WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit
+ double d1 = worldborder.getSize();
+
+ if (d1 == d0) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/dedicated/DedicatedServer.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/dedicated/DedicatedServer.java.patch
new file mode 100644
index 0000000000..6dadb64480
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/dedicated/DedicatedServer.java.patch
@@ -0,0 +1,250 @@
+--- a/net/minecraft/server/dedicated/DedicatedServer.java
++++ b/net/minecraft/server/dedicated/DedicatedServer.java
+@@ -53,6 +52,16 @@
+ import net.minecraft.world.level.storage.LevelStorageSource;
+ import org.slf4j.Logger;
+
++// CraftBukkit start
++import net.minecraft.server.WorldLoader;
++import org.apache.logging.log4j.Level;
++import org.apache.logging.log4j.LogManager;
++import org.apache.logging.log4j.io.IoBuilder;
++import org.bukkit.command.CommandSender;
++import org.bukkit.event.server.ServerCommandEvent;
++import org.bukkit.event.server.RemoteServerCommandEvent;
++// CraftBukkit end
++
+ public class DedicatedServer extends MinecraftServer implements ServerInterface {
+
+ static final Logger LOGGER = LogUtils.getLogger();
+@@ -61,7 +70,7 @@
+ private final List<ConsoleInput> consoleInput = Collections.synchronizedList(Lists.newArrayList());
+ @Nullable
+ private QueryThreadGs4 queryThreadGs4;
+- private final RconConsoleSource rconConsoleSource;
++ // private final RemoteControlCommandListener rconConsoleSource; // CraftBukkit - remove field
+ @Nullable
+ private RconThread rconThread;
+ private final DedicatedServerSettings settings;
+@@ -70,10 +79,12 @@
+ @Nullable
+ private final TextFilterClient textFilterClient;
+
+- public DedicatedServer(Thread thread, LevelStorageSource.LevelStorageAccess levelstoragesource_levelstorageaccess, PackRepository packrepository, WorldStem worldstem, DedicatedServerSettings dedicatedserversettings, DataFixer datafixer, Services services, ChunkProgressListenerFactory chunkprogresslistenerfactory) {
+- super(thread, levelstoragesource_levelstorageaccess, packrepository, worldstem, Proxy.NO_PROXY, datafixer, services, chunkprogresslistenerfactory);
++ // CraftBukkit start - Signature changed
++ public DedicatedServer(joptsimple.OptionSet options, WorldLoader.a worldLoader, Thread thread, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PackRepository resourcepackrepository, WorldStem worldstem, DedicatedServerSettings dedicatedserversettings, DataFixer datafixer, Services services, ChunkProgressListenerFactory worldloadlistenerfactory) {
++ super(options, worldLoader, thread, convertable_conversionsession, resourcepackrepository, worldstem, Proxy.NO_PROXY, datafixer, services, worldloadlistenerfactory);
++ // CraftBukkit end
+ this.settings = dedicatedserversettings;
+- this.rconConsoleSource = new RconConsoleSource(this);
++ // this.rconConsoleSource = new RemoteControlCommandListener(this); // CraftBukkit - remove field
+ this.textFilterClient = TextFilterClient.createFromConfig(dedicatedserversettings.getProperties().textFilteringConfig);
+ }
+
+@@ -83,13 +92,44 @@
+ Thread thread = new Thread("Server console handler") {
+ @Override
+ public void run() {
+- BufferedReader bufferedreader = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));
++ // CraftBukkit start
++ if (!org.bukkit.craftbukkit.Main.useConsole) {
++ return;
++ }
++ jline.console.ConsoleReader bufferedreader = reader;
+
++ // MC-33041, SPIGOT-5538: if System.in is not valid due to javaw, then return
++ try {
++ System.in.available();
++ } catch (IOException ex) {
++ return;
++ }
++ // CraftBukkit end
++
+ String s;
+
+ try {
+- while (!DedicatedServer.this.isStopped() && DedicatedServer.this.isRunning() && (s = bufferedreader.readLine()) != null) {
+- DedicatedServer.this.handleConsoleInput(s, DedicatedServer.this.createCommandSourceStack());
++ // CraftBukkit start - JLine disabling compatibility
++ while (!DedicatedServer.this.isStopped() && DedicatedServer.this.isRunning()) {
++ if (org.bukkit.craftbukkit.Main.useJline) {
++ s = bufferedreader.readLine(">", null);
++ } else {
++ s = bufferedreader.readLine();
++ }
++
++ // SPIGOT-5220: Throttle if EOF (ctrl^d) or stdin is /dev/null
++ if (s == null) {
++ try {
++ Thread.sleep(50L);
++ } catch (InterruptedException ex) {
++ Thread.currentThread().interrupt();
++ }
++ continue;
++ }
++ if (s.trim().length() > 0) { // Trim to filter lines which are just spaces
++ DedicatedServer.this.handleConsoleInput(s, DedicatedServer.this.createCommandSourceStack());
++ }
++ // CraftBukkit end
+ }
+ } catch (IOException ioexception) {
+ DedicatedServer.LOGGER.error("Exception handling console input", ioexception);
+@@ -98,6 +138,27 @@
+ }
+ };
+
++ // CraftBukkit start - TODO: handle command-line logging arguments
++ java.util.logging.Logger global = java.util.logging.Logger.getLogger("");
++ global.setUseParentHandlers(false);
++ for (java.util.logging.Handler handler : global.getHandlers()) {
++ global.removeHandler(handler);
++ }
++ global.addHandler(new org.bukkit.craftbukkit.util.ForwardLogHandler());
++
++ final org.apache.logging.log4j.core.Logger logger = ((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger());
++ for (org.apache.logging.log4j.core.Appender appender : logger.getAppenders().values()) {
++ if (appender instanceof org.apache.logging.log4j.core.appender.ConsoleAppender) {
++ logger.removeAppender(appender);
++ }
++ }
++
++ new org.bukkit.craftbukkit.util.TerminalConsoleWriterThread(System.out, this.reader).start();
++
++ System.setOut(IoBuilder.forLogger(logger).setLevel(Level.INFO).buildPrintStream());
++ System.setErr(IoBuilder.forLogger(logger).setLevel(Level.WARN).buildPrintStream());
++ // CraftBukkit end
++
+ thread.setDaemon(true);
+ thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(DedicatedServer.LOGGER));
+ thread.start();
+@@ -122,7 +183,7 @@
+ this.setMotd(dedicatedserverproperties.motd);
+ super.setPlayerIdleTimeout((Integer) dedicatedserverproperties.playerIdleTimeout.get());
+ this.setEnforceWhitelist(dedicatedserverproperties.enforceWhitelist);
+- this.worldData.setGameType(dedicatedserverproperties.gamemode);
++ // this.worldData.setGameType(dedicatedserverproperties.gamemode); // CraftBukkit - moved to world loading
+ DedicatedServer.LOGGER.info("Default game type: {}", dedicatedserverproperties.gamemode);
+ InetAddress inetaddress = null;
+
+@@ -146,6 +207,12 @@
+ return false;
+ }
+
++ // CraftBukkit start
++ this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage));
++ server.loadPlugins();
++ server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.STARTUP);
++ // CraftBukkit end
++
+ if (!this.usesAuthentication()) {
+ DedicatedServer.LOGGER.warn("**** SERVER IS RUNNING IN OFFLINE/INSECURE MODE!");
+ DedicatedServer.LOGGER.warn("The server will make no attempt to authenticate usernames. Beware.");
+@@ -160,13 +227,13 @@
+ if (!OldUsersConverter.serverReadyAfterUserconversion(this)) {
+ return false;
+ } else {
+- this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage));
++ // this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage)); // CraftBukkit - moved up
+ long i = Util.getNanos();
+
+ SkullBlockEntity.setup(this.services, this);
+ GameProfileCache.setUsesAuthentication(this.usesAuthentication());
+ DedicatedServer.LOGGER.info("Preparing level \"{}\"", this.getLevelIdName());
+- this.loadLevel();
++ this.loadLevel(storageSource.getLevelId()); // CraftBukkit
+ long j = Util.getNanos() - i;
+ String s = String.format(Locale.ROOT, "%.3fs", (double) j / 1.0E9D);
+
+@@ -307,6 +365,7 @@
+ this.queryThreadGs4.stop();
+ }
+
++ System.exit(0); // CraftBukkit
+ }
+
+ @Override
+@@ -330,7 +387,15 @@
+ while (!this.consoleInput.isEmpty()) {
+ ConsoleInput consoleinput = (ConsoleInput) this.consoleInput.remove(0);
+
+- this.getCommands().performPrefixedCommand(consoleinput.source, consoleinput.msg);
++ // CraftBukkit start - ServerCommand for preprocessing
++ ServerCommandEvent event = new ServerCommandEvent(console, servercommand.msg);
++ server.getPluginManager().callEvent(event);
++ if (event.isCancelled()) continue;
++ servercommand = new ConsoleInput(event.getCommand(), servercommand.source);
++
++ // this.getCommands().performPrefixedCommand(servercommand.source, servercommand.msg); // Called in dispatchServerCommand
++ server.dispatchServerCommand(console, servercommand);
++ // CraftBukkit end
+ }
+
+ }
+@@ -582,17 +622,45 @@
+ @Override
+ @Override
+ public String getPluginNames() {
+- return "";
++ // CraftBukkit start - Whole method
++ StringBuilder result = new StringBuilder();
++ org.bukkit.plugin.Plugin[] plugins = server.getPluginManager().getPlugins();
++
++ result.append(server.getName());
++ result.append(" on Bukkit ");
++ result.append(server.getBukkitVersion());
++
++ if (plugins.length > 0 && server.getQueryPlugins()) {
++ result.append(": ");
++
++ for (int i = 0; i < plugins.length; i++) {
++ if (i > 0) {
++ result.append("; ");
++ }
++
++ result.append(plugins[i].getDescription().getName());
++ result.append(" ");
++ result.append(plugins[i].getDescription().getVersion().replaceAll(";", ","));
++ }
++ }
++
++ return result.toString();
++ // CraftBukkit end
+ }
+
+ @Override
+- @Override
+- public String runCommand(String s) {
+- this.rconConsoleSource.prepareForCommand();
++ public String runCommand(String command) {
++ // CraftBukkit start - fire RemoteServerCommandEvent
++ throw new UnsupportedOperationException("Not supported - remote source required.");
++ }
++
++ public String runCommand(RconConsoleSource rconConsoleSource, String s) {
++ rconConsoleSource.prepareForCommand();
+ this.executeBlocking(() -> {
+ this.getCommands().performPrefixedCommand(this.rconConsoleSource.createCommandSourceStack(), s);
+ });
+- return this.rconConsoleSource.getCommandResponse();
++ return rconConsoleSource.getCommandResponse();
++ // CraftBukkit end
+ }
+
+ public void storeUsingWhiteList(boolean flag) {
+@@ -651,4 +718,15 @@
+ public Optional<MinecraftServer.ServerResourcePackInfo> getServerResourcePack() {
+ return this.settings.getProperties().serverResourcePackInfo;
+ }
++
++ // CraftBukkit start
++ public boolean isDebugging() {
++ return this.getProperties().debug;
++ }
++
++ @Override
++ public CommandSender getBukkitSender(CommandSourceStack wrapper) {
++ return console;
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/dedicated/DedicatedServerProperties.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/dedicated/DedicatedServerProperties.java.patch
new file mode 100644
index 0000000000..e4edcccb3e
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/dedicated/DedicatedServerProperties.java.patch
@@ -0,0 +1,53 @@
+--- a/net/minecraft/server/dedicated/DedicatedServerProperties.java
++++ b/net/minecraft/server/dedicated/DedicatedServerProperties.java
+@@ -44,11 +44,16 @@
+ import net.minecraft.world.level.levelgen.presets.WorldPresets;
+ import org.slf4j.Logger;
+
++// CraftBukkit start
++import joptsimple.OptionSet;
++// CraftBukkit end
++
+ public class DedicatedServerProperties extends Settings<DedicatedServerProperties> {
+
+ static final Logger LOGGER = LogUtils.getLogger();
+ private static final Pattern SHA1 = Pattern.compile("^[a-fA-F0-9]{40}$");
+ private static final Splitter COMMA_SPLITTER = Splitter.on(',').trimResults();
++ public final boolean debug = this.get("debug", false); // CraftBukkit
+ public final boolean onlineMode = this.get("online-mode", true);
+ public final boolean preventProxyConnections = this.get("prevent-proxy-connections", false);
+ public final String serverIp = this.get("server-ip", "");
+@@ -103,8 +108,10 @@
+ private final DedicatedServerProperties.WorldDimensionData worldDimensionData;
+ public final WorldOptions worldOptions;
+
+- public DedicatedServerProperties(Properties properties) {
+- super(properties);
++ // CraftBukkit start
++ public DedicatedServerProperties(Properties properties, OptionSet optionset) {
++ super(properties, optionset);
++ // CraftBukkit end
+ this.difficulty = (Difficulty) this.get("difficulty", dispatchNumberOrString(Difficulty::byId, Difficulty::byName), Difficulty::getKey, Difficulty.EASY);
+ this.gamemode = (GameType) this.get("gamemode", dispatchNumberOrString(GameType::byId, GameType::byName), GameType::getName, GameType.SURVIVAL);
+ this.levelName = this.get("level-name", "world");
+@@ -161,14 +168,15 @@
+ this.initialDataPackConfiguration = getDatapackConfig(this.get("initial-enabled-packs", String.join(",", WorldDataConfiguration.DEFAULT.dataPacks().getEnabled())), this.get("initial-disabled-packs", String.join(",", WorldDataConfiguration.DEFAULT.dataPacks().getDisabled())));
+ }
+
+- public static DedicatedServerProperties fromFile(Path path) {
+- return new DedicatedServerProperties(loadFromFile(path));
++ // CraftBukkit start
++ public static DedicatedServerProperties fromFile(Path path, OptionSet optionset) {
++ return new DedicatedServerProperties(loadFromFile(path), optionset);
+ }
+
+ @Override
+- @Override
+- protected DedicatedServerProperties reload(RegistryAccess registryaccess, Properties properties) {
+- return new DedicatedServerProperties(properties);
++ protected DedicatedServerProperties reload(RegistryAccess iregistrycustom, Properties properties, OptionSet optionset) {
++ return new DedicatedServerProperties(properties, optionset);
++ // CraftBukkit end
+ }
+
+ @Nullable
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/dedicated/DedicatedServerSettings.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/dedicated/DedicatedServerSettings.java.patch
new file mode 100644
index 0000000000..9a52b5cc81
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/dedicated/DedicatedServerSettings.java.patch
@@ -0,0 +1,27 @@
+--- a/net/minecraft/server/dedicated/DedicatedServerSettings.java
++++ b/net/minecraft/server/dedicated/DedicatedServerSettings.java
+@@ -3,14 +3,21 @@
+ import java.nio.file.Path;
+ import java.util.function.UnaryOperator;
+
++// CraftBukkit start
++import java.io.File;
++import joptsimple.OptionSet;
++// CraftBukkit end
++
+ public class DedicatedServerSettings {
+
+ private final Path source;
+ private DedicatedServerProperties properties;
+
+- public DedicatedServerSettings(Path path) {
+- this.source = path;
+- this.properties = DedicatedServerProperties.fromFile(path);
++ // CraftBukkit start
++ public DedicatedServerSettings(OptionSet optionset) {
++ this.source = ((File) optionset.valueOf("config")).toPath();
++ this.properties = DedicatedServerProperties.fromFile(source, optionset);
++ // CraftBukkit end
+ }
+
+ public DedicatedServerProperties getProperties() {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/dedicated/Settings.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/dedicated/Settings.java.patch
new file mode 100644
index 0000000000..d784dbc38c
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/dedicated/Settings.java.patch
@@ -0,0 +1,102 @@
+--- a/net/minecraft/server/dedicated/Settings.java
++++ b/net/minecraft/server/dedicated/Settings.java
+@@ -23,17 +22,36 @@
+ import net.minecraft.core.RegistryAccess;
+ import org.slf4j.Logger;
+
++import joptsimple.OptionSet; // CraftBukkit
++import net.minecraft.core.RegistryAccess;
++
+ public abstract class Settings<T extends Settings<T>> {
+
+ private static final Logger LOGGER = LogUtils.getLogger();
+- protected final Properties properties;
++ public final Properties properties;
++ // CraftBukkit start
++ private OptionSet options = null;
+
+ public Settings(Properties properties) {
+ this.properties = properties;
+ }
+
++ private String getOverride(String name, String value) {
++ if ((this.options != null) && (this.options.has(name))) {
++ return String.valueOf(this.options.valueOf(name));
++ }
++
++ return value;
++ // CraftBukkit end
++ }
++
+ public static Properties loadFromFile(Path path) {
+ try {
++ // CraftBukkit start - SPIGOT-7465, MC-264979: Don't load if file doesn't exist
++ if (!path.toFile().exists()) {
++ return new Properties();
++ }
++ // CraftBukkit end
+ Properties properties;
+ Properties properties1;
+
+@@ -97,6 +117,11 @@
+
+ public void store(Path path) {
+ try {
++ // CraftBukkit start - Don't attempt writing to file if it's read only
++ if (path.toFile().exists() && !path.toFile().canWrite()) {
++ return;
++ }
++ // CraftBukkit end
+ BufferedWriter bufferedwriter = Files.newBufferedWriter(path, StandardCharsets.UTF_8);
+
+ try {
+@@ -143,8 +168,8 @@
+ }
+
+ @Nullable
+- private String getStringRaw(String s) {
+- return (String) this.properties.get(s);
++ private String getStringRaw(String key) {
++ return (String) getOverride(key, this.properties.getProperty(key)); // CraftBukkit
+ }
+
+ @Nullable
+@@ -159,7 +184,17 @@
+ }
+ }
+
+- protected <V> V get(String s, Function<String, V> function, Function<V, String> function1, V v0) {
++ protected <V> V get(String key, Function<String, V> mapper, Function<V, String> toString, V value) {
++ // CraftBukkit start
++ try {
++ return get0(key, mapper, toString, value);
++ } catch (Exception ex) {
++ throw new RuntimeException("Could not load invalidly configured property '" + key + "'", ex);
++ }
++ }
++
++ private <V> V get0(String s, Function<String, V> function, Function<V, String> function1, V v0) {
++ // CraftBukkit end
+ String s1 = this.getStringRaw(s);
+ V v1 = MoreObjects.firstNonNull(s1 != null ? function.apply(s1) : null, v0);
+
+@@ -236,7 +271,7 @@
+ return properties;
+ }
+
+- protected abstract T reload(RegistryAccess registryAccess, Properties properties);
++ protected abstract T reload(RegistryAccess iregistrycustom, Properties properties, OptionSet optionset); // CraftBukkit
+
+ public class MutableValue<V> implements Supplier<V> {
+
+@@ -258,8 +292,8 @@
+ public T update(RegistryAccess registryaccess, V v0) {
+ Properties properties = Settings.this.cloneProperties();
+
+- properties.put(this.key, this.serializer.apply(v0));
+- return Settings.this.reload(registryaccess, properties);
++ properties.put(this.key, this.serializer.apply(newValue));
++ return Settings.this.reload(registryAccess, properties, Settings.this.options); // CraftBukkit
+ }
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/gui/MinecraftServerGui.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/gui/MinecraftServerGui.java.patch
new file mode 100644
index 0000000000..107e690cde
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/gui/MinecraftServerGui.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/server/gui/MinecraftServerGui.java
++++ b/net/minecraft/server/gui/MinecraftServerGui.java
+@@ -168,7 +166,8 @@
+ this.finalizers.forEach(Runnable::run);
+ }
+
+- public void print(JTextArea jtextarea, JScrollPane jscrollpane, String s) {
++ private static final java.util.regex.Pattern ANSI = java.util.regex.Pattern.compile("\\x1B\\[([0-9]{1,2}(;[0-9]{1,2})*)?[m|K]"); // CraftBukkit
++ public void print(JTextArea textArea, JScrollPane scrollPane, String line) {
+ if (!SwingUtilities.isEventDispatchThread()) {
+ SwingUtilities.invokeLater(() -> {
+ this.print(jtextarea, jscrollpane, s);
+@@ -183,7 +182,7 @@
+ }
+
+ try {
+- document.insertString(document.getLength(), s, (AttributeSet) null);
++ document.insertString(document.getLength(), ANSI.matcher(line).replaceAll(""), (AttributeSet) null); // CraftBukkit
+ } catch (BadLocationException badlocationexception) {
+ ;
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ChunkHolder.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ChunkHolder.java.patch
new file mode 100644
index 0000000000..3e97a8baff
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ChunkHolder.java.patch
@@ -0,0 +1,122 @@
+--- a/net/minecraft/server/level/ChunkHolder.java
++++ b/net/minecraft/server/level/ChunkHolder.java
+@@ -36,6 +36,10 @@
+ import net.minecraft.world.level.chunk.ProtoChunk;
+ import net.minecraft.world.level.lighting.LevelLightEngine;
+
++// CraftBukkit start
++import net.minecraft.server.MinecraftServer;
++// CraftBukkit end
++
+ public class ChunkHolder {
+
+ public static final Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> UNLOADED_CHUNK = Either.right(ChunkHolder.ChunkLoadingFailure.UNLOADED);
+@@ -90,9 +94,23 @@
+ this.changedBlocksPerSection = new ShortSet[levelheightaccessor.getSectionsCount()];
+ }
+
+- public CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getFutureIfPresentUnchecked(ChunkStatus chunkstatus) {
+- CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completablefuture = (CompletableFuture) this.futures.get(chunkstatus.getIndex());
++ // CraftBukkit start
++ public LevelChunk getFullChunkNow() {
++ // Note: We use the oldTicketLevel for isLoaded checks.
++ if (!ChunkLevel.fullStatus(this.oldTicketLevel).isOrAfter(FullChunkStatus.FULL)) return null;
++ return this.getFullChunkNowUnchecked();
++ }
+
++ public LevelChunk getFullChunkNowUnchecked() {
++ CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>> statusFuture = this.getFutureIfPresentUnchecked(ChunkStatus.FULL);
++ Either<ChunkAccess, ChunkHolder.Failure> either = (Either<ChunkAccess, ChunkHolder.Failure>) statusFuture.getNow(null);
++ return (either == null) ? null : (LevelChunk) either.left().orElse(null);
++ }
++ // CraftBukkit end
++
++ public CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>> getFutureIfPresentUnchecked(ChunkStatus chunkStatus) {
++ CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>> completablefuture = (CompletableFuture) this.futures.get(chunkStatus.getIndex());
++
+ return completablefuture == null ? ChunkHolder.UNLOADED_CHUNK_FUTURE : completablefuture;
+ }
+
+@@ -179,6 +197,7 @@
+ if (levelchunk != null) {
+ int i = this.levelHeightAccessor.getSectionIndex(blockpos.getY());
+
++ if (i < 0 || i >= this.changedBlocksPerSection.length) return; // CraftBukkit - SPIGOT-6086, SPIGOT-6296
+ if (this.changedBlocksPerSection[i] == null) {
+ this.hasChangedSections = true;
+ this.changedBlocksPerSection[i] = new ShortOpenHashSet();
+@@ -256,9 +275,12 @@
+ LevelChunkSection levelchunksection = levelchunk.getSection(i);
+ ClientboundSectionBlocksUpdatePacket clientboundsectionblocksupdatepacket = new ClientboundSectionBlocksUpdatePacket(sectionpos, shortset, levelchunksection);
+
+- this.broadcast(list, clientboundsectionblocksupdatepacket);
+- clientboundsectionblocksupdatepacket.runUpdates((blockpos1, blockstate1) -> {
+- this.broadcastBlockEntityIfNeeded(list, level, blockpos1, blockstate1);
++ this.broadcast(list, packetplayoutmultiblockchange);
++ // CraftBukkit start
++ List finalList = list;
++ packetplayoutmultiblockchange.runUpdates((blockposition1, iblockdata1) -> {
++ this.broadcastBlockEntityIfNeeded(finalList, world, blockposition1, iblockdata1);
++ // CraftBukkit end
+ });
+ }
+ }
+@@ -411,7 +433,31 @@
+ boolean flag1 = ChunkLevel.isLoaded(this.ticketLevel);
+ FullChunkStatus fullchunkstatus = ChunkLevel.fullStatus(this.oldTicketLevel);
+ FullChunkStatus fullchunkstatus1 = ChunkLevel.fullStatus(this.ticketLevel);
++ // CraftBukkit start
++ // ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins.
++ if (fullchunkstatus.isOrAfter(FullChunkStatus.FULL) && !fullchunkstatus1.isOrAfter(FullChunkStatus.FULL)) {
++ this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> {
++ LevelChunk chunk = (LevelChunk)either.left().orElse(null);
++ if (chunk != null) {
++ chunkMap.callbackExecutor.execute(() -> {
++ // Minecraft will apply the chunks tick lists to the world once the chunk got loaded, and then store the tick
++ // lists again inside the chunk once the chunk becomes inaccessible and set the chunk's needsSaving flag.
++ // These actions may however happen deferred, so we manually set the needsSaving flag already here.
++ chunk.setUnsaved(true);
++ chunk.unloadCallback();
++ });
++ }
++ }).exceptionally((throwable) -> {
++ // ensure exceptions are printed, by default this is not the case
++ MinecraftServer.LOGGER.error("Failed to schedule unload callback for chunk " + ChunkHolder.this.pos, throwable);
++ return null;
++ });
+
++ // Run callback right away if the future was already done
++ chunkMap.callbackExecutor.run();
++ }
++ // CraftBukkit end
++
+ if (flag) {
+ Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either = Either.right(new ChunkHolder.ChunkLoadingFailure() {
+ @Override
+@@ -482,6 +527,26 @@
+
+ this.onLevelChange.onLevelChange(this.pos, this::getQueueLevel, this.ticketLevel, this::setQueueLevel);
+ this.oldTicketLevel = this.ticketLevel;
++ // CraftBukkit start
++ // ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins.
++ if (!fullchunkstatus.isOrAfter(FullChunkStatus.FULL) && fullchunkstatus1.isOrAfter(FullChunkStatus.FULL)) {
++ this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> {
++ LevelChunk chunk = (LevelChunk)either.left().orElse(null);
++ if (chunk != null) {
++ chunkMap.callbackExecutor.execute(() -> {
++ chunk.loadCallback();
++ });
++ }
++ }).exceptionally((throwable) -> {
++ // ensure exceptions are printed, by default this is not the case
++ MinecraftServer.LOGGER.error("Failed to schedule load callback for chunk " + ChunkHolder.this.pos, throwable);
++ return null;
++ });
++
++ // Run callback right away if the future was already done
++ chunkMap.callbackExecutor.run();
++ }
++ // CraftBukkit end
+ }
+
+ public boolean wasAccessibleSinceLastSave() {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ChunkMap.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ChunkMap.java.patch
new file mode 100644
index 0000000000..720cf2486b
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ChunkMap.java.patch
@@ -0,0 +1,147 @@
+--- a/net/minecraft/server/level/ChunkMap.java
++++ b/net/minecraft/server/level/ChunkMap.java
+@@ -100,6 +100,9 @@
+ import net.minecraft.world.phys.Vec3;
+ import org.apache.commons.lang3.mutable.MutableBoolean;
+ import org.slf4j.Logger;
++import org.bukkit.craftbukkit.generator.CustomChunkGenerator;
++import org.bukkit.entity.Player;
++// CraftBukkit end
+
+ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider {
+
+@@ -143,8 +146,29 @@
+ private final Queue<Runnable> unloadQueue;
+ private int serverViewDistance;
+
+- public ChunkMap(ServerLevel serverlevel, LevelStorageSource.LevelStorageAccess levelstoragesource_levelstorageaccess, DataFixer datafixer, StructureTemplateManager structuretemplatemanager, Executor executor, BlockableEventLoop<Runnable> blockableeventloop, LightChunkGetter lightchunkgetter, ChunkGenerator chunkgenerator, ChunkProgressListener chunkprogresslistener, ChunkStatusUpdateListener chunkstatusupdatelistener, Supplier<DimensionDataStorage> supplier, int i, boolean flag) {
+- super(levelstoragesource_levelstorageaccess.getDimensionPath(serverlevel.dimension()).resolve("region"), datafixer, flag);
++ // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback()
++ public final CallbackExecutor callbackExecutor = new CallbackExecutor();
++ public static final class CallbackExecutor implements java.util.concurrent.Executor, Runnable {
++
++ private final java.util.Queue<Runnable> queue = new java.util.ArrayDeque<>();
++
++ @Override
++ public void execute(Runnable runnable) {
++ queue.add(runnable);
++ }
++
++ @Override
++ public void run() {
++ Runnable task;
++ while ((task = queue.poll()) != null) {
++ task.run();
++ }
++ }
++ };
++ // CraftBukkit end
++
++ public ChunkMap(ServerLevel level, LevelStorageSource.LevelStorageAccess levelStorageAccess, DataFixer fixerUpper, StructureTemplateManager structureManager, Executor dispatcher, BlockableEventLoop<Runnable> mainThreadExecutor, LightChunkGetter lightChunk, ChunkGenerator generator, ChunkProgressListener progressListener, ChunkStatusUpdateListener chunkStatusListener, Supplier<DimensionDataStorage> overworldDataStorage, int viewDistance, boolean sync) {
++ super(levelStorageAccess.getDimensionPath(level.dimension()).resolve("region"), fixerUpper, sync);
+ this.visibleChunkMap = this.updatingChunkMap.clone();
+ this.pendingUnloads = new Long2ObjectLinkedOpenHashMap();
+ this.entitiesInLevel = new LongOpenHashSet();
+@@ -159,10 +183,15 @@
+ Path path = levelstoragesource_levelstorageaccess.getDimensionPath(serverlevel.dimension());
+
+ this.storageName = path.getFileName().toString();
+- this.level = serverlevel;
+- this.generator = chunkgenerator;
+- RegistryAccess registryaccess = serverlevel.registryAccess();
+- long j = serverlevel.getSeed();
++ this.level = level;
++ this.generator = generator;
++ // CraftBukkit start - SPIGOT-7051: It's a rigged game! Use delegate for random state creation, otherwise it is not so random.
++ if (generator instanceof CustomChunkGenerator) {
++ generator = ((CustomChunkGenerator) generator).getDelegate();
++ }
++ // CraftBukkit end
++ RegistryAccess iregistrycustom = level.registryAccess();
++ long j = level.getSeed();
+
+ if (chunkgenerator instanceof NoiseBasedChunkGenerator) {
+ NoiseBasedChunkGenerator noisebasedchunkgenerator = (NoiseBasedChunkGenerator) chunkgenerator;
+@@ -335,8 +364,10 @@
+ List<ChunkAccess> list3 = Lists.newArrayList();
+ final int l1 = 0;
+
+- for (Iterator iterator = list2.iterator(); iterator.hasNext(); ++l1) {
+- final Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either = (Either) iterator.next();
++ for (Iterator iterator = list2.iterator(); iterator.hasNext(); ++cnt) {
++ final int l1 = cnt;
++ // CraftBukkit end
++ final Either<ChunkAccess, ChunkHolder.Failure> either = (Either) iterator.next();
+
+ if (either == null) {
+ throw this.debugFuturesAndCreateReportedException(new IllegalStateException("At least one of the chunk futures were null"), "n/a");
+@@ -749,9 +778,23 @@
+ return chunkstatus1;
+ }
+
+- private static void postLoadProtoChunk(ServerLevel serverlevel, List<CompoundTag> list) {
+- if (!list.isEmpty()) {
+- serverlevel.addWorldGenChunkEntities(EntityType.loadEntitiesRecursive(list, serverlevel));
++ private static void postLoadProtoChunk(ServerLevel level, List<CompoundTag> tags) {
++ if (!tags.isEmpty()) {
++ // CraftBukkit start - these are spawned serialized (DefinedStructure) and we don't call an add event below at the moment due to ordering complexities
++ level.addWorldGenChunkEntities(EntityType.loadEntitiesRecursive(tags, level).filter((entity) -> {
++ boolean needsRemoval = false;
++ net.minecraft.server.dedicated.DedicatedServer server = level.getCraftServer().getServer();
++ if (!server.areNpcsEnabled() && entity instanceof net.minecraft.world.entity.npc.NPC) {
++ entity.discard();
++ needsRemoval = true;
++ }
++ if (!server.isSpawningAnimals() && (entity instanceof net.minecraft.world.entity.animal.Animal || entity instanceof net.minecraft.world.entity.animal.WaterAnimal)) {
++ entity.discard();
++ needsRemoval = true;
++ }
++ return !needsRemoval;
++ }));
++ // CraftBukkit end
+ }
+
+ }
+@@ -1047,14 +1091,16 @@
+ }
+ }
+
+- private CompletableFuture<Optional<CompoundTag>> readChunk(ChunkPos chunkpos) {
+- return this.read(chunkpos).thenApplyAsync((optional) -> {
+- return optional.map(this::upgradeChunkTag);
++ private CompletableFuture<Optional<CompoundTag>> readChunk(ChunkPos pos) {
++ return this.read(pos).thenApplyAsync((optional) -> {
++ return optional.map((nbttagcompound) -> upgradeChunkTag(nbttagcompound, pos)); // CraftBukkit
+ }, Util.backgroundExecutor());
+ }
+
+- private CompoundTag upgradeChunkTag(CompoundTag compoundtag) {
+- return this.upgradeChunkTag(this.level.dimension(), this.overworldDataStorage, compoundtag, this.generator.getTypeNameForDataFixer());
++ // CraftBukkit start
++ private CompoundTag upgradeChunkTag(CompoundTag nbttagcompound, ChunkPos chunkcoordintpair) {
++ return this.upgradeChunkTag(this.level.getTypeKey(), this.overworldDataStorage, nbttagcompound, this.generator.getTypeNameForDataFixer(), chunkcoordintpair, level);
++ // CraftBukkit end
+ }
+
+ boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkpos) {
+@@ -1467,7 +1509,7 @@
+ private final Set<ServerPlayerConnection> seenBy = Sets.newIdentityHashSet();
+
+ public TrackedEntity(Entity entity, int i, int j, boolean flag) {
+- this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast);
++ this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, seenBy); // CraftBukkit
+ this.entity = entity;
+ this.range = i;
+ this.lastSectionPos = SectionPos.of((EntityAccess) entity);
+@@ -1529,6 +1569,11 @@
+ double d2 = d0 * d0;
+ boolean flag = d1 <= d2 && this.entity.broadcastToPlayer(serverplayer) && ChunkMap.this.isChunkTracked(serverplayer, this.entity.chunkPosition().x, this.entity.chunkPosition().z);
+
++ // CraftBukkit start - respect vanish API
++ if (!player.getBukkitEntity().canSee(this.entity.getBukkitEntity())) {
++ flag = false;
++ }
++ // CraftBukkit end
+ if (flag) {
+ if (this.seenBy.add(serverplayer.connection)) {
+ this.serverEntity.addPairing(serverplayer);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/DistanceManager.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/DistanceManager.java.patch
new file mode 100644
index 0000000000..3ee42861ac
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/DistanceManager.java.patch
@@ -0,0 +1,149 @@
+--- a/net/minecraft/server/level/DistanceManager.java
++++ b/net/minecraft/server/level/DistanceManager.java
+@@ -122,10 +122,25 @@
+ }
+
+ if (!this.chunksToUpdateFutures.isEmpty()) {
+- this.chunksToUpdateFutures.forEach((chunkholder) -> {
+- chunkholder.updateFutures(chunkmap, this.mainThreadExecutor);
+- });
+- this.chunksToUpdateFutures.clear();
++ // CraftBukkit start
++ // Iterate pending chunk updates with protection against concurrent modification exceptions
++ java.util.Iterator<ChunkHolder> iter = this.chunksToUpdateFutures.iterator();
++ int expectedSize = this.chunksToUpdateFutures.size();
++ do {
++ ChunkHolder playerchunk = iter.next();
++ iter.remove();
++ expectedSize--;
++
++ playerchunk.updateFutures(chunkManager, this.mainThreadExecutor);
++
++ // Reset iterator if set was modified using add()
++ if (this.chunksToUpdateFutures.size() != expectedSize) {
++ expectedSize = this.chunksToUpdateFutures.size();
++ iter = this.chunksToUpdateFutures.iterator();
++ }
++ } while (iter.hasNext());
++ // CraftBukkit end
++
+ return true;
+ } else {
+ if (!this.ticketsToRelease.isEmpty()) {
+@@ -161,30 +176,33 @@
+ }
+ }
+
+- void addTicket(long i, Ticket<?> ticket) {
+- SortedArraySet<Ticket<?>> sortedarrayset = this.getTickets(i);
+- int j = getTicketLevelAt(sortedarrayset);
+- Ticket<?> ticket1 = (Ticket) sortedarrayset.addOrGet(ticket);
++ boolean addTicket(long i, Ticket<?> ticket) { // CraftBukkit - void -> boolean
++ SortedArraySet<Ticket<?>> arraysetsorted = this.getTickets(i);
++ int j = getTicketLevelAt(arraysetsorted);
++ Ticket<?> ticket1 = (Ticket) arraysetsorted.addOrGet(ticket);
+
+ ticket1.setCreatedTick(this.ticketTickCounter);
+ if (ticket.getTicketLevel() < j) {
+ this.ticketTracker.update(i, ticket.getTicketLevel(), true);
+ }
+
++ return ticket == ticket1; // CraftBukkit
+ }
+
+- void removeTicket(long i, Ticket<?> ticket) {
+- SortedArraySet<Ticket<?>> sortedarrayset = this.getTickets(i);
++ boolean removeTicket(long i, Ticket<?> ticket) { // CraftBukkit - void -> boolean
++ SortedArraySet<Ticket<?>> arraysetsorted = this.getTickets(i);
+
+- if (sortedarrayset.remove(ticket)) {
+- ;
++ boolean removed = false; // CraftBukkit
++ if (arraysetsorted.remove(ticket)) {
++ removed = true; // CraftBukkit
+ }
+
+ if (sortedarrayset.isEmpty()) {
+ this.tickets.remove(i);
+ }
+
+- this.ticketTracker.update(i, getTicketLevelAt(sortedarrayset), false);
++ this.ticketTracker.update(i, getTicketLevelAt(arraysetsorted), false);
++ return removed; // CraftBukkit
+ }
+
+ public <T> void addTicket(TicketType<T> tickettype, ChunkPos chunkpos, int i, T t0) {
+@@ -197,20 +215,34 @@
+ this.removeTicket(chunkpos.toLong(), ticket);
+ }
+
+- public <T> void addRegionTicket(TicketType<T> tickettype, ChunkPos chunkpos, int i, T t0) {
++ public <T> void addRegionTicket(TicketType<T> type, ChunkPos pos, int distance, T value) {
++ // CraftBukkit start
++ addRegionTicketAtDistance(type, pos, distance, value);
++ }
++
++ public <T> boolean addRegionTicketAtDistance(TicketType<T> tickettype, ChunkPos chunkcoordintpair, int i, T t0) {
++ // CraftBukkit end
+ Ticket<T> ticket = new Ticket<>(tickettype, ChunkLevel.byStatus(FullChunkStatus.FULL) - i, t0);
+ long j = chunkpos.toLong();
+
+- this.addTicket(j, ticket);
++ boolean added = this.addTicket(j, ticket); // CraftBukkit
+ this.tickingTicketsTracker.addTicket(j, ticket);
++ return added; // CraftBukkit
+ }
+
+- public <T> void removeRegionTicket(TicketType<T> tickettype, ChunkPos chunkpos, int i, T t0) {
++ public <T> void removeRegionTicket(TicketType<T> type, ChunkPos pos, int distance, T value) {
++ // CraftBukkit start
++ removeRegionTicketAtDistance(type, pos, distance, value);
++ }
++
++ public <T> boolean removeRegionTicketAtDistance(TicketType<T> tickettype, ChunkPos chunkcoordintpair, int i, T t0) {
++ // CraftBukkit end
+ Ticket<T> ticket = new Ticket<>(tickettype, ChunkLevel.byStatus(FullChunkStatus.FULL) - i, t0);
+ long j = chunkpos.toLong();
+
+- this.removeTicket(j, ticket);
++ boolean removed = this.removeTicket(j, ticket); // CraftBukkit
+ this.tickingTicketsTracker.removeTicket(j, ticket);
++ return removed; // CraftBukkit
+ }
+
+ private SortedArraySet<Ticket<?>> getTickets(long i) {
+@@ -249,6 +281,7 @@
+ ChunkPos chunkpos = sectionpos.chunk();
+ long i = chunkpos.toLong();
+ ObjectSet<ServerPlayer> objectset = (ObjectSet) this.playersPerChunk.get(i);
++ if (objectset == null) return; // CraftBukkit - SPIGOT-6208
+
+ objectset.remove(serverplayer);
+ if (objectset.isEmpty()) {
+@@ -378,6 +411,26 @@
+ return !this.tickets.isEmpty();
+ }
+
++ // CraftBukkit start
++ public <T> void removeAllTicketsFor(TicketType<T> ticketType, int ticketLevel, T ticketIdentifier) {
++ Ticket<T> target = new Ticket<>(ticketType, ticketLevel, ticketIdentifier);
++
++ for (java.util.Iterator<Entry<SortedArraySet<Ticket<?>>>> iterator = this.tickets.long2ObjectEntrySet().fastIterator(); iterator.hasNext();) {
++ Entry<SortedArraySet<Ticket<?>>> entry = iterator.next();
++ SortedArraySet<Ticket<?>> tickets = entry.getValue();
++ if (tickets.remove(target)) {
++ // copied from removeTicket
++ this.ticketTracker.update(entry.getLongKey(), getTicketLevelAt(tickets), false);
++
++ // can't use entry after it's removed
++ if (tickets.isEmpty()) {
++ iterator.remove();
++ }
++ }
++ }
++ }
++ // CraftBukkit end
++
+ private class ChunkTicketTracker extends ChunkTracker {
+
+ private static final int MAX_LEVEL = ChunkLevel.MAX_LEVEL + 1;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ServerChunkCache.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ServerChunkCache.java.patch
new file mode 100644
index 0000000000..5e59873968
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ServerChunkCache.java.patch
@@ -0,0 +1,139 @@
+--- a/net/minecraft/server/level/ServerChunkCache.java
++++ b/net/minecraft/server/level/ServerChunkCache.java
+@@ -83,6 +83,16 @@
+ this.clearCache();
+ }
+
++ // CraftBukkit start - properly implement isChunkLoaded
++ public boolean isChunkLoaded(int chunkX, int chunkZ) {
++ ChunkHolder chunk = this.chunkMap.getUpdatingChunkIfPresent(ChunkPos.asLong(chunkX, chunkZ));
++ if (chunk == null) {
++ return false;
++ }
++ return chunk.getFullChunkNow() != null;
++ }
++ // CraftBukkit end
++
+ @Override
+ @Override
+ public ThreadedLevelLightEngine getLightEngine() {
+@@ -127,10 +135,10 @@
+ ChunkAccess chunkaccess;
+
+ for (int l = 0; l < 4; ++l) {
+- if (k == this.lastChunkPos[l] && chunkstatus == this.lastChunkStatus[l]) {
+- chunkaccess = this.lastChunk[l];
+- if (chunkaccess != null || !flag) {
+- return chunkaccess;
++ if (k == this.lastChunkPos[l] && requiredStatus == this.lastChunkStatus[l]) {
++ ichunkaccess = this.lastChunk[l];
++ if (ichunkaccess != null) { // CraftBukkit - the chunk can become accessible in the meantime TODO for non-null chunks it might also make sense to check that the chunk's state hasn't changed in the meantime
++ return ichunkaccess;
+ }
+ }
+ }
+@@ -231,10 +238,18 @@
+ int l = ChunkLevel.byStatus(chunkstatus);
+ ChunkHolder chunkholder = this.getVisibleChunkIfPresent(k);
+
+- if (flag) {
+- this.distanceManager.addTicket(TicketType.UNKNOWN, chunkpos, l, chunkpos);
+- if (this.chunkAbsent(chunkholder, l)) {
+- ProfilerFiller profilerfiller = this.level.getProfiler();
++ // CraftBukkit start - don't add new ticket for currently unloading chunk
++ boolean currentlyUnloading = false;
++ if (playerchunk != null) {
++ FullChunkStatus oldChunkState = ChunkLevel.fullStatus(playerchunk.oldTicketLevel);
++ FullChunkStatus currentChunkState = ChunkLevel.fullStatus(playerchunk.getTicketLevel());
++ currentlyUnloading = (oldChunkState.isOrAfter(FullChunkStatus.FULL) && !currentChunkState.isOrAfter(FullChunkStatus.FULL));
++ }
++ if (load && !currentlyUnloading) {
++ // CraftBukkit end
++ this.distanceManager.addTicket(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair);
++ if (this.chunkAbsent(playerchunk, l)) {
++ ProfilerFiller gameprofilerfiller = this.level.getProfiler();
+
+ profilerfiller.push("chunkLoad");
+ this.runDistanceManagerUpdates();
+@@ -249,8 +264,8 @@
+ return this.chunkAbsent(chunkholder, l) ? ChunkHolder.UNLOADED_CHUNK_FUTURE : chunkholder.getOrScheduleFuture(chunkstatus, this.chunkMap);
+ }
+
+- private boolean chunkAbsent(@Nullable ChunkHolder chunkholder, int i) {
+- return chunkholder == null || chunkholder.getTicketLevel() > i;
++ private boolean chunkAbsent(@Nullable ChunkHolder chunkHolder, int status) {
++ return chunkHolder == null || chunkHolder.oldTicketLevel > status; // CraftBukkit using oldTicketLevel for isLoaded checks
+ }
+
+ @Override
+@@ -335,11 +346,31 @@
+ @Override
+ @Override
+ public void close() throws IOException {
+- this.save(true);
++ // CraftBukkit start
++ close(true);
++ }
++
++ public void close(boolean save) throws IOException {
++ if (save) {
++ this.save(true);
++ }
++ // CraftBukkit end
+ this.lightEngine.close();
+ this.chunkMap.close();
+ }
+
++ // CraftBukkit start - modelled on below
++ public void purgeUnload() {
++ this.level.getProfiler().push("purge");
++ this.distanceManager.purgeStaleTickets();
++ this.runDistanceManagerUpdates();
++ this.level.getProfiler().popPush("unload");
++ this.chunkMap.tick(() -> true);
++ this.level.getProfiler().pop();
++ this.clearCache();
++ }
++ // CraftBukkit end
++
+ @Override
+ @Override
+ public void tick(BooleanSupplier booleansupplier, boolean flag) {
+@@ -385,13 +415,13 @@
+ int k = this.distanceManager.getNaturalSpawnChunkCount();
+ NaturalSpawner.SpawnState naturalspawner_spawnstate = NaturalSpawner.createState(k, this.level.getAllEntities(), this::getFullChunk, new LocalMobCapCalculator(this.chunkMap));
+
+- this.lastSpawnState = naturalspawner_spawnstate;
+- profilerfiller.popPush("spawnAndTick");
+- boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING);
++ this.lastSpawnState = spawnercreature_d;
++ gameprofilerfiller.popPush("spawnAndTick");
++ boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit
+
+ Util.shuffle(list, this.level.random);
+ int l = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING);
+- boolean flag1 = this.level.getLevelData().getGameTime() % 400L == 0L;
++ boolean flag1 = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit
+ Iterator iterator1 = list.iterator();
+
+ while (iterator1.hasNext()) {
+@@ -604,7 +624,9 @@
+ }
+
+ @Override
+- protected boolean pollTask() {
++ // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task
++ public boolean pollTask() {
++ try {
+ if (ServerChunkCache.this.runDistanceManagerUpdates()) {
+ return true;
+ } else {
+@@ -612,6 +636,8 @@
+ return super.pollTask();
+ }
+ }
++ // CraftBukkit end
++ }
+ }
+
+ private static record ChunkAndHolder(LevelChunk chunk, ChunkHolder holder) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ServerEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ServerEntity.java.patch
new file mode 100644
index 0000000000..fc4abd4089
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ServerEntity.java.patch
@@ -0,0 +1,146 @@
+--- a/net/minecraft/server/level/ServerEntity.java
++++ b/net/minecraft/server/level/ServerEntity.java
+@@ -42,6 +41,13 @@
+ import net.minecraft.world.phys.Vec3;
+ import org.slf4j.Logger;
+
++// CraftBukkit start
++import net.minecraft.server.network.ServerPlayerConnection;
++import net.minecraft.util.Mth;
++import org.bukkit.entity.Player;
++import org.bukkit.event.player.PlayerVelocityEvent;
++// CraftBukkit end
++
+ public class ServerEntity {
+
+ private static final Logger LOGGER = LogUtils.getLogger();
+@@ -66,8 +72,12 @@
+ private boolean wasOnGround;
+ @Nullable
+ private List<SynchedEntityData.DataValue<?>> trackedDataValues;
++ // CraftBukkit start
++ private final Set<ServerPlayerConnection> trackedPlayers;
+
+- public ServerEntity(ServerLevel serverlevel, Entity entity, int i, boolean flag, Consumer<Packet<?>> consumer) {
++ public ServerEntity(ServerLevel worldserver, Entity entity, int i, boolean flag, Consumer<Packet<?>> consumer, Set<ServerPlayerConnection> trackedPlayers) {
++ this.trackedPlayers = trackedPlayers;
++ // CraftBukkit end
+ this.ap = Vec3.ZERO;
+ this.lastPassengers = Collections.emptyList();
+ this.level = serverlevel;
+@@ -87,7 +97,7 @@
+ List<Entity> list = this.entity.getPassengers();
+
+ if (!list.equals(this.lastPassengers)) {
+- this.broadcast.accept(new ClientboundSetPassengersPacket(this.entity));
++ this.broadcastAndSend(new ClientboundSetPassengersPacket(this.entity)); // CraftBukkit
+ removedPassengers(list, this.lastPassengers).forEach((entity) -> {
+ if (entity instanceof ServerPlayer) {
+ ServerPlayer serverplayer = (ServerPlayer) entity;
+@@ -104,18 +114,18 @@
+ if (entity instanceof ItemFrame) {
+ ItemFrame itemframe = (ItemFrame) entity;
+
+- if (this.tickCount % 10 == 0) {
+- ItemStack itemstack = itemframe.getItem();
++ if (true || this.tickCount % 10 == 0) { // CraftBukkit - Moved below, should always enter this block
++ ItemStack itemstack = entityitemframe.getItem();
+
+- if (itemstack.getItem() instanceof MapItem) {
++ if (this.tickCount % 10 == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks
+ Integer integer = MapItem.getMapId(itemstack);
+ MapItemSavedData mapitemsaveddata = MapItem.getSavedData(integer, this.level);
+
+- if (mapitemsaveddata != null) {
+- Iterator iterator = this.level.players().iterator();
++ if (worldmap != null) {
++ Iterator<ServerPlayerConnection> iterator = this.trackedPlayers.iterator(); // CraftBukkit
+
+ while (iterator.hasNext()) {
+- ServerPlayer serverplayer = (ServerPlayer) iterator.next();
++ ServerPlayer entityplayer = iterator.next().getPlayer(); // CraftBukkit
+
+ mapitemsaveddata.tickCarriedBy(serverplayer, itemstack);
+ Packet<?> packet = mapitemsaveddata.getUpdatePacket(integer, serverplayer);
+@@ -228,7 +238,27 @@
+
+ ++this.tickCount;
+ if (this.entity.hurtMarked) {
+- this.broadcastAndSend(new ClientboundSetEntityMotionPacket(this.entity));
++ // CraftBukkit start - Create PlayerVelocity event
++ boolean cancelled = false;
++
++ if (this.entity instanceof ServerPlayer) {
++ Player player = (Player) this.entity.getBukkitEntity();
++ org.bukkit.util.Vector velocity = player.getVelocity();
++
++ PlayerVelocityEvent event = new PlayerVelocityEvent(player, velocity.clone());
++ this.entity.level().getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ cancelled = true;
++ } else if (!velocity.equals(event.getVelocity())) {
++ player.setVelocity(event.getVelocity());
++ }
++ }
++
++ if (!cancelled) {
++ this.broadcastAndSend(new ClientboundSetEntityMotionPacket(this.entity));
++ }
++ // CraftBukkit end
+ this.entity.hurtMarked = false;
+ }
+
+@@ -256,7 +286,10 @@
+
+ public void sendPairingData(ServerPlayer serverplayer, Consumer<Packet<ClientGamePacketListener>> consumer) {
+ if (this.entity.isRemoved()) {
+- ServerEntity.LOGGER.warn("Fetching packet for removed entity {}", this.entity);
++ // CraftBukkit start - Remove useless error spam, just return
++ // EntityTrackerEntry.LOGGER.warn("Fetching packet for removed entity {}", this.entity);
++ return;
++ // CraftBukkit end
+ }
+
+ Packet<ClientGamePacketListener> packet = this.entity.getAddEntityPacket();
+@@ -272,6 +305,12 @@
+ if (this.entity instanceof LivingEntity) {
+ Collection<AttributeInstance> collection = ((LivingEntity) this.entity).getAttributes().getSyncableAttributes();
+
++ // CraftBukkit start - If sending own attributes send scaled health instead of current maximum health
++ if (this.entity.getId() == player.getId()) {
++ ((ServerPlayer) this.entity).getBukkitEntity().injectScaledMaxHealth(collection, false);
++ }
++ // CraftBukkit end
++
+ if (!collection.isEmpty()) {
+ consumer.accept(new ClientboundUpdateAttributesPacket(this.entity.getId(), collection));
+ }
+@@ -303,8 +342,15 @@
+ if (!list.isEmpty()) {
+ consumer.accept(new ClientboundSetEquipmentPacket(this.entity.getId(), list));
+ }
++ ((LivingEntity) this.entity).detectEquipmentUpdatesPublic(); // CraftBukkit - SPIGOT-3789: sync again immediately after sending
+ }
+
++ // CraftBukkit start - MC-109346: Fix for nonsensical head yaw
++ if (this.entity instanceof ServerPlayer) {
++ consumer.accept(new ClientboundRotateHeadPacket(this.entity, (byte) Mth.floor(this.entity.getYHeadRot() * 256.0F / 360.0F)));
++ }
++ // CraftBukkit end
++
+ if (!this.entity.getPassengers().isEmpty()) {
+ consumer.accept(new ClientboundSetPassengersPacket(this.entity));
+ }
+@@ -338,6 +384,11 @@
+ Set<AttributeInstance> set = ((LivingEntity) this.entity).getAttributes().getDirtyAttributes();
+
+ if (!set.isEmpty()) {
++ // CraftBukkit start - Send scaled max health
++ if (this.entity instanceof ServerPlayer) {
++ ((ServerPlayer) this.entity).getBukkitEntity().injectScaledMaxHealth(set, false);
++ }
++ // CraftBukkit end
+ this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), set));
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ServerLevel.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ServerLevel.java.patch
new file mode 100644
index 0000000000..754c0d02de
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ServerLevel.java.patch
@@ -0,0 +1,657 @@
+--- a/net/minecraft/server/level/ServerLevel.java
++++ b/net/minecraft/server/level/ServerLevel.java
+@@ -163,6 +164,19 @@
+ import net.minecraft.world.phys.shapes.VoxelShape;
+ import net.minecraft.world.ticks.LevelTicks;
+ import org.slf4j.Logger;
++import org.bukkit.Bukkit;
++import org.bukkit.Location;
++import org.bukkit.WeatherType;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.craftbukkit.generator.CustomWorldChunkManager;
++import org.bukkit.craftbukkit.util.CraftNamespacedKey;
++import org.bukkit.craftbukkit.util.WorldUUID;
++import org.bukkit.event.entity.CreatureSpawnEvent;
++import org.bukkit.event.server.MapInitializeEvent;
++import org.bukkit.event.weather.LightningStrikeEvent;
++import org.bukkit.event.world.GenericGameEvent;
++import org.bukkit.event.world.TimeSkipEvent;
++// CraftBukkit end
+
+ public class ServerLevel extends Level implements WorldGenLevel {
+
+@@ -177,7 +191,7 @@
+ final List<ServerPlayer> players;
+ private final ServerChunkCache chunkSource;
+ private final MinecraftServer server;
+- private final ServerLevelData serverLevelData;
++ public final PrimaryLevelData serverLevelData; // CraftBukkit - type
+ final EntityTickList entityTickList;
+ private final PersistentEntitySectionManager<Entity> entityManager;
+ private final GameEventDispatcher gameEventDispatcher;
+@@ -202,12 +216,30 @@
+ private final boolean tickTime;
+ private final RandomSequences randomSequences;
+
+- public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess levelstoragesource_levelstorageaccess, ServerLevelData serverleveldata, ResourceKey<Level> resourcekey, LevelStem levelstem, ChunkProgressListener chunkprogresslistener, boolean flag, long i, List<CustomSpawner> list, boolean flag1, @Nullable RandomSequences randomsequences) {
+- RegistryAccess.Frozen registryaccess_frozen = minecraftserver.registryAccess();
+- Holder holder = levelstem.type();
++ // CraftBukkit start
++ public final LevelStorageSource.LevelStorageAccess convertable;
++ public final UUID uuid;
+
+- Objects.requireNonNull(minecraftserver);
+- super(serverleveldata, resourcekey, registryaccess_frozen, holder, minecraftserver::getProfiler, false, flag, i, minecraftserver.getMaxChainedNeighborUpdates());
++ public LevelChunk getChunkIfLoaded(int x, int z) {
++ return this.chunkSource.getChunk(x, z, false);
++ }
++
++ @Override
++ public ResourceKey<LevelStem> getTypeKey() {
++ return convertable.dimensionType;
++ }
++
++ // Add env and gen to constructor, IWorldDataServer -> WorldDataServer
++ public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, PrimaryLevelData iworlddataserver, ResourceKey<Level> resourcekey, LevelStem worlddimension, ChunkProgressListener worldloadlistener, boolean flag, long i, List<CustomSpawner> list, boolean flag1, @Nullable RandomSequences randomsequences, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) {
++ // IRegistryCustom.Dimension iregistrycustom_dimension = minecraftserver.registryAccess(); // CraftBukkit - decompile error
++ // Holder holder = worlddimension.type(); // CraftBukkit - decompile error
++
++ // Objects.requireNonNull(minecraftserver); // CraftBukkit - decompile error
++ super(iworlddataserver, resourcekey, minecraftserver.registryAccess(), worlddimension.type(), minecraftserver::getProfiler, false, flag, i, minecraftserver.getMaxChainedNeighborUpdates(), gen, biomeProvider, env);
++ this.pvpMode = minecraftserver.isPvpAllowed();
++ convertable = convertable_conversionsession;
++ uuid = WorldUUID.getUUID(convertable_conversionsession.levelDirectory.path().toFile());
++ // CraftBukkit end
+ this.players = Lists.newArrayList();
+ this.entityTickList = new EntityTickList();
+ this.blockTicks = new LevelTicks<>(this::isPositionTickingWithEntitiesLoaded, this.getProfilerSupplier());
+@@ -219,8 +251,24 @@
+ this.tickTime = flag1;
+ this.server = minecraftserver;
+ this.customSpawners = list;
+- this.serverLevelData = serverleveldata;
+- ChunkGenerator chunkgenerator = levelstem.generator();
++ this.serverLevelData = iworlddataserver;
++ ChunkGenerator chunkgenerator = worlddimension.generator();
++ // CraftBukkit start
++ serverLevelData.setWorld(this);
++
++ if (biomeProvider != null) {
++ BiomeSource worldChunkManager = new CustomWorldChunkManager(getWorld(), biomeProvider, server.registryAccess().registryOrThrow(Registries.BIOME));
++ if (chunkgenerator instanceof NoiseBasedChunkGenerator cga) {
++ chunkgenerator = new NoiseBasedChunkGenerator(worldChunkManager, cga.settings);
++ } else if (chunkgenerator instanceof FlatLevelSource cpf) {
++ chunkgenerator = new FlatLevelSource(cpf.settings(), worldChunkManager);
++ }
++ }
++
++ if (gen != null) {
++ chunkgenerator = new org.bukkit.craftbukkit.generator.CustomChunkGenerator(this, chunkgenerator, gen);
++ }
++ // CraftBukkit end
+ boolean flag2 = minecraftserver.forceSynchronousWrites();
+ DataFixer datafixer = minecraftserver.getFixerUpper();
+ EntityPersistentStorage<Entity> entitypersistentstorage = new EntityStorage(this, levelstoragesource_levelstorageaccess.getDimensionPath(resourcekey).resolve("entities"), datafixer, flag2, minecraftserver);
+@@ -248,9 +296,9 @@
+ long l = minecraftserver.getWorldData().worldGenOptions().seed();
+
+ this.structureCheck = new StructureCheck(this.chunkSource.chunkScanner(), this.registryAccess(), minecraftserver.getStructureManager(), resourcekey, chunkgenerator, this.chunkSource.randomState(), this, chunkgenerator.getBiomeSource(), l, datafixer);
+- this.structureManager = new StructureManager(this, minecraftserver.getWorldData().worldGenOptions(), this.structureCheck);
+- if (this.dimension() == Level.END && this.dimensionTypeRegistration().is(BuiltinDimensionTypes.END)) {
+- this.dragonFight = new EndDragonFight(this, l, minecraftserver.getWorldData().endDragonFightData());
++ this.structureManager = new StructureManager(this, this.serverLevelData.worldGenOptions(), structureCheck); // CraftBukkit
++ if ((this.dimension() == Level.END && this.dimensionTypeRegistration().is(BuiltinDimensionTypes.END)) || env == org.bukkit.World.Environment.THE_END) { // CraftBukkit - Allow to create EnderDragonBattle in default and custom END
++ this.dragonFight = new EndDragonFight(this, this.serverLevelData.worldGenOptions().seed(), this.serverLevelData.endDragonFightData()); // CraftBukkit
+ } else {
+ this.dragonFight = null;
+ }
+@@ -260,6 +308,7 @@
+ this.randomSequences = (RandomSequences) Objects.requireNonNullElseGet(randomsequences, () -> {
+ return (RandomSequences) this.getDataStorage().computeIfAbsent(RandomSequences.factory(l), "random_sequences");
+ });
++ this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit
+ }
+
+ /** @deprecated */
+@@ -305,12 +353,18 @@
+ long j;
+
+ if (this.sleepStatus.areEnoughSleeping(i) && this.sleepStatus.areEnoughDeepSleeping(i, this.players)) {
++ // CraftBukkit start
++ j = this.levelData.getDayTime() + 24000L;
++ TimeSkipEvent event = new TimeSkipEvent(this.getWorld(), TimeSkipEvent.SkipReason.NIGHT_SKIP, (j - j % 24000L) - this.getDayTime());
+ if (this.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) {
+ j = this.levelData.getDayTime() + 24000L;
+ this.setDayTime(j - j % 24000L);
+ }
+
+- this.wakeUpAllPlayers();
++ if (!event.isCancelled()) {
++ this.wakeUpAllPlayers();
++ }
++ // CraftBukkit end
+ if (this.getGameRules().getBoolean(GameRules.RULE_WEATHER_CYCLE) && this.isRaining()) {
+ this.resetWeatherCycle();
+ }
+@@ -344,8 +400,8 @@
+ }
+
+ this.handlingTick = false;
+- profilerfiller.pop();
+- boolean flag1 = !this.players.isEmpty() || !this.getForcedChunks().isEmpty();
++ gameprofilerfiller.pop();
++ boolean flag1 = true || !this.players.isEmpty() || !this.getForcedChunks().isEmpty(); // CraftBukkit - this prevents entity cleanup, other issues on servers with no players
+
+ if (flag1) {
+ this.resetEmptyTime();
+@@ -361,7 +417,7 @@
+
+ this.entityTickList.forEach((entity) -> {
+ if (!entity.isRemoved()) {
+- if (this.shouldDiscardEntity(entity)) {
++ if (false && this.shouldDiscardEntity(entity)) { // CraftBukkit - We prevent spawning in general, so this butchering is not needed
+ entity.discard();
+ } else if (!tickratemanager.isEntityFrozen(entity)) {
+ profilerfiller.push("checkDespawn");
+@@ -457,20 +512,20 @@
+ if (flag1) {
+ SkeletonHorse skeletonhorse = (SkeletonHorse) EntityType.SKELETON_HORSE.create(this);
+
+- if (skeletonhorse != null) {
+- skeletonhorse.setTrap(true);
+- skeletonhorse.setAge(0);
+- skeletonhorse.setPos((double) blockpos.getX(), (double) blockpos.getY(), (double) blockpos.getZ());
+- this.addFreshEntity(skeletonhorse);
++ if (entityhorseskeleton != null) {
++ entityhorseskeleton.setTrap(true);
++ entityhorseskeleton.setAge(0);
++ entityhorseskeleton.setPos((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ());
++ this.addFreshEntity(entityhorseskeleton, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING); // CraftBukkit
+ }
+ }
+
+ LightningBolt lightningbolt = (LightningBolt) EntityType.LIGHTNING_BOLT.create(this);
+
+- if (lightningbolt != null) {
+- lightningbolt.moveTo(Vec3.atBottomCenterOf(blockpos));
+- lightningbolt.setVisualOnly(flag1);
+- this.addFreshEntity(lightningbolt);
++ if (entitylightning != null) {
++ entitylightning.moveTo(Vec3.atBottomCenterOf(blockposition));
++ entitylightning.setVisualOnly(flag1);
++ this.strikeLightning(entitylightning, org.bukkit.event.weather.LightningStrikeEvent.Cause.WEATHER); // CraftBukkit
+ }
+ }
+ }
+@@ -525,8 +580,8 @@
+ BlockPos blockpos2 = blockpos1.below();
+ Biome biome = (Biome) this.getBiome(blockpos1).value();
+
+- if (biome.shouldFreeze(this, blockpos2)) {
+- this.setBlockAndUpdate(blockpos2, Blocks.ICE.defaultBlockState());
++ if (biomebase.shouldFreeze(this, blockposition2)) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition2, Blocks.ICE.defaultBlockState(), null); // CraftBukkit
+ }
+
+ if (this.isRaining()) {
+@@ -541,11 +596,11 @@
+ if (j < Math.min(i, 8)) {
+ BlockState blockstate1 = (BlockState) blockstate.setValue(SnowLayerBlock.LAYERS, j + 1);
+
+- Block.pushEntitiesUp(blockstate, blockstate1, this, blockpos1);
+- this.setBlockAndUpdate(blockpos1, blockstate1);
++ Block.pushEntitiesUp(iblockdata, iblockdata1, this, blockposition1);
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition1, iblockdata1, null); // CraftBukkit
+ }
+ } else {
+- this.setBlockAndUpdate(blockpos1, Blocks.SNOW.defaultBlockState());
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition1, Blocks.SNOW.defaultBlockState(), null); // CraftBukkit
+ }
+ }
+
+@@ -707,6 +761,7 @@
+ this.rainLevel = Mth.clamp(this.rainLevel, 0.0F, 1.0F);
+ }
+
++ /* CraftBukkit start
+ if (this.oRainLevel != this.rainLevel) {
+ this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, this.rainLevel), this.dimension());
+ }
+@@ -726,14 +787,41 @@
+ this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, this.thunderLevel));
+ }
+
++ if (flag != this.isRaining()) {
++ // Only send weather packets to those affected
++ for (int idx = 0; idx < this.players.size(); ++idx) {
++ if (((ServerPlayer) this.players.get(idx)).level() == this) {
++ ((ServerPlayer) this.players.get(idx)).setPlayerWeather((!flag ? WeatherType.DOWNFALL : WeatherType.CLEAR), false);
++ }
++ }
++ }
++ for (int idx = 0; idx < this.players.size(); ++idx) {
++ if (((ServerPlayer) this.players.get(idx)).level() == this) {
++ ((ServerPlayer) this.players.get(idx)).updateWeather(this.oRainLevel, this.rainLevel, this.oThunderLevel, this.thunderLevel);
++ }
++ }
++ // CraftBukkit end
++
+ }
+
+ @VisibleForTesting
+ public void resetWeatherCycle() {
+- this.serverLevelData.setRainTime(0);
++ // CraftBukkit start
+ this.serverLevelData.setRaining(false);
+- this.serverLevelData.setThunderTime(0);
++ // If we stop due to everyone sleeping we should reset the weather duration to some other random value.
++ // Not that everyone ever manages to get the whole server to sleep at the same time....
++ if (!this.serverLevelData.isRaining()) {
++ this.serverLevelData.setRainTime(0);
++ }
++ // CraftBukkit end
+ this.serverLevelData.setThundering(false);
++ // CraftBukkit start
++ // If we stop due to everyone sleeping we should reset the weather duration to some other random value.
++ // Not that everyone ever manages to get the whole server to sleep at the same time....
++ if (!this.serverLevelData.isThundering()) {
++ this.serverLevelData.setThunderTime(0);
++ }
++ // CraftBukkit end
+ }
+
+ public void resetEmptyTime() {
+@@ -768,6 +856,7 @@
+ });
+ profilerfiller.incrementCounter("tickNonPassenger");
+ entity.tick();
++ entity.postTick(); // CraftBukkit
+ this.getProfiler().pop();
+ Iterator iterator = entity.getPassengers().iterator();
+
+@@ -789,10 +878,11 @@
+ profilerfiller.push(() -> {
+ return BuiltInRegistries.ENTITY_TYPE.getKey(entity1.getType()).toString();
+ });
+- profilerfiller.incrementCounter("tickPassenger");
+- entity1.rideTick();
+- profilerfiller.pop();
+- Iterator iterator = entity1.getPassengers().iterator();
++ gameprofilerfiller.incrementCounter("tickPassenger");
++ passengerEntity.rideTick();
++ passengerEntity.postTick(); // CraftBukkit
++ gameprofilerfiller.pop();
++ Iterator iterator = passengerEntity.getPassengers().iterator();
+
+ while (iterator.hasNext()) {
+ Entity entity2 = (Entity) iterator.next();
+@@ -815,9 +904,10 @@
+ public void save(@Nullable ProgressListener progresslistener, boolean flag, boolean flag1) {
+ ServerChunkCache serverchunkcache = this.getChunkSource();
+
+- if (!flag1) {
+- if (progresslistener != null) {
+- progresslistener.progressStartNoAbort(Component.translatable("menu.savingLevel"));
++ if (!skipSave) {
++ org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld())); // CraftBukkit
++ if (progress != null) {
++ progress.progressStartNoAbort(Component.translatable("menu.savingLevel"));
+ }
+
+ this.saveLevelData();
+@@ -833,11 +923,19 @@
+ }
+
+ }
++
++ // CraftBukkit start - moved from MinecraftServer.saveChunks
++ ServerLevel worldserver1 = this;
++
++ serverLevelData.setWorldBorder(worldserver1.getWorldBorder().createSettings());
++ serverLevelData.setCustomBossEvents(this.server.getCustomBossEvents().save());
++ convertable.saveDataTag(this.server.registryAccess(), this.serverLevelData, this.server.getPlayerList().getSingleplayerData());
++ // CraftBukkit end
+ }
+
+ private void saveLevelData() {
+ if (this.dragonFight != null) {
+- this.server.getWorldData().setEndDragonFightData(this.dragonFight.saveData());
++ this.serverLevelData.setEndDragonFightData(this.dragonFight.saveData()); // CraftBukkit
+ }
+
+ this.getChunkSource().getDataStorage().save();
+@@ -903,19 +1000,37 @@
+ @Override
+ @Override
+ public boolean addFreshEntity(Entity entity) {
+- return this.addEntity(entity);
++ // CraftBukkit start
++ return this.addFreshEntity(entity, CreatureSpawnEvent.SpawnReason.DEFAULT);
+ }
+
++ @Override
++ public boolean addFreshEntity(Entity entity, CreatureSpawnEvent.SpawnReason reason) {
++ return this.addEntity(entity, reason);
++ // CraftBukkit end
++ }
++
+ public boolean addWithUUID(Entity entity) {
+- return this.addEntity(entity);
++ // CraftBukkit start
++ return this.addWithUUID(entity, CreatureSpawnEvent.SpawnReason.DEFAULT);
+ }
+
++ public boolean addWithUUID(Entity entity, CreatureSpawnEvent.SpawnReason reason) {
++ return this.addEntity(entity, reason);
++ // CraftBukkit end
++ }
++
+ public void addDuringTeleport(Entity entity) {
+- this.addEntity(entity);
++ // CraftBukkit start
++ // SPIGOT-6415: Don't call spawn event for entities which travel trough worlds,
++ // since it is only an implementation detail, that a new entity is created when
++ // they are traveling between worlds.
++ this.addDuringTeleport(entity, null);
+ }
+
+- public void addDuringCommandTeleport(ServerPlayer serverplayer) {
+- this.addPlayer(serverplayer);
++ public void addDuringTeleport(Entity entity, CreatureSpawnEvent.SpawnReason reason) {
++ this.addEntity(entity, reason);
++ // CraftBukkit end
+ }
+
+ public void addDuringPortalTeleport(ServerPlayer serverplayer) {
+@@ -942,24 +1061,37 @@
+ this.entityManager.addNewEntity(serverplayer);
+ }
+
+- private boolean addEntity(Entity entity) {
++ // CraftBukkit start
++ private boolean addEntity(Entity entity, CreatureSpawnEvent.SpawnReason spawnReason) {
+ if (entity.isRemoved()) {
+- ServerLevel.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityType.getKey(entity.getType()));
++ // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getKey(entity.getType())); // CraftBukkit
+ return false;
+ } else {
++ // SPIGOT-6415: Don't call spawn event when reason is null. For example when an entity teleports to a new world.
++ if (spawnReason != null && !CraftEventFactory.doEntityAddEventCalling(this, entity, spawnReason)) {
++ return false;
++ }
++ // CraftBukkit end
++
+ return this.entityManager.addNewEntity(entity);
+ }
+ }
+
+ public boolean tryAddFreshEntityWithPassengers(Entity entity) {
+- Stream stream = entity.getSelfAndPassengers().map(Entity::getUUID);
++ // CraftBukkit start
++ return this.tryAddFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
++ }
++
++ public boolean tryAddFreshEntityWithPassengers(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
++ // CraftBukkit end
++ Stream<UUID> stream = entity.getSelfAndPassengers().map(Entity::getUUID); // CraftBukkit - decompile error
+ PersistentEntitySectionManager persistententitysectionmanager = this.entityManager;
+
+ Objects.requireNonNull(this.entityManager);
+ if (stream.anyMatch(persistententitysectionmanager::isLoaded)) {
+ return false;
+ } else {
+- this.addFreshEntityWithPassengers(entity);
++ this.addFreshEntityWithPassengers(entity, reason); // CraftBukkit
+ return true;
+ }
+ }
+@@ -973,11 +1105,33 @@
+ serverplayer.remove(entity_removalreason);
+ }
+
++ // CraftBukkit start
++ public boolean strikeLightning(Entity entitylightning) {
++ return this.strikeLightning(entitylightning, LightningStrikeEvent.Cause.UNKNOWN);
++ }
++
++ public boolean strikeLightning(Entity entitylightning, LightningStrikeEvent.Cause cause) {
++ LightningStrikeEvent lightning = CraftEventFactory.callLightningStrikeEvent((org.bukkit.entity.LightningStrike) entitylightning.getBukkitEntity(), cause);
++
++ if (lightning.isCancelled()) {
++ return false;
++ }
++
++ return this.addFreshEntity(entitylightning);
++ }
++ // CraftBukkit end
++
+ @Override
+ @Override
+ public void destroyBlockProgress(int i, BlockPos blockpos, int j) {
+ Iterator iterator = this.server.getPlayerList().getPlayers().iterator();
+
++ // CraftBukkit start
++ Player entityhuman = null;
++ Entity entity = this.getEntity(breakerId);
++ if (entity instanceof Player) entityhuman = (Player) entity;
++ // CraftBukkit end
++
+ while (iterator.hasNext()) {
+ ServerPlayer serverplayer = (ServerPlayer) iterator.next();
+
+@@ -986,6 +1139,12 @@
+ double d1 = (double) blockpos.getY() - serverplayer.getY();
+ double d2 = (double) blockpos.getZ() - serverplayer.getZ();
+
++ // CraftBukkit start
++ if (entityhuman != null && !entityplayer.getBukkitEntity().canSee(entityhuman.getBukkitEntity())) {
++ continue;
++ }
++ // CraftBukkit end
++
+ if (d0 * d0 + d1 * d1 + d2 * d2 < 1024.0D) {
+ serverplayer.connection.send(new ClientboundBlockDestructionPacket(i, blockpos, j));
+ }
+@@ -1051,8 +1204,19 @@
+ Iterator iterator = this.navigatingMobs.iterator();
+
+ while (iterator.hasNext()) {
+- Mob mob = (Mob) iterator.next();
+- PathNavigation pathnavigation = mob.getNavigation();
++ // CraftBukkit start - fix SPIGOT-6362
++ Mob entityinsentient;
++ try {
++ entityinsentient = (Mob) iterator.next();
++ } catch (java.util.ConcurrentModificationException ex) {
++ // This can happen because the pathfinder update below may trigger a chunk load, which in turn may cause more navigators to register
++ // In this case we just run the update again across all the iterators as the chunk will then be loaded
++ // As this is a relative edge case it is much faster than copying navigators (on either read or write)
++ sendBlockUpdated(pos, oldState, newState, flags);
++ return;
++ }
++ // CraftBukkit end
++ PathNavigation navigationabstract = entityinsentient.getNavigation();
+
+ if (pathnavigation.shouldRecomputePath(blockpos)) {
+ list.add(pathnavigation);
+@@ -1118,9 +1275,13 @@
+ }
+
+ @Override
+- @Override
+- public Explosion explode(@Nullable Entity entity, @Nullable DamageSource damagesource, @Nullable ExplosionDamageCalculator explosiondamagecalculator, double d0, double d1, double d2, float f, boolean flag, Level.ExplosionInteraction level_explosioninteraction, ParticleOptions particleoptions, ParticleOptions particleoptions1, SoundEvent soundevent) {
+- Explosion explosion = this.explode(entity, damagesource, explosiondamagecalculator, d0, d1, d2, f, flag, level_explosioninteraction, false, particleoptions, particleoptions1, soundevent);
++ public Explosion explode(@Nullable Entity entity, @Nullable DamageSource damagesource, @Nullable ExplosionDamageCalculator explosiondamagecalculator, double d0, double d1, double d2, float f, boolean flag, Level.a world_a, ParticleOptions particleparam, ParticleOptions particleparam1, SoundEvent soundeffect) {
++ Explosion explosion = this.explode(entity, damagesource, explosiondamagecalculator, d0, d1, d2, f, flag, world_a, false, particleparam, particleparam1, soundeffect);
++ // CraftBukkit start
++ if (explosion.wasCanceled) {
++ return explosion;
++ }
++ // CraftBukkit end
+
+ if (!explosion.interactsWithBlocks()) {
+ explosion.clearToBlow();
+@@ -1196,14 +1353,21 @@
+ return this.server.getStructureManager();
+ }
+
+- public <T extends ParticleOptions> int sendParticles(T t0, double d0, double d1, double d2, int i, double d3, double d4, double d5, double d6) {
+- ClientboundLevelParticlesPacket clientboundlevelparticlespacket = new ClientboundLevelParticlesPacket(t0, false, d0, d1, d2, (float) d3, (float) d4, (float) d5, (float) d6, i);
++ public <T extends ParticleOptions> int sendParticles(T type, double posX, double d1, double posY, int i, double posZ, double d4, double particleCount, double xOffset) {
++ // CraftBukkit - visibility api support
++ return sendParticles(null, type, posX, d1, posY, i, posZ, d4, particleCount, xOffset, false);
++ }
++
++ public <T extends ParticleOptions> int sendParticles(ServerPlayer sender, T t0, double d0, double d1, double d2, int i, double d3, double d4, double d5, double d6, boolean force) {
++ ClientboundLevelParticlesPacket packetplayoutworldparticles = new ClientboundLevelParticlesPacket(t0, force, d0, d1, d2, (float) d3, (float) d4, (float) d5, (float) d6, i);
++ // CraftBukkit end
+ int j = 0;
+
+ for (int k = 0; k < this.players.size(); ++k) {
+- ServerPlayer serverplayer = (ServerPlayer) this.players.get(k);
++ ServerPlayer entityplayer = (ServerPlayer) this.players.get(k);
++ if (sender != null && !entityplayer.getBukkitEntity().canSee(sender.getBukkitEntity())) continue; // CraftBukkit
+
+- if (this.sendParticles(serverplayer, false, d0, d1, d2, clientboundlevelparticlespacket)) {
++ if (this.sendParticles(entityplayer, force, d0, d1, d2, packetplayoutworldparticles)) { // CraftBukkit
+ ++j;
+ }
+ }
+@@ -1254,8 +1417,8 @@
+ }
+
+ @Nullable
+- public BlockPos findNearestMapStructure(TagKey<Structure> tagkey, BlockPos blockpos, int i, boolean flag) {
+- if (!this.server.getWorldData().worldGenOptions().generateStructures()) {
++ public BlockPos findNearestMapStructure(TagKey<Structure> structureTag, BlockPos pos, int radius, boolean skipExistingChunks) {
++ if (!this.serverLevelData.worldGenOptions().generateStructures()) { // CraftBukkit
+ return null;
+ } else {
+ Optional<HolderSet.Named<Structure>> optional = this.registryAccess().registryOrThrow(Registries.STRUCTURE).getTag(tagkey);
+@@ -1299,15 +1459,24 @@
+
+ @Nullable
+ @Override
+- @Override
+- public MapItemSavedData getMapData(String s) {
+- return (MapItemSavedData) this.getServer().overworld().getDataStorage().get(MapItemSavedData.factory(), s);
++ public MapItemSavedData getMapData(String mapName) {
++ // CraftBukkit start
++ MapItemSavedData worldmap = (MapItemSavedData) this.getServer().overworld().getDataStorage().get(MapItemSavedData.factory(), mapName);
++ if (worldmap != null) {
++ worldmap.id = mapName;
++ }
++ return worldmap;
++ // CraftBukkit end
+ }
+
+ @Override
+- @Override
+- public void setMapData(String s, MapItemSavedData mapitemsaveddata) {
+- this.getServer().overworld().getDataStorage().set(s, mapitemsaveddata);
++ public void setMapData(String mapName, MapItemSavedData data) {
++ // CraftBukkit start
++ data.id = mapName;
++ MapInitializeEvent event = new MapInitializeEvent(data.mapView);
++ Bukkit.getServer().getPluginManager().callEvent(event);
++ // CraftBukkit end
++ this.getServer().overworld().getDataStorage().set(mapName, data);
+ }
+
+ @Override
+@@ -1608,7 +1773,12 @@
+ @Override
+ public void blockUpdated(BlockPos blockpos, Block block) {
+ if (!this.isDebug()) {
+- this.updateNeighborsAt(blockpos, block);
++ // CraftBukkit start
++ if (populating) {
++ return;
++ }
++ // CraftBukkit end
++ this.updateNeighborsAt(pos, block);
+ }
+
+ }
+@@ -1629,13 +1797,13 @@
+ }
+
+ public boolean isFlat() {
+- return this.server.getWorldData().isFlatWorld();
++ return this.serverLevelData.isFlatWorld(); // CraftBukkit
+ }
+
+ @Override
+ @Override
+ public long getSeed() {
+- return this.server.getWorldData().worldGenOptions().seed();
++ return this.serverLevelData.worldGenOptions().seed(); // CraftBukkit
+ }
+
+ @Nullable
+@@ -1678,18 +1844,34 @@
+ }
+ }
+
+- public static void makeObsidianPlatform(ServerLevel serverlevel) {
+- BlockPos blockpos = ServerLevel.END_SPAWN_POINT;
+- int i = blockpos.getX();
+- int j = blockpos.getY() - 2;
+- int k = blockpos.getZ();
++ public static void makeObsidianPlatform(ServerLevel serverLevel) {
++ // CraftBukkit start
++ ServerLevel.makeObsidianPlatform(serverLevel, null);
++ }
+
+- BlockPos.betweenClosed(i - 2, j + 1, k - 2, i + 2, j + 3, k + 2).forEach((blockpos1) -> {
+- serverlevel.setBlockAndUpdate(blockpos1, Blocks.AIR.defaultBlockState());
++ public static void makeObsidianPlatform(ServerLevel worldserver, Entity entity) {
++ // CraftBukkit end
++ BlockPos blockposition = ServerLevel.END_SPAWN_POINT;
++ int i = blockposition.getX();
++ int j = blockposition.getY() - 2;
++ int k = blockposition.getZ();
++
++ // CraftBukkit start
++ org.bukkit.craftbukkit.util.BlockStateListPopulator blockList = new org.bukkit.craftbukkit.util.BlockStateListPopulator(worldserver);
++ BlockPos.betweenClosed(i - 2, j + 1, k - 2, i + 2, j + 3, k + 2).forEach((blockposition1) -> {
++ blockList.setBlock(blockposition1, Blocks.AIR.defaultBlockState(), 3);
+ });
+ BlockPos.betweenClosed(i - 2, j, k - 2, i + 2, j, k + 2).forEach((blockpos1) -> {
+ serverlevel.setBlockAndUpdate(blockpos1, Blocks.OBSIDIAN.defaultBlockState());
+ });
++ org.bukkit.World bworld = worldserver.getWorld();
++ org.bukkit.event.world.PortalCreateEvent portalEvent = new org.bukkit.event.world.PortalCreateEvent((List<org.bukkit.block.BlockState>) (List) blockList.getList(), bworld, (entity == null) ? null : entity.getBukkitEntity(), org.bukkit.event.world.PortalCreateEvent.CreateReason.END_PLATFORM);
++
++ worldserver.getCraftServer().getPluginManager().callEvent(portalEvent);
++ if (!portalEvent.isCancelled()) {
++ blockList.updateList();
++ }
++ // CraftBukkit end
+ }
+
+ @Override
+@@ -1833,6 +2005,8 @@
+ }
+
+ entity.updateDynamicGameEventListener(DynamicGameEventListener::add);
++ entity.inWorld = true; // CraftBukkit - Mark entity as in world
++ entity.valid = true; // CraftBukkit
+ }
+
+ @Override
+@@ -1870,6 +2043,14 @@
+ }
+
+ entity.updateDynamicGameEventListener(DynamicGameEventListener::remove);
++ // CraftBukkit start
++ entity.valid = false;
++ if (!(entity instanceof ServerPlayer)) {
++ for (ServerPlayer player : players) {
++ player.getBukkitEntity().onEntityRemove(entity);
++ }
++ }
++ // CraftBukkit end
+ }
+
+ @Override
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ServerPlayer.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ServerPlayer.java.patch
new file mode 100644
index 0000000000..eb83e16f4a
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ServerPlayer.java.patch
@@ -0,0 +1,1159 @@
+--- a/net/minecraft/server/level/ServerPlayer.java
++++ b/net/minecraft/server/level/ServerPlayer.java
+@@ -155,7 +162,29 @@
+ import net.minecraft.world.scores.ScoreHolder;
+ import net.minecraft.world.scores.Team;
+ import net.minecraft.world.scores.criteria.ObjectiveCriteria;
+-import org.slf4j.Logger;
++import org.bukkit.Bukkit;
++import org.bukkit.Location;
++import org.bukkit.WeatherType;
++import org.bukkit.craftbukkit.CraftWorld;
++import org.bukkit.craftbukkit.CraftWorldBorder;
++import org.bukkit.craftbukkit.entity.CraftPlayer;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.craftbukkit.event.CraftPortalEvent;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.craftbukkit.util.CraftDimensionUtil;
++import org.bukkit.craftbukkit.util.CraftLocation;
++import org.bukkit.event.entity.EntityExhaustionEvent;
++import org.bukkit.event.player.PlayerBedLeaveEvent;
++import org.bukkit.event.player.PlayerChangedMainHandEvent;
++import org.bukkit.event.player.PlayerChangedWorldEvent;
++import org.bukkit.event.player.PlayerLocaleChangeEvent;
++import org.bukkit.event.player.PlayerPortalEvent;
++import org.bukkit.event.player.PlayerSpawnChangeEvent;
++import org.bukkit.event.player.PlayerTeleportEvent;
++import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
++import org.bukkit.event.player.PlayerToggleSneakEvent;
++import org.bukkit.inventory.MainHand;
++// CraftBukkit end
+
+ public class ServerPlayer extends Player {
+
+@@ -192,7 +221,7 @@
+ private int levitationStartTime;
+ private boolean disconnected;
+ private int requestedViewDistance;
+- private String language;
++ public String language = "en_us"; // CraftBukkit - default
+ @Nullable
+ private Vec3 startingToFallPosition;
+ @Nullable
+@@ -217,8 +246,22 @@
+ private int containerCounter;
+ public boolean wonGame;
+
+- public ServerPlayer(MinecraftServer minecraftserver, ServerLevel serverlevel, GameProfile gameprofile, ClientInformation clientinformation) {
+- super(serverlevel, serverlevel.getSharedSpawnPos(), serverlevel.getSharedSpawnAngle(), gameprofile);
++ // CraftBukkit start
++ public String displayName;
++ public Component listName;
++ public org.bukkit.Location compassTarget;
++ public int newExp = 0;
++ public int newLevel = 0;
++ public int newTotalExp = 0;
++ public boolean keepLevel = false;
++ public double maxHealthCache;
++ public boolean joining = true;
++ public boolean sentListPacket = false;
++ public String kickLeaveMessage = null; // SPIGOT-3034: Forward leave message to PlayerQuitEvent
++ // CraftBukkit end
++
++ public ServerPlayer(MinecraftServer minecraftserver, ServerLevel worldserver, GameProfile gameprofile, ClientInformation clientinformation) {
++ super(worldserver, worldserver.getSharedSpawnPos(), worldserver.getSharedSpawnAngle(), gameprofile);
+ this.chatVisibility = ChatVisiblity.FULL;
+ this.canChatColor = true;
+ this.lastActionTime = Util.getMillis();
+@@ -289,6 +326,11 @@
+ this.setMaxUpStep(1.0F);
+ this.fudgeSpawnLocation(serverlevel);
+ this.updateOptions(clientinformation);
++
++ // CraftBukkit start
++ this.displayName = this.getScoreboardName();
++ this.bukkitPickUpLoot = true;
++ this.maxHealthCache = this.getMaxHealth();
+ }
+
+ private void fudgeSpawnLocation(ServerLevel serverlevel) {
+@@ -318,9 +362,46 @@
+ int k2 = i2 / (i * 2 + 1);
+ BlockPos blockpos1 = PlayerRespawnLogic.getOverworldRespawnPos(serverlevel, blockpos.getX() + j2 - i, blockpos.getZ() + k2 - i);
+
+- if (blockpos1 != null) {
+- this.moveTo(blockpos1, 0.0F, 0.0F);
+- if (serverlevel.noCollision((Entity) this)) {
++ if (blockposition1 != null) {
++ return blockposition1;
++ }
++ }
++ }
++
++ return blockposition;
++ }
++ // CraftBukkit end
++
++ private void fudgeSpawnLocation(ServerLevel level) {
++ BlockPos blockposition = level.getSharedSpawnPos();
++
++ if (level.dimensionType().hasSkyLight() && level.serverLevelData.getGameType() != GameType.ADVENTURE) { // CraftBukkit
++ int i = Math.max(0, this.server.getSpawnRadius(level));
++ int j = Mth.floor(level.getWorldBorder().getDistanceToBorder((double) blockposition.getX(), (double) blockposition.getZ()));
++
++ if (j < i) {
++ i = j;
++ }
++
++ if (j <= 1) {
++ i = 1;
++ }
++
++ long k = (long) (i * 2 + 1);
++ long l = k * k;
++ int i1 = l > 2147483647L ? Integer.MAX_VALUE : (int) l;
++ int j1 = this.getCoprime(i1);
++ int k1 = RandomSource.create().nextInt(i1);
++
++ for (int l1 = 0; l1 < i1; ++l1) {
++ int i2 = (k1 + j1 * l1) % i1;
++ int j2 = i2 % (i * 2 + 1);
++ int k2 = i2 / (i * 2 + 1);
++ BlockPos blockposition1 = PlayerRespawnLogic.getOverworldRespawnPos(level, blockposition.getX() + j2 - i, blockposition.getZ() + k2 - i);
++
++ if (blockposition1 != null) {
++ this.moveTo(blockposition1, 0.0F, 0.0F);
++ if (level.noCollision((Entity) this)) {
+ break;
+ }
+ }
+@@ -363,17 +443,26 @@
+ if (compoundtag.contains("recipeBook", 10)) {
+ this.recipeBook.fromNbt(compoundtag.getCompound("recipeBook"), this.server.getRecipeManager());
+ }
++ this.getBukkitEntity().readExtraData(compound); // CraftBukkit
+
+ if (this.isSleeping()) {
+ this.stopSleeping();
+ }
+
+- if (compoundtag.contains("SpawnX", 99) && compoundtag.contains("SpawnY", 99) && compoundtag.contains("SpawnZ", 99)) {
+- this.respawnPosition = new BlockPos(compoundtag.getInt("SpawnX"), compoundtag.getInt("SpawnY"), compoundtag.getInt("SpawnZ"));
+- this.respawnForced = compoundtag.getBoolean("SpawnForced");
+- this.respawnAngle = compoundtag.getFloat("SpawnAngle");
+- if (compoundtag.contains("SpawnDimension")) {
+- DataResult dataresult1 = Level.RESOURCE_KEY_CODEC.parse(NbtOps.INSTANCE, compoundtag.get("SpawnDimension"));
++ // CraftBukkit start
++ String spawnWorld = compound.getString("SpawnWorld");
++ CraftWorld oldWorld = (CraftWorld) Bukkit.getWorld(spawnWorld);
++ if (oldWorld != null) {
++ this.respawnDimension = oldWorld.getHandle().dimension();
++ }
++ // CraftBukkit end
++
++ if (compound.contains("SpawnX", 99) && compound.contains("SpawnY", 99) && compound.contains("SpawnZ", 99)) {
++ this.respawnPosition = new BlockPos(compound.getInt("SpawnX"), compound.getInt("SpawnY"), compound.getInt("SpawnZ"));
++ this.respawnForced = compound.getBoolean("SpawnForced");
++ this.respawnAngle = compound.getFloat("SpawnAngle");
++ if (compound.contains("SpawnDimension")) {
++ DataResult<ResourceKey<Level>> dataresult1 = Level.RESOURCE_KEY_CODEC.parse(NbtOps.INSTANCE, compound.get("SpawnDimension")); // CraftBukkit - decompile error
+ Logger logger1 = ServerPlayer.LOGGER;
+
+ Objects.requireNonNull(logger1);
+@@ -408,14 +496,27 @@
+ Entity entity = this.getRootVehicle();
+ Entity entity1 = this.getVehicle();
+
+- if (entity1 != null && entity != this && entity.hasExactlyOnePlayerPassenger()) {
+- CompoundTag compoundtag2 = new CompoundTag();
+- CompoundTag compoundtag3 = new CompoundTag();
++ // CraftBukkit start - handle non-persistent vehicles
++ boolean persistVehicle = true;
++ if (entity1 != null) {
++ Entity vehicle;
++ for (vehicle = entity1; vehicle != null; vehicle = vehicle.getVehicle()) {
++ if (!vehicle.persist) {
++ persistVehicle = false;
++ break;
++ }
++ }
++ }
+
+- entity.save(compoundtag3);
+- compoundtag2.putUUID("Attach", entity1.getUUID());
+- compoundtag2.put("Entity", compoundtag3);
+- compoundtag.put("RootVehicle", compoundtag2);
++ if (persistVehicle && entity1 != null && entity != this && entity.hasExactlyOnePlayerPassenger()) {
++ // CraftBukkit end
++ CompoundTag nbttagcompound2 = new CompoundTag();
++ CompoundTag nbttagcompound3 = new CompoundTag();
++
++ entity.save(nbttagcompound3);
++ nbttagcompound2.putUUID("Attach", entity1.getUUID());
++ nbttagcompound2.put("Entity", nbttagcompound3);
++ compound.put("RootVehicle", nbttagcompound2);
+ }
+
+ compoundtag.put("recipeBook", this.recipeBook.toNbt());
+@@ -433,10 +534,34 @@
+ compoundtag.put("SpawnDimension", tag);
+ });
+ }
++ this.getBukkitEntity().setExtraData(compound); // CraftBukkit
+
+ }
+
+- public void setExperiencePoints(int i) {
++ // CraftBukkit start - World fallback code, either respawn location or global spawn
++ public void spawnIn(Level world) {
++ this.setLevel(world);
++ if (world == null) {
++ this.unsetRemoved();
++ Vec3 position = null;
++ if (this.respawnDimension != null) {
++ world = this.server.getLevel(this.respawnDimension);
++ if (world != null && this.getRespawnPosition() != null) {
++ position = Player.findRespawnPositionAndUseSpawnBlock((ServerLevel) world, this.getRespawnPosition(), this.getRespawnAngle(), false, false).orElse(null);
++ }
++ }
++ if (world == null || position == null) {
++ world = ((CraftWorld) Bukkit.getServer().getWorlds().get(0)).getHandle();
++ position = Vec3.atCenterOf(world.getSharedSpawnPos());
++ }
++ this.setLevel(world);
++ this.setPos(position);
++ }
++ this.gameMode.setLevel((ServerLevel) world);
++ }
++ // CraftBukkit end
++
++ public void setExperiencePoints(int experiencePoints) {
+ float f = (float) this.getXpNeededForNextLevel();
+ float f1 = (f - 1.0F) / f;
+
+@@ -501,6 +619,11 @@
+ @Override
+ @Override
+ public void tick() {
++ // CraftBukkit start
++ if (this.joining) {
++ this.joining = false;
++ }
++ // CraftBukkit end
+ this.gameMode.tick();
+ this.wardenSpawnTracker.tick();
+ --this.spawnInvulnerableTime;
+@@ -557,7 +680,7 @@
+ }
+
+ if (this.getHealth() != this.lastSentHealth || this.lastSentFood != this.foodData.getFoodLevel() || this.foodData.getSaturationLevel() == 0.0F != this.lastFoodSaturationZero) {
+- this.connection.send(new ClientboundSetHealthPacket(this.getHealth(), this.foodData.getFoodLevel(), this.foodData.getSaturationLevel()));
++ this.connection.send(new ClientboundSetHealthPacket(this.getBukkitEntity().getScaledHealth(), this.foodData.getFoodLevel(), this.foodData.getSaturationLevel())); // CraftBukkit
+ this.lastSentHealth = this.getHealth();
+ this.lastSentFood = this.foodData.getFoodLevel();
+ this.lastFoodSaturationZero = this.foodData.getSaturationLevel() == 0.0F;
+@@ -588,6 +711,12 @@
+ this.updateScoreForCriteria(ObjectiveCriteria.EXPERIENCE, Mth.ceil((float) this.lastRecordedExperience));
+ }
+
++ // CraftBukkit start - Force max health updates
++ if (this.maxHealthCache != this.getMaxHealth()) {
++ this.getBukkitEntity().updateScaledHealth();
++ }
++ // CraftBukkit end
++
+ if (this.experienceLevel != this.lastRecordedLevel) {
+ this.lastRecordedLevel = this.experienceLevel;
+ this.updateScoreForCriteria(ObjectiveCriteria.LEVEL, Mth.ceil((float) this.lastRecordedLevel));
+@@ -602,6 +731,20 @@
+ CriteriaTriggers.LOCATION.trigger(this);
+ }
+
++ // CraftBukkit start - initialize oldLevel, fire PlayerLevelChangeEvent, and tick client-sided world border
++ if (this.oldLevel == -1) {
++ this.oldLevel = this.experienceLevel;
++ }
++
++ if (this.oldLevel != this.experienceLevel) {
++ CraftEventFactory.callPlayerLevelChangeEvent(this.getBukkitEntity(), this.oldLevel, this.experienceLevel);
++ this.oldLevel = this.experienceLevel;
++ }
++
++ if (this.getBukkitEntity().hasClientWorldBorder()) {
++ ((CraftWorldBorder) this.getBukkitEntity().getWorldBorder()).getHandle().tick();
++ }
++ // CraftBukkit end
+ } catch (Throwable throwable) {
+ CrashReport crashreport = CrashReport.forThrowable(throwable, "Ticking player");
+ CrashReportCategory crashreportcategory = crashreport.addCategory("Player being ticked");
+@@ -644,9 +786,10 @@
+
+ }
+
+- private void updateScoreForCriteria(ObjectiveCriteria objectivecriteria, int i) {
+- this.getScoreboard().forAllObjectives(objectivecriteria, this, (scoreaccess) -> {
+- scoreaccess.set(i);
++ private void updateScoreForCriteria(ObjectiveCriteria criteria, int points) {
++ // CraftBukkit - Use our scores instead
++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(criteria, this, (scoreaccess) -> {
++ scoreaccess.set(points);
+ });
+ }
+
+@@ -655,6 +797,12 @@
+ public void die(DamageSource damagesource) {
+ this.gameEvent(GameEvent.ENTITY_DIE);
+ boolean flag = this.level().getGameRules().getBoolean(GameRules.RULE_SHOWDEATHMESSAGES);
++ // CraftBukkit start - fire PlayerDeathEvent
++ if (this.isRemoved()) {
++ return;
++ }
++ java.util.List<org.bukkit.inventory.ItemStack> loot = new java.util.ArrayList<org.bukkit.inventory.ItemStack>(this.getInventory().getContainerSize());
++ boolean keepInventory = this.level().getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) || this.isSpectator();
+
+ if (flag) {
+ Component component = this.getCombatTracker().getDeathMessage();
+@@ -693,13 +875,17 @@
+ this.dropAllDeathLoot(damagesource);
+ }
+
+- this.getScoreboard().forAllObjectives(ObjectiveCriteria.DEATH_COUNT, this, ScoreAccess::increment);
+- LivingEntity livingentity = this.getKillCredit();
++ this.setCamera(this); // Remove spectated target
++ // CraftBukkit end
+
+- if (livingentity != null) {
+- this.awardStat(Stats.ENTITY_KILLED_BY.get(livingentity.getType()));
+- livingentity.awardKillScore(this, this.deathScore, damagesource);
+- this.createWitherRose(livingentity);
++ // CraftBukkit - Get our scores instead
++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(ObjectiveCriteria.DEATH_COUNT, this, ScoreAccess::increment);
++ LivingEntity entityliving = this.getKillCredit();
++
++ if (entityliving != null) {
++ this.awardStat(Stats.ENTITY_KILLED_BY.get(entityliving.getType()));
++ entityliving.awardKillScore(this, this.deathScore, cause);
++ this.createWitherRose(entityliving);
+ }
+
+ this.level().broadcastEntityEvent(this, (byte) 3);
+@@ -724,15 +910,16 @@
+ }
+
+ @Override
+- @Override
+- public void awardKillScore(Entity entity, int i, DamageSource damagesource) {
+- if (entity != this) {
+- super.awardKillScore(entity, i, damagesource);
+- this.increaseScore(i);
+- this.getScoreboard().forAllObjectives(ObjectiveCriteria.KILL_COUNT_ALL, this, ScoreAccess::increment);
+- if (entity instanceof Player) {
++ public void awardKillScore(Entity killed, int scoreValue, DamageSource damageSource) {
++ if (killed != this) {
++ super.awardKillScore(killed, scoreValue, damageSource);
++ this.increaseScore(scoreValue);
++ // CraftBukkit - Get our scores instead
++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(ObjectiveCriteria.KILL_COUNT_ALL, this, ScoreAccess::increment);
++ if (killed instanceof Player) {
+ this.awardStat(Stats.PLAYER_KILLS);
+- this.getScoreboard().forAllObjectives(ObjectiveCriteria.KILL_COUNT_PLAYERS, this, ScoreAccess::increment);
++ // CraftBukkit - Get our scores instead
++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(ObjectiveCriteria.KILL_COUNT_PLAYERS, this, ScoreAccess::increment);
+ } else {
+ this.awardStat(Stats.MOB_KILLS);
+ }
+@@ -749,8 +936,9 @@
+ if (playerteam != null) {
+ int i = playerteam.getColor().getId();
+
+- if (i >= 0 && i < aobjectivecriteria.length) {
+- this.getScoreboard().forAllObjectives(aobjectivecriteria[i], scoreholder, ScoreAccess::increment);
++ if (i >= 0 && i < aiscoreboardcriteria.length) {
++ // CraftBukkit - Get our scores instead
++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(aiscoreboardcriteria[i], scoreholder, ScoreAccess::increment);
+ }
+ }
+
+@@ -802,19 +988,20 @@
+ }
+
+ private boolean isPvpAllowed() {
+- return this.server.isPvpAllowed();
++ // CraftBukkit - this.server.isPvpAllowed() -> this.world.pvpMode
++ return this.level().pvpMode;
+ }
+
+ @Nullable
+ @Override
+- @Override
+- protected PortalInfo findDimensionEntryPoint(ServerLevel serverlevel) {
+- PortalInfo portalinfo = super.findDimensionEntryPoint(serverlevel);
++ protected PortalInfo findDimensionEntryPoint(ServerLevel destination) {
++ PortalInfo shapedetectorshape = super.findDimensionEntryPoint(destination);
++ destination = (shapedetectorshape == null) ? destination : shapedetectorshape.world; // CraftBukkit
+
+- if (portalinfo != null && this.level().dimension() == Level.OVERWORLD && serverlevel.dimension() == Level.END) {
+- Vec3 vec3 = portalinfo.pos.add(0.0D, -1.0D, 0.0D);
++ if (shapedetectorshape != null && this.level().getTypeKey() == LevelStem.OVERWORLD && destination != null && destination.getTypeKey() == LevelStem.END) { // CraftBukkit
++ Vec3 vec3d = shapedetectorshape.pos.add(0.0D, -1.0D, 0.0D);
+
+- return new PortalInfo(vec3, Vec3.ZERO, 90.0F, 0.0F);
++ return new PortalInfo(vec3d, Vec3.ZERO, 90.0F, 0.0F, destination, shapedetectorshape.portalEventInfo); // CraftBukkit
+ } else {
+ return portalinfo;
+ }
+@@ -822,13 +1009,21 @@
+
+ @Nullable
+ @Override
+- @Override
+- public Entity changeDimension(ServerLevel serverlevel) {
+- this.isChangingDimension = true;
+- ServerLevel serverlevel1 = this.serverLevel();
+- ResourceKey<Level> resourcekey = serverlevel1.dimension();
++ public Entity changeDimension(ServerLevel server) {
++ // CraftBukkit start
++ return changeDimension(server, TeleportCause.UNKNOWN);
++ }
+
+- if (resourcekey == Level.END && serverlevel.dimension() == Level.OVERWORLD) {
++ @Nullable
++ public Entity changeDimension(ServerLevel worldserver, PlayerTeleportEvent.TeleportCause cause) {
++ // CraftBukkit end
++ if (this.isSleeping()) return this; // CraftBukkit - SPIGOT-3154
++ // this.isChangingDimension = true; // CraftBukkit - Moved down and into PlayerList#changeDimension
++ ServerLevel worldserver1 = this.serverLevel();
++ ResourceKey<LevelStem> resourcekey = worldserver1.getTypeKey(); // CraftBukkit
++
++ if (resourcekey == LevelStem.END && worldserver != null && worldserver.getTypeKey() == LevelStem.OVERWORLD) { // CraftBukkit
++ this.isChangingDimension = true; // CraftBukkit - Moved down from above
+ this.unRide();
+ this.serverLevel().removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION);
+ if (!this.wonGame) {
+@@ -839,7 +1034,9 @@
+
+ return this;
+ } else {
+- LevelData leveldata = serverlevel.getLevelData();
++ // CraftBukkit start
++ /*
++ WorldData worlddata = worldserver.getLevelData();
+
+ this.connection.send(new ClientboundRespawnPacket(this.createCommonSpawnInfo(serverlevel), (byte) 3));
+ this.connection.send(new ClientboundChangeDifficultyPacket(leveldata.getDifficulty(), leveldata.isDifficultyLocked()));
+@@ -848,20 +1045,50 @@
+ playerlist.sendPlayerPermissionLevel(this);
+ serverlevel1.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION);
+ this.unsetRemoved();
+- PortalInfo portalinfo = this.findDimensionEntryPoint(serverlevel);
++ */
++ // CraftBukkit end
++ PortalInfo shapedetectorshape = this.findDimensionEntryPoint(worldserver);
+
+- if (portalinfo != null) {
+- serverlevel1.getProfiler().push("moving");
+- if (resourcekey == Level.OVERWORLD && serverlevel.dimension() == Level.NETHER) {
++ if (shapedetectorshape != null) {
++ worldserver1.getProfiler().push("moving");
++ worldserver = shapedetectorshape.world; // CraftBukkit
++ if (worldserver == null) { } else // CraftBukkit - empty to fall through to null to event
++ if (resourcekey == LevelStem.OVERWORLD && worldserver.getTypeKey() == LevelStem.NETHER) { // CraftBukkit
+ this.enteredNetherPosition = this.position();
+- } else if (serverlevel.dimension() == Level.END) {
+- this.createEndPlatform(serverlevel, BlockPos.containing(portalinfo.pos));
++ } else if (worldserver.getTypeKey() == LevelStem.END && shapedetectorshape.portalEventInfo != null && shapedetectorshape.portalEventInfo.getCanCreatePortal()) { // CraftBukkit
++ this.createEndPlatform(worldserver, BlockPos.containing(shapedetectorshape.pos));
+ }
++ // CraftBukkit start
++ } else {
++ return null;
++ }
++ Location enter = this.getBukkitEntity().getLocation();
++ Location exit = (worldserver == null) ? null : CraftLocation.toBukkit(shapedetectorshape.pos, worldserver.getWorld(), shapedetectorshape.yRot, shapedetectorshape.xRot);
++ PlayerTeleportEvent tpEvent = new PlayerTeleportEvent(this.getBukkitEntity(), enter, exit, cause);
++ Bukkit.getServer().getPluginManager().callEvent(tpEvent);
++ if (tpEvent.isCancelled() || tpEvent.getTo() == null) {
++ return null;
++ }
++ exit = tpEvent.getTo();
++ worldserver = ((CraftWorld) exit.getWorld()).getHandle();
++ // CraftBukkit end
+
+- serverlevel1.getProfiler().pop();
+- serverlevel1.getProfiler().push("placing");
+- this.setServerLevel(serverlevel);
+- this.connection.teleport(portalinfo.pos.x, portalinfo.pos.y, portalinfo.pos.z, portalinfo.yRot, portalinfo.xRot);
++ worldserver1.getProfiler().pop();
++ worldserver1.getProfiler().push("placing");
++ if (true) { // CraftBukkit
++ this.isChangingDimension = true; // CraftBukkit - Set teleport invulnerability only if player changing worlds
++
++ this.connection.send(new ClientboundRespawnPacket(this.createCommonSpawnInfo(worldserver), (byte) 3));
++ this.connection.send(new ClientboundChangeDifficultyPacket(this.level().getDifficulty(), this.level().getLevelData().isDifficultyLocked()));
++ PlayerList playerlist = this.server.getPlayerList();
++
++ playerlist.sendPlayerPermissionLevel(this);
++ worldserver1.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION);
++ this.unsetRemoved();
++
++ // CraftBukkit end
++ this.setServerLevel(worldserver);
++ this.connection.teleport(exit); // CraftBukkit - use internal teleport without event
+ this.connection.resetPosition();
+ serverlevel.addDuringPortalTeleport(this);
+ serverlevel1.getProfiler().pop();
+@@ -881,40 +1108,66 @@
+ this.lastSentExp = -1;
+ this.lastSentHealth = -1.0F;
+ this.lastSentFood = -1;
++
++ // CraftBukkit start
++ PlayerChangedWorldEvent changeEvent = new PlayerChangedWorldEvent(this.getBukkitEntity(), worldserver1.getWorld());
++ this.level().getCraftServer().getPluginManager().callEvent(changeEvent);
++ // CraftBukkit end
+ }
+
+ return this;
+ }
+ }
+
+- private void createEndPlatform(ServerLevel serverlevel, BlockPos blockpos) {
+- BlockPos.MutableBlockPos blockpos_mutableblockpos = blockpos.mutable();
++ // CraftBukkit start
++ @Override
++ protected CraftPortalEvent callPortalEvent(Entity entity, ServerLevel exitWorldServer, Vec3 exitPosition, TeleportCause cause, int searchRadius, int creationRadius) {
++ Location enter = this.getBukkitEntity().getLocation();
++ Location exit = CraftLocation.toBukkit(exitPosition, exitWorldServer.getWorld(), getYRot(), getXRot());
++ PlayerPortalEvent event = new PlayerPortalEvent(this.getBukkitEntity(), enter, exit, cause, searchRadius, true, creationRadius);
++ Bukkit.getServer().getPluginManager().callEvent(event);
++ if (event.isCancelled() || event.getTo() == null || event.getTo().getWorld() == null) {
++ return null;
++ }
++ return new CraftPortalEvent(event);
++ }
++ // CraftBukkit end
+
++ private void createEndPlatform(ServerLevel level, BlockPos pos) {
++ BlockPos.MutableBlockPos blockposition_mutableblockposition = pos.mutable();
++ org.bukkit.craftbukkit.util.BlockStateListPopulator blockList = new org.bukkit.craftbukkit.util.BlockStateListPopulator(level); // CraftBukkit
++
+ for (int i = -2; i <= 2; ++i) {
+ for (int j = -2; j <= 2; ++j) {
+ for (int k = -1; k < 3; ++k) {
+ BlockState blockstate = k == -1 ? Blocks.OBSIDIAN.defaultBlockState() : Blocks.AIR.defaultBlockState();
+
+- serverlevel.setBlockAndUpdate(blockpos_mutableblockpos.set(blockpos).move(j, k, i), blockstate);
++ blockList.setBlock(blockposition_mutableblockposition.set(pos).move(j, k, i), iblockdata, 3); // CraftBukkit
+ }
+ }
+ }
++ // CraftBukkit start - call portal event
++ org.bukkit.event.world.PortalCreateEvent portalEvent = new org.bukkit.event.world.PortalCreateEvent((List<org.bukkit.block.BlockState>) (List) blockList.getList(), level.getWorld(), this.getBukkitEntity(), org.bukkit.event.world.PortalCreateEvent.CreateReason.END_PLATFORM);
++ level.getCraftServer().getPluginManager().callEvent(portalEvent);
++ if (!portalEvent.isCancelled()) {
++ blockList.updateList();
++ }
++ // CraftBukkit end
+
+ }
+
+ @Override
+- @Override
+- protected Optional<BlockUtil.FoundRectangle> getExitPortal(ServerLevel serverlevel, BlockPos blockpos, boolean flag, WorldBorder worldborder) {
+- Optional<BlockUtil.FoundRectangle> optional = super.getExitPortal(serverlevel, blockpos, flag, worldborder);
++ protected Optional<BlockUtil.FoundRectangle> getExitPortal(ServerLevel worldserver, BlockPos blockposition, boolean flag, WorldBorder worldborder, int searchRadius, boolean canCreatePortal, int createRadius) { // CraftBukkit
++ Optional<BlockUtil.FoundRectangle> optional = super.getExitPortal(worldserver, blockposition, flag, worldborder, searchRadius, canCreatePortal, createRadius); // CraftBukkit
+
+- if (optional.isPresent()) {
++ if (optional.isPresent() || !canCreatePortal) { // CraftBukkit
+ return optional;
+ } else {
+- Direction.Axis direction_axis = (Direction.Axis) this.level().getBlockState(this.portalEntrancePos).getOptionalValue(NetherPortalBlock.AXIS).orElse(Direction.Axis.X);
+- Optional<BlockUtil.FoundRectangle> optional1 = serverlevel.getPortalForcer().createPortal(blockpos, direction_axis);
++ Direction.Axis enumdirection_enumaxis = (Direction.Axis) this.level().getBlockState(this.portalEntrancePos).getOptionalValue(NetherPortalBlock.AXIS).orElse(Direction.Axis.X);
++ Optional<BlockUtil.FoundRectangle> optional1 = worldserver.getPortalForcer().createPortal(blockposition, enumdirection_enumaxis, this, createRadius); // CraftBukkit
+
+ if (optional1.isEmpty()) {
+- ServerPlayer.LOGGER.error("Unable to create a portal, likely target out of worldborder");
++ // EntityPlayer.LOGGER.error("Unable to create a portal, likely target out of worldborder"); // CraftBukkit
+ }
+
+ return optional1;
+@@ -924,13 +1177,21 @@
+ private void triggerDimensionChangeTriggers(ServerLevel serverlevel) {
+ ResourceKey<Level> resourcekey = serverlevel.dimension();
+ ResourceKey<Level> resourcekey1 = this.level().dimension();
++ // CraftBukkit start
++ ResourceKey<Level> maindimensionkey = CraftDimensionUtil.getMainDimensionKey(level);
++ ResourceKey<Level> maindimensionkey1 = CraftDimensionUtil.getMainDimensionKey(this.level());
+
+- CriteriaTriggers.CHANGED_DIMENSION.trigger(this, resourcekey, resourcekey1);
+- if (resourcekey == Level.NETHER && resourcekey1 == Level.OVERWORLD && this.enteredNetherPosition != null) {
++ CriteriaTriggers.CHANGED_DIMENSION.trigger(this, maindimensionkey, maindimensionkey1);
++ if (maindimensionkey != resourcekey || maindimensionkey1 != resourcekey1) {
++ CriteriaTriggers.CHANGED_DIMENSION.trigger(this, resourcekey, resourcekey1);
++ }
++
++ if (maindimensionkey == Level.NETHER && maindimensionkey1 == Level.OVERWORLD && this.enteredNetherPosition != null) {
++ // CraftBukkit end
+ CriteriaTriggers.NETHER_TRAVEL.trigger(this, this.enteredNetherPosition);
+ }
+
+- if (resourcekey1 != Level.NETHER) {
++ if (maindimensionkey1 != Level.NETHER) { // CraftBukkit
+ this.enteredNetherPosition = null;
+ }
+
+@@ -949,11 +1208,8 @@
+ this.containerMenu.broadcastChanges();
+ }
+
+- @Override
+- @Override
+- public Either<Player.BedSleepingProblem, Unit> startSleepInBed(BlockPos blockpos) {
+- Direction direction = (Direction) this.level().getBlockState(blockpos).getValue(HorizontalDirectionalBlock.FACING);
+-
++ // CraftBukkit start - moved bed result checks from below into separate method
++ private Either<Player.BedSleepingProblem, Unit> getBedResult(BlockPos blockposition, Direction enumdirection) {
+ if (!this.isSleeping() && this.isAlive()) {
+ if (!this.level().dimensionType().natural()) {
+ return Either.left(Player.BedSleepingProblem.NOT_POSSIBLE_HERE);
+@@ -962,7 +1218,7 @@
+ } else if (this.bedBlocked(blockpos, direction)) {
+ return Either.left(Player.BedSleepingProblem.OBSTRUCTED);
+ } else {
+- this.setRespawnPosition(this.level().dimension(), blockpos, this.getYRot(), false, true);
++ this.setRespawnPosition(this.level().dimension(), blockposition, this.getYRot(), false, true, PlayerSpawnChangeEvent.Cause.BED); // CraftBukkit
+ if (this.level().isDay()) {
+ return Either.left(Player.BedSleepingProblem.NOT_POSSIBLE_NOW);
+ } else {
+@@ -995,6 +1278,7 @@
+ } else {
+ return Either.left(Player.BedSleepingProblem.OTHER_PROBLEM);
+ }
++ // CraftBukkit end
+ }
+
+ @Override
+@@ -1021,15 +1304,32 @@
+ }
+
+ @Override
+- @Override
+- public void stopSleepInBed(boolean flag, boolean flag1) {
++ public void stopSleepInBed(boolean wakeImmediately, boolean updateLevelForSleepingPlayers) {
++ if (!this.isSleeping()) return; // CraftBukkit - Can't leave bed if not in one!
++ // CraftBukkit start - fire PlayerBedLeaveEvent
++ CraftPlayer player = this.getBukkitEntity();
++ BlockPos bedPosition = this.getSleepingPos().orElse(null);
++
++ org.bukkit.block.Block bed;
++ if (bedPosition != null) {
++ bed = this.level().getWorld().getBlockAt(bedPosition.getX(), bedPosition.getY(), bedPosition.getZ());
++ } else {
++ bed = this.level().getWorld().getBlockAt(player.getLocation());
++ }
++
++ PlayerBedLeaveEvent event = new PlayerBedLeaveEvent(player, bed, true);
++ this.level().getCraftServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
+ if (this.isSleeping()) {
+ this.serverLevel().getChunkSource().broadcastAndSend(this, new ClientboundAnimatePacket(this, 2));
+ }
+
+ super.stopSleepInBed(flag, flag1);
+ if (this.connection != null) {
+- this.connection.teleport(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot());
++ this.connection.teleport(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot(), TeleportCause.EXIT_BED); // CraftBukkit
+ }
+
+ }
+@@ -1085,8 +1379,9 @@
+ this.connection.send(new ClientboundOpenSignEditorPacket(signblockentity.getBlockPos(), flag));
+ }
+
+- private void nextContainerCounter() {
++ public int nextContainerCounter() { // CraftBukkit - void -> int
+ this.containerCounter = this.containerCounter % 100 + 1;
++ return containerCounter; // CraftBukkit
+ }
+
+ @Override
+@@ -1095,23 +1389,47 @@
+ if (menuprovider == null) {
+ return OptionalInt.empty();
+ } else {
++ // CraftBukkit start - SPIGOT-6552: Handle inventory closing in CraftEventFactory#callInventoryOpenEvent(...)
++ /*
+ if (this.containerMenu != this.inventoryMenu) {
+ this.closeContainer();
+ }
++ */
++ // CraftBukkit end
+
+ this.nextContainerCounter();
+ AbstractContainerMenu abstractcontainermenu = menuprovider.createMenu(this.containerCounter, this.getInventory(), this);
+
+- if (abstractcontainermenu == null) {
++ // CraftBukkit start - Inventory open hook
++ if (container != null) {
++ container.setTitle(menu.getDisplayName());
++
++ boolean cancelled = false;
++ container = CraftEventFactory.callInventoryOpenEvent(this, container, cancelled);
++ if (container == null && !cancelled) { // Let pre-cancelled events fall through
++ // SPIGOT-5263 - close chest if cancelled
++ if (menu instanceof Container) {
++ ((Container) menu).stopOpen(this);
++ } else if (menu instanceof ChestBlock.DoubleInventory) {
++ // SPIGOT-5355 - double chests too :(
++ ((ChestBlock.DoubleInventory) menu).inventorylargechest.stopOpen(this);
++ }
++ return OptionalInt.empty();
++ }
++ }
++ // CraftBukkit end
++ if (container == null) {
+ if (this.isSpectator()) {
+ this.displayClientMessage(Component.translatable("container.spectatorCantOpen").withStyle(ChatFormatting.RED), true);
+ }
+
+ return OptionalInt.empty();
+ } else {
+- this.connection.send(new ClientboundOpenScreenPacket(abstractcontainermenu.containerId, abstractcontainermenu.getType(), menuprovider.getDisplayName()));
+- this.initMenu(abstractcontainermenu);
+- this.containerMenu = abstractcontainermenu;
++ // CraftBukkit start
++ this.containerMenu = container;
++ this.connection.send(new ClientboundOpenScreenPacket(container.containerId, container.getType(), container.getTitle()));
++ // CraftBukkit end
++ this.initMenu(container);
+ return OptionalInt.of(this.containerCounter);
+ }
+ }
+@@ -1124,15 +1441,25 @@
+ }
+
+ @Override
+- @Override
+- public void openHorseInventory(AbstractHorse abstracthorse, Container container) {
++ public void openHorseInventory(AbstractHorse horse, Container inventory) {
++ // CraftBukkit start - Inventory open hook
++ this.nextContainerCounter();
++ AbstractContainerMenu container = new HorseInventoryMenu(this.containerCounter, this.getInventory(), inventory, horse);
++ container.setTitle(horse.getDisplayName());
++ container = CraftEventFactory.callInventoryOpenEvent(this, container);
++
++ if (container == null) {
++ inventory.stopOpen(this);
++ return;
++ }
++ // CraftBukkit end
+ if (this.containerMenu != this.inventoryMenu) {
+ this.closeContainer();
+ }
+
+- this.nextContainerCounter();
+- this.connection.send(new ClientboundHorseScreenOpenPacket(this.containerCounter, container.getContainerSize(), abstracthorse.getId()));
+- this.containerMenu = new HorseInventoryMenu(this.containerCounter, this.getInventory(), container, abstracthorse);
++ // this.nextContainerCounter(); // CraftBukkit - moved up
++ this.connection.send(new ClientboundHorseScreenOpenPacket(this.containerCounter, inventory.getContainerSize(), horse.getId()));
++ this.containerMenu = container; // CraftBukkit
+ this.initMenu(this.containerMenu);
+ }
+
+@@ -1158,6 +1482,7 @@
+ @Override
+ @Override
+ public void closeContainer() {
++ CraftEventFactory.handleInventoryCloseEvent(this); // CraftBukkit
+ this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId));
+ this.doCloseContainer();
+ }
+@@ -1180,8 +1504,18 @@
+ this.zza = f1;
+ }
+
+- this.jumping = flag;
+- this.setShiftKeyDown(flag1);
++ this.jumping = jumping;
++ // CraftBukkit start
++ if (sneaking != this.isShiftKeyDown()) {
++ PlayerToggleSneakEvent event = new PlayerToggleSneakEvent(this.getBukkitEntity(), sneaking);
++ this.server.server.getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return;
++ }
++ }
++ // CraftBukkit end
++ this.setShiftKeyDown(sneaking);
+ }
+
+ }
+@@ -1216,19 +1548,19 @@
+ i = Math.round((float) Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2) * 100.0F);
+ if (i > 0) {
+ this.awardStat(Stats.SWIM_ONE_CM, i);
+- this.causeFoodExhaustion(0.01F * (float) i * 0.01F);
++ this.causeFoodExhaustion(0.01F * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.SWIM); // CraftBukkit - EntityExhaustionEvent
+ }
+ } else if (this.isEyeInFluid(FluidTags.WATER)) {
+ i = Math.round((float) Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2) * 100.0F);
+ if (i > 0) {
+ this.awardStat(Stats.WALK_UNDER_WATER_ONE_CM, i);
+- this.causeFoodExhaustion(0.01F * (float) i * 0.01F);
++ this.causeFoodExhaustion(0.01F * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.WALK_UNDERWATER); // CraftBukkit - EntityExhaustionEvent
+ }
+ } else if (this.isInWater()) {
+ i = Math.round((float) Math.sqrt(d0 * d0 + d2 * d2) * 100.0F);
+ if (i > 0) {
+ this.awardStat(Stats.WALK_ON_WATER_ONE_CM, i);
+- this.causeFoodExhaustion(0.01F * (float) i * 0.01F);
++ this.causeFoodExhaustion(0.01F * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.WALK_ON_WATER); // CraftBukkit - EntityExhaustionEvent
+ }
+ } else if (this.onClimbable()) {
+ if (d1 > 0.0D) {
+@@ -1239,13 +1571,13 @@
+ if (i > 0) {
+ if (this.isSprinting()) {
+ this.awardStat(Stats.SPRINT_ONE_CM, i);
+- this.causeFoodExhaustion(0.1F * (float) i * 0.01F);
++ this.causeFoodExhaustion(0.1F * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.SPRINT); // CraftBukkit - EntityExhaustionEvent
+ } else if (this.isCrouching()) {
+ this.awardStat(Stats.CROUCH_ONE_CM, i);
+- this.causeFoodExhaustion(0.0F * (float) i * 0.01F);
++ this.causeFoodExhaustion(0.0F * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.CROUCH); // CraftBukkit - EntityExhaustionEvent
+ } else {
+ this.awardStat(Stats.WALK_ONE_CM, i);
+- this.causeFoodExhaustion(0.0F * (float) i * 0.01F);
++ this.causeFoodExhaustion(0.0F * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.WALK); // CraftBukkit - EntityExhaustionEvent
+ }
+ }
+ } else if (this.isFallFlying()) {
+@@ -1298,7 +1628,7 @@
+ @Override
+ public void resetStat(Stat<?> stat) {
+ this.stats.setValue(this, stat, 0);
+- this.getScoreboard().forAllObjectives(stat, this, ScoreAccess::reset);
++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(stat, this, ScoreAccess::reset); // CraftBukkit - Get our scores instead
+ }
+
+ @Override
+@@ -1351,6 +1676,7 @@
+
+ public void resetSentInfo() {
+ this.lastSentHealth = -1.0E8F;
++ this.lastSentExp = -1; // CraftBukkit - Added to reset
+ }
+
+ @Override
+@@ -1411,13 +1734,13 @@
+ this.lastSentExp = -1;
+ this.lastSentHealth = -1.0F;
+ this.lastSentFood = -1;
+- this.recipeBook.copyOverData(serverplayer.recipeBook);
+- this.seenCredits = serverplayer.seenCredits;
+- this.enteredNetherPosition = serverplayer.enteredNetherPosition;
+- this.chunkTrackingView = serverplayer.chunkTrackingView;
+- this.setShoulderEntityLeft(serverplayer.getShoulderEntityLeft());
+- this.setShoulderEntityRight(serverplayer.getShoulderEntityRight());
+- this.setLastDeathLocation(serverplayer.getLastDeathLocation());
++ // this.recipeBook.copyOverData(entityplayer.recipeBook); // CraftBukkit
++ this.seenCredits = that.seenCredits;
++ this.enteredNetherPosition = that.enteredNetherPosition;
++ this.chunkTrackingView = that.chunkTrackingView;
++ this.setShoulderEntityLeft(that.getShoulderEntityLeft());
++ this.setShoulderEntityRight(that.getShoulderEntityRight());
++ this.setLastDeathLocation(that.getLastDeathLocation());
+ }
+
+ @Override
+@@ -1466,20 +1784,25 @@
+ }
+
+ @Override
+- @Override
+- public boolean teleportTo(ServerLevel serverlevel, double d0, double d1, double d2, Set<RelativeMovement> set, float f, float f1) {
+- ChunkPos chunkpos = new ChunkPos(BlockPos.containing(d0, d1, d2));
++ public boolean teleportTo(ServerLevel level, double x, double d1, double y, Set<RelativeMovement> set, float z, float f1) {
++ // CraftBukkit start
++ return teleportTo(level, x, d1, y, set, z, f1, TeleportCause.UNKNOWN);
++ }
+
+- serverlevel.getChunkSource().addRegionTicket(TicketType.POST_TELEPORT, chunkpos, 1, this.getId());
++ public boolean teleportTo(ServerLevel worldserver, double d0, double d1, double d2, Set<RelativeMovement> set, float f, float f1, TeleportCause cause) {
++ // CraftBukkit end
++ ChunkPos chunkcoordintpair = new ChunkPos(BlockPos.containing(d0, d1, d2));
++
++ worldserver.getChunkSource().addRegionTicket(TicketType.POST_TELEPORT, chunkcoordintpair, 1, this.getId());
+ this.stopRiding();
+ if (this.isSleeping()) {
+ this.stopSleepInBed(true, true);
+ }
+
+- if (serverlevel == this.level()) {
+- this.connection.teleport(d0, d1, d2, f, f1, set);
++ if (worldserver == this.level()) {
++ this.connection.teleport(d0, d1, d2, f, f1, set, cause); // CraftBukkit
+ } else {
+- this.teleportTo(serverlevel, d0, d1, d2, f, f1);
++ this.teleportTo(worldserver, d0, d1, d2, f, f1, cause); // CraftBukkit
+ }
+
+ this.setYHeadRot(f);
+@@ -1590,6 +1906,16 @@
+ }
+
+ public void updateOptions(ClientInformation clientinformation) {
++ // CraftBukkit start
++ if (getMainArm() != clientinformation.mainHand()) {
++ PlayerChangedMainHandEvent event = new PlayerChangedMainHandEvent(getBukkitEntity(), getMainArm() == HumanoidArm.LEFT ? MainHand.LEFT : MainHand.RIGHT);
++ this.server.server.getPluginManager().callEvent(event);
++ }
++ if (!this.language.equals(clientinformation.language())) {
++ PlayerLocaleChangeEvent event = new PlayerLocaleChangeEvent(getBukkitEntity(), clientinformation.language());
++ this.server.server.getPluginManager().callEvent(event);
++ }
++ // CraftBukkit end
+ this.language = clientinformation.language();
+ this.requestedViewDistance = clientinformation.viewDistance();
+ this.chatVisibility = clientinformation.chatVisibility();
+@@ -1675,7 +1999,7 @@
+ if (level instanceof ServerLevel) {
+ ServerLevel serverlevel = (ServerLevel) level;
+
+- this.teleportTo(serverlevel, this.camera.getX(), this.camera.getY(), this.camera.getZ(), Set.of(), this.getYRot(), this.getXRot());
++ this.teleportTo(worldserver, this.camera.getX(), this.camera.getY(), this.camera.getZ(), Set.of(), this.getYRot(), this.getXRot(), TeleportCause.SPECTATE); // CraftBukkit
+ }
+
+ if (entity != null) {
+@@ -1714,7 +2036,7 @@
+
+ @Nullable
+ public Component getTabListDisplayName() {
+- return null;
++ return listName; // CraftBukkit
+ }
+
+ @Override
+@@ -1736,10 +2057,17 @@
+ return this.advancements;
+ }
+
+- public void teleportTo(ServerLevel serverlevel, double d0, double d1, double d2, float f, float f1) {
++ // CraftBukkit start
++ public void teleportTo(ServerLevel newLevel, double x, double d1, double y, float f, float z) {
++ this.teleportTo(newLevel, x, d1, y, f, z, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.UNKNOWN);
++ }
++
++ public void teleportTo(ServerLevel worldserver, double d0, double d1, double d2, float f, float f1, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) {
++ // CraftBukkit end
+ this.setCamera(this);
+ this.stopRiding();
+- if (serverlevel == this.level()) {
++ /* CraftBukkit start - replace with bukkit handling for multi-world
++ if (worldserver == this.level()) {
+ this.connection.teleport(d0, d1, d2, f, f1);
+ } else {
+ ServerLevel serverlevel1 = this.serverLevel();
+@@ -1758,6 +2086,9 @@
+ this.server.getPlayerList().sendLevelInfo(this, serverlevel);
+ this.server.getPlayerList().sendAllPlayerInfo(this);
+ }
++ */
++ this.getBukkitEntity().teleport(new Location(worldserver.getWorld(), d0, d1, d2, f, f1), cause);
++ // CraftBukkit end
+
+ }
+
+@@ -1778,10 +2109,36 @@
+ return this.respawnForced;
+ }
+
+- public void setRespawnPosition(ResourceKey<Level> resourcekey, @Nullable BlockPos blockpos, float f, boolean flag, boolean flag1) {
+- if (blockpos != null) {
+- boolean flag2 = blockpos.equals(this.respawnPosition) && resourcekey.equals(this.respawnDimension);
++ public void setRespawnPosition(ResourceKey<Level> dimension, @Nullable BlockPos position, float angle, boolean forced, boolean sendMessage) {
++ // CraftBukkit start
++ this.setRespawnPosition(dimension, position, angle, forced, sendMessage, PlayerSpawnChangeEvent.Cause.UNKNOWN);
++ }
+
++ public void setRespawnPosition(ResourceKey<Level> resourcekey, @Nullable BlockPos blockposition, float f, boolean flag, boolean flag1, PlayerSpawnChangeEvent.Cause cause) {
++ ServerLevel newWorld = this.server.getLevel(resourcekey);
++ Location newSpawn = (blockposition != null) ? CraftLocation.toBukkit(blockposition, newWorld.getWorld(), f, 0) : null;
++
++ PlayerSpawnChangeEvent event = new PlayerSpawnChangeEvent(this.getBukkitEntity(), newSpawn, flag, cause);
++ Bukkit.getServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ return;
++ }
++ newSpawn = event.getNewSpawn();
++ flag = event.isForced();
++
++ if (newSpawn != null) {
++ resourcekey = ((CraftWorld) newSpawn.getWorld()).getHandle().dimension();
++ blockposition = BlockPos.containing(newSpawn.getX(), newSpawn.getY(), newSpawn.getZ());
++ f = newSpawn.getYaw();
++ } else {
++ resourcekey = Level.OVERWORLD;
++ blockposition = null;
++ f = 0.0F;
++ }
++ // CraftBukkit end
++ if (blockposition != null) {
++ boolean flag2 = blockposition.equals(this.respawnPosition) && resourcekey.equals(this.respawnDimension);
++
+ if (flag1 && !flag2) {
+ this.sendSystemMessage(Component.translatable("block.minecraft.set_spawn"));
+ }
+@@ -1994,4 +2341,146 @@
+ public CommonPlayerSpawnInfo createCommonSpawnInfo(ServerLevel serverlevel) {
+ return new CommonPlayerSpawnInfo(serverlevel.dimensionTypeId(), serverlevel.dimension(), BiomeManager.obfuscateSeed(serverlevel.getSeed()), this.gameMode.getGameModeForPlayer(), this.gameMode.getPreviousGameModeForPlayer(), serverlevel.isDebug(), serverlevel.isFlat(), this.getLastDeathLocation(), this.getPortalCooldown());
+ }
++
++ // CraftBukkit start - Add per-player time and weather.
++ public long timeOffset = 0;
++ public boolean relativeTime = true;
++
++ public long getPlayerTime() {
++ if (this.relativeTime) {
++ // Adds timeOffset to the current server time.
++ return this.level().getDayTime() + this.timeOffset;
++ } else {
++ // Adds timeOffset to the beginning of this day.
++ return this.level().getDayTime() - (this.level().getDayTime() % 24000) + this.timeOffset;
++ }
++ }
++
++ public WeatherType weather = null;
++
++ public WeatherType getPlayerWeather() {
++ return this.weather;
++ }
++
++ public void setPlayerWeather(WeatherType type, boolean plugin) {
++ if (!plugin && this.weather != null) {
++ return;
++ }
++
++ if (plugin) {
++ this.weather = type;
++ }
++
++ if (type == WeatherType.DOWNFALL) {
++ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.STOP_RAINING, 0));
++ } else {
++ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0));
++ }
++ }
++
++ private float pluginRainPosition;
++ private float pluginRainPositionPrevious;
++
++ public void updateWeather(float oldRain, float newRain, float oldThunder, float newThunder) {
++ if (this.weather == null) {
++ // Vanilla
++ if (oldRain != newRain) {
++ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, newRain));
++ }
++ } else {
++ // Plugin
++ if (pluginRainPositionPrevious != pluginRainPosition) {
++ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, pluginRainPosition));
++ }
++ }
++
++ if (oldThunder != newThunder) {
++ if (weather == WeatherType.DOWNFALL || weather == null) {
++ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, newThunder));
++ } else {
++ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, 0));
++ }
++ }
++ }
++
++ public void tickWeather() {
++ if (this.weather == null) return;
++
++ pluginRainPositionPrevious = pluginRainPosition;
++ if (weather == WeatherType.DOWNFALL) {
++ pluginRainPosition += 0.01;
++ } else {
++ pluginRainPosition -= 0.01;
++ }
++
++ pluginRainPosition = Mth.clamp(pluginRainPosition, 0.0F, 1.0F);
++ }
++
++ public void resetPlayerWeather() {
++ this.weather = null;
++ this.setPlayerWeather(this.level().getLevelData().isRaining() ? WeatherType.DOWNFALL : WeatherType.CLEAR, false);
++ }
++
++ @Override
++ public String toString() {
++ return super.toString() + "(" + this.getScoreboardName() + " at " + this.getX() + "," + this.getY() + "," + this.getZ() + ")";
++ }
++
++ // SPIGOT-1903, MC-98153
++ public void forceSetPositionRotation(double x, double y, double z, float yaw, float pitch) {
++ this.moveTo(x, y, z, yaw, pitch);
++ this.connection.resetPosition();
++ }
++
++ @Override
++ public boolean isImmobile() {
++ return super.isImmobile() || !getBukkitEntity().isOnline();
++ }
++
++ @Override
++ public Scoreboard getScoreboard() {
++ return getBukkitEntity().getScoreboard().getHandle();
++ }
++
++ public void reset() {
++ float exp = 0;
++ boolean keepInventory = this.level().getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY);
++
++ if (this.keepLevel) { // CraftBukkit - SPIGOT-6687: Only use keepLevel (was pre-set with RULE_KEEPINVENTORY value in PlayerDeathEvent)
++ exp = this.experienceProgress;
++ this.newTotalExp = this.totalExperience;
++ this.newLevel = this.experienceLevel;
++ }
++
++ this.setHealth(this.getMaxHealth());
++ this.stopUsingItem(); // CraftBukkit - SPIGOT-6682: Clear active item on reset
++ this.setRemainingFireTicks(0);
++ this.fallDistance = 0;
++ this.foodData = new FoodData(this);
++ this.experienceLevel = this.newLevel;
++ this.totalExperience = this.newTotalExp;
++ this.experienceProgress = 0;
++ this.deathTime = 0;
++ this.setArrowCount(0, true); // CraftBukkit - ArrowBodyCountChangeEvent
++ this.removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.DEATH);
++ this.effectsDirty = true;
++ this.containerMenu = this.inventoryMenu;
++ this.lastHurtByPlayer = null;
++ this.lastHurtByMob = null;
++ this.combatTracker = new CombatTracker(this);
++ this.lastSentExp = -1;
++ if (this.keepLevel) { // CraftBukkit - SPIGOT-6687: Only use keepLevel (was pre-set with RULE_KEEPINVENTORY value in PlayerDeathEvent)
++ this.experienceProgress = exp;
++ } else {
++ this.giveExperiencePoints(this.newExp);
++ }
++ this.keepLevel = false;
++ this.setDeltaMovement(0, 0, 0); // CraftBukkit - SPIGOT-6948: Reset velocity on death
++ }
++
++ @Override
++ public CraftPlayer getBukkitEntity() {
++ return (CraftPlayer) super.getBukkitEntity();
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ServerPlayerGameMode.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ServerPlayerGameMode.java.patch
new file mode 100644
index 0000000000..bf951d1eea
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/ServerPlayerGameMode.java.patch
@@ -0,0 +1,334 @@
+--- a/net/minecraft/server/level/ServerPlayerGameMode.java
++++ b/net/minecraft/server/level/ServerPlayerGameMode.java
+@@ -21,10 +23,28 @@
+ import net.minecraft.world.level.block.Block;
+ import net.minecraft.world.level.block.GameMasterBlock;
+ import net.minecraft.world.level.block.entity.BlockEntity;
+-import net.minecraft.world.level.block.state.BlockState;
++import net.minecraft.world.level.block.state.IBlockData;
++import org.slf4j.Logger;
++
++// CraftBukkit start
++import java.util.ArrayList;
++import net.minecraft.server.MinecraftServer;
++import net.minecraft.server.network.ServerGamePacketListenerImpl;
++import net.minecraft.world.level.block.Blocks;
++import net.minecraft.world.level.block.CakeBlock;
++import net.minecraft.world.level.block.DoorBlock;
++import net.minecraft.world.level.block.state.properties.BlockPropertyDoubleBlockHalf;
+ import net.minecraft.world.phys.BlockHitResult;
+ import net.minecraft.world.phys.Vec3;
+-import org.slf4j.Logger;
++import org.bukkit.GameMode;
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.event.block.BlockBreakEvent;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.Event;
++import org.bukkit.event.block.Action;
++import org.bukkit.event.player.PlayerGameModeChangeEvent;
++import org.bukkit.event.player.PlayerInteractEvent;
++// CraftBukkit end
+
+ public class ServerPlayerGameMode {
+
+@@ -56,9 +76,16 @@
+ if (gametype == this.gameModeForPlayer) {
+ return false;
+ } else {
+- this.setGameModeForPlayer(gametype, this.previousGameModeForPlayer);
++ // CraftBukkit start
++ PlayerGameModeChangeEvent event = new PlayerGameModeChangeEvent(player.getBukkitEntity(), GameMode.getByValue(gameModeForPlayer.getId()));
++ level.getCraftServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ return false;
++ }
++ // CraftBukkit end
++ this.setGameModeForPlayer(gameModeForPlayer, this.previousGameModeForPlayer);
+ this.player.onUpdateAbilities();
+- this.player.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, this.player));
++ this.player.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, this.player), this.player); // CraftBukkit
+ this.level.updateSleepingPlayerList();
+ return true;
+ }
+@@ -88,8 +115,8 @@
+ }
+
+ public void tick() {
+- ++this.gameTicks;
+- BlockState blockstate;
++ this.gameTicks = MinecraftServer.currentTick; // CraftBukkit;
++ IBlockData iblockdata;
+
+ if (this.hasDelayedDestroy) {
+ blockstate = this.level.getBlockState(this.delayedDestroyPos);
+@@ -140,13 +167,35 @@
+ } else {
+ BlockState blockstate;
+
+- if (serverboundplayeractionpacket_action == ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK) {
+- if (!this.level.mayInteract(this.player, blockpos)) {
+- this.player.connection.send(new ClientboundBlockUpdatePacket(blockpos, this.level.getBlockState(blockpos)));
+- this.debugLogging(blockpos, false, j, "may not interact");
++ if (action == ServerboundPlayerActionPacket.EnumPlayerDigType.START_DESTROY_BLOCK) {
++ if (!this.level.mayInteract(this.player, pos)) {
++ // CraftBukkit start - fire PlayerInteractEvent
++ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, pos, face, this.player.getInventory().getSelected(), EnumHand.MAIN_HAND);
++ this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos)));
++ this.debugLogging(pos, false, sequence, "may not interact");
++ // Update any tile entity data for this block
++ BlockEntity tileentity = level.getBlockEntity(pos);
++ if (tileentity != null) {
++ this.player.connection.send(tileentity.getUpdatePacket());
++ }
++ // CraftBukkit end
+ return;
+ }
+
++ // CraftBukkit start
++ PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, pos, face, this.player.getInventory().getSelected(), EnumHand.MAIN_HAND);
++ if (event.isCancelled()) {
++ // Let the client know the block still exists
++ this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos));
++ // Update any tile entity data for this block
++ BlockEntity tileentity = this.level.getBlockEntity(pos);
++ if (tileentity != null) {
++ this.player.connection.send(tileentity.getUpdatePacket());
++ }
++ return;
++ }
++ // CraftBukkit end
++
+ if (this.isCreative()) {
+ this.destroyAndAck(blockpos, j, "creative destroy");
+ return;
+@@ -161,14 +210,46 @@
+ this.destroyProgressStart = this.gameTicks;
+ float f = 1.0F;
+
+- blockstate = this.level.getBlockState(blockpos);
+- if (!blockstate.isAir()) {
+- blockstate.attack(this.level, blockpos, this.player);
+- f = blockstate.getDestroyProgress(this.player, this.player.level(), blockpos);
++ iblockdata = this.level.getBlockState(pos);
++ // CraftBukkit start - Swings at air do *NOT* exist.
++ if (event.useInteractedBlock() == Event.Result.DENY) {
++ // If we denied a door from opening, we need to send a correcting update to the client, as it already opened the door.
++ IBlockData data = this.level.getBlockState(pos);
++ if (data.getBlock() instanceof DoorBlock) {
++ // For some reason *BOTH* the bottom/top part have to be marked updated.
++ boolean bottom = data.getValue(DoorBlock.HALF) == BlockPropertyDoubleBlockHalf.LOWER;
++ this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos));
++ this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, bottom ? pos.above() : pos.below()));
++ } else if (data.getBlock() instanceof TrapDoorBlock) {
++ this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos));
++ }
++ } else if (!iblockdata.isAir()) {
++ iblockdata.attack(this.level, pos, this.player);
++ f = iblockdata.getDestroyProgress(this.player, this.player.level(), pos);
+ }
+
+- if (!blockstate.isAir() && f >= 1.0F) {
+- this.destroyAndAck(blockpos, j, "insta mine");
++ if (event.useItemInHand() == Event.Result.DENY) {
++ // If we 'insta destroyed' then the client needs to be informed.
++ if (f > 1.0f) {
++ this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos));
++ }
++ return;
++ }
++ org.bukkit.event.block.BlockDamageEvent blockEvent = CraftEventFactory.callBlockDamageEvent(this.player, pos, this.player.getInventory().getSelected(), f >= 1.0f);
++
++ if (blockEvent.isCancelled()) {
++ // Let the client know the block still exists
++ this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos));
++ return;
++ }
++
++ if (blockEvent.getInstaBreak()) {
++ f = 2.0f;
++ }
++ // CraftBukkit end
++
++ if (!iblockdata.isAir() && f >= 1.0F) {
++ this.destroyAndAck(pos, sequence, "insta mine");
+ } else {
+ if (this.isDestroyingBlock) {
+ this.player.connection.send(new ClientboundBlockUpdatePacket(this.destroyPos, this.level.getBlockState(this.destroyPos)));
+@@ -210,14 +291,16 @@
+ this.debugLogging(blockpos, true, j, "stopped destroying");
+ } else if (serverboundplayeractionpacket_action == ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK) {
+ this.isDestroyingBlock = false;
+- if (!Objects.equals(this.destroyPos, blockpos)) {
+- ServerPlayerGameMode.LOGGER.warn("Mismatch in destroy block pos: {} {}", this.destroyPos, blockpos);
++ if (!Objects.equals(this.destroyPos, pos)) {
++ ServerPlayerGameMode.LOGGER.debug("Mismatch in destroy block pos: {} {}", this.destroyPos, pos); // CraftBukkit - SPIGOT-5457 sent by client when interact event cancelled
+ this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1);
+ this.debugLogging(blockpos, true, j, "aborted mismatched destroying");
+ }
+
+- this.level.destroyBlockProgress(this.player.getId(), blockpos, -1);
+- this.debugLogging(blockpos, true, j, "aborted destroying");
++ this.level.destroyBlockProgress(this.player.getId(), pos, -1);
++ this.debugLogging(pos, true, sequence, "aborted destroying");
++
++ CraftEventFactory.callBlockDamageAbortEvent(this.player, pos, this.player.getInventory().getSelected()); // CraftBukkit
+ }
+
+ }
+@@ -233,14 +316,69 @@
+
+ }
+
+- public boolean destroyBlock(BlockPos blockpos) {
+- BlockState blockstate = this.level.getBlockState(blockpos);
++ public boolean destroyBlock(BlockPos pos) {
++ IBlockData iblockdata = this.level.getBlockState(pos);
++ // CraftBukkit start - fire BlockBreakEvent
++ org.bukkit.block.Block bblock = CraftBlock.at(level, pos);
++ BlockBreakEvent event = null;
+
+- if (!this.player.getMainHandItem().getItem().canAttackBlock(blockstate, this.level, blockpos, this.player)) {
++ if (this.player instanceof ServerPlayer) {
++ // Sword + Creative mode pre-cancel
++ boolean isSwordNoBreak = !this.player.getMainHandItem().getItem().canAttackBlock(iblockdata, this.level, pos, this.player);
++
++ // Tell client the block is gone immediately then process events
++ // Don't tell the client if its a creative sword break because its not broken!
++ if (level.getBlockEntity(pos) == null && !isSwordNoBreak) {
++ ClientboundBlockUpdatePacket packet = new ClientboundBlockUpdatePacket(pos, Blocks.AIR.defaultBlockState());
++ this.player.connection.send(packet);
++ }
++
++ event = new BlockBreakEvent(bblock, this.player.getBukkitEntity());
++
++ // Sword + Creative mode pre-cancel
++ event.setCancelled(isSwordNoBreak);
++
++ // Calculate default block experience
++ IBlockData nmsData = this.level.getBlockState(pos);
++ Block nmsBlock = nmsData.getBlock();
++
++ ItemStack itemstack = this.player.getItemBySlot(EquipmentSlot.MAINHAND);
++
++ if (nmsBlock != null && !event.isCancelled() && !this.isCreative() && this.player.hasCorrectToolForDrops(nmsBlock.defaultBlockState())) {
++ event.setExpToDrop(nmsBlock.getExpDrop(nmsData, this.level, pos, itemstack, true));
++ }
++
++ this.level.getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ if (isSwordNoBreak) {
++ return false;
++ }
++ // Let the client know the block still exists
++ this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos));
++
++ // Brute force all possible updates
++ for (Direction dir : Direction.values()) {
++ this.player.connection.send(new ClientboundBlockUpdatePacket(level, pos.relative(dir)));
++ }
++
++ // Update any tile entity data for this block
++ BlockEntity tileentity = this.level.getBlockEntity(pos);
++ if (tileentity != null) {
++ this.player.connection.send(tileentity.getUpdatePacket());
++ }
++ return false;
++ }
++ }
++ // CraftBukkit end
++
++ if (false && !this.player.getMainHandItem().getItem().canAttackBlock(iblockdata, this.level, pos, this.player)) { // CraftBukkit - false
+ return false;
+ } else {
+- BlockEntity blockentity = this.level.getBlockEntity(blockpos);
+- Block block = blockstate.getBlock();
++ iblockdata = this.level.getBlockState(pos); // CraftBukkit - update state from plugins
++ if (iblockdata.isAir()) return false; // CraftBukkit - A plugin set block to air without cancelling
++ BlockEntity tileentity = this.level.getBlockEntity(pos);
++ Block block = iblockdata.getBlock();
+
+ if (block instanceof GameMasterBlock && !this.player.canUseGameMasterBlocks()) {
+ this.level.sendBlockUpdated(blockpos, blockstate, blockstate, 3);
+@@ -248,27 +386,44 @@
+ } else if (this.player.blockActionRestricted(this.level, blockpos, this.gameModeForPlayer)) {
+ return false;
+ } else {
+- BlockState blockstate1 = block.playerWillDestroy(this.level, blockpos, blockstate, this.player);
+- boolean flag = this.level.removeBlock(blockpos, false);
++ // CraftBukkit start
++ org.bukkit.block.BlockState state = bblock.getState();
++ level.captureDrops = new ArrayList<>();
++ // CraftBukkit end
++ IBlockData iblockdata1 = block.playerWillDestroy(this.level, pos, iblockdata, this.player);
++ boolean flag = this.level.removeBlock(pos, false);
+
+ if (flag) {
+ block.destroy(this.level, blockpos, blockstate1);
+ }
+
+ if (this.isCreative()) {
+- return true;
++ // return true; // CraftBukkit
+ } else {
+ ItemStack itemstack = this.player.getMainHandItem();
+ ItemStack itemstack1 = itemstack.copy();
+ boolean flag1 = this.player.hasCorrectToolForDrops(blockstate1);
+
+- itemstack.mineBlock(this.level, blockstate1, blockpos, this.player);
+- if (flag && flag1) {
+- block.playerDestroy(this.level, this.player, blockpos, blockstate1, blockentity, itemstack1);
++ itemstack.mineBlock(this.level, iblockdata1, pos, this.player);
++ if (flag && flag1 && event.isDropItems()) { // CraftBukkit - Check if block should drop items
++ block.playerDestroy(this.level, this.player, pos, iblockdata1, tileentity, itemstack1);
+ }
+
+- return true;
++ // return true; // CraftBukkit
+ }
++ // CraftBukkit start
++ if (event.isDropItems()) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDropItemEvent(bblock, state, this.player, level.captureDrops);
++ }
++ level.captureDrops = null;
++
++ // Drop event experience
++ if (flag && event != null) {
++ iblockdata.getBlock().popExperience(this.level, pos, event.getExpToDrop());
++ }
++
++ return true;
++ // CraftBukkit end
+ }
+ }
+ }
+@@ -313,9 +468,17 @@
+ }
+ }
+
+- public InteractionResult useItemOn(ServerPlayer serverplayer, Level level, ItemStack itemstack, InteractionHand interactionhand, BlockHitResult blockhitresult) {
+- BlockPos blockpos = blockhitresult.getBlockPos();
+- BlockState blockstate = level.getBlockState(blockpos);
++ // CraftBukkit start - whole method
++ public boolean interactResult = false;
++ public boolean firedInteract = false;
++ public BlockPos interactPosition;
++ public EnumHand interactHand;
++ public ItemStack interactItemStack;
++ public InteractionResult useItemOn(ServerPlayer player, Level level, ItemStack stack, EnumHand hand, BlockHitResult hitResult) {
++ BlockPos blockposition = hitResult.getBlockPos();
++ IBlockData iblockdata = level.getBlockState(blockposition);
++ InteractionResult enuminteractionresult = InteractionResult.PASS;
++ boolean cancelledBlock = false;
+
+ if (!blockstate.getBlock().isEnabled(level.enabledFeatures())) {
+ return InteractionResult.FAIL;
+@@ -364,6 +557,8 @@
+ return InteractionResult.PASS;
+ }
+ }
++ return enuminteractionresult;
++ // CraftBukkit end
+ }
+
+ public void setLevel(ServerLevel serverlevel) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/TicketType.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/TicketType.java.patch
new file mode 100644
index 0000000000..cd45c85c43
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/TicketType.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/server/level/TicketType.java
++++ b/net/minecraft/server/level/TicketType.java
+@@ -23,6 +23,8 @@
+ public static final TicketType<BlockPos> PORTAL = create("portal", Vec3i::compareTo, 300);
+ public static final TicketType<Integer> POST_TELEPORT = create("post_teleport", Integer::compareTo, 5);
+ public static final TicketType<ChunkPos> UNKNOWN = create("unknown", Comparator.comparingLong(ChunkPos::toLong), 1);
++ public static final TicketType<Unit> PLUGIN = create("plugin", (a, b) -> 0); // CraftBukkit
++ public static final TicketType<org.bukkit.plugin.Plugin> PLUGIN_TICKET = create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit
+
+ public static <T> TicketType<T> create(String s, Comparator<T> comparator) {
+ return new TicketType<>(s, comparator, 0L);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/WorldGenRegion.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/WorldGenRegion.java.patch
new file mode 100644
index 0000000000..e5539e27fd
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/level/WorldGenRegion.java.patch
@@ -0,0 +1,27 @@
+--- a/net/minecraft/server/level/WorldGenRegion.java
++++ b/net/minecraft/server/level/WorldGenRegion.java
+@@ -221,8 +208,8 @@
+ if (blockstate.isAir()) {
+ return false;
+ } else {
+- if (flag) {
+- BlockEntity blockentity = blockstate.hasBlockEntity() ? this.getBlockEntity(blockpos) : null;
++ if (false) { // CraftBukkit - SPIGOT-6833: Do not drop during world generation
++ BlockEntity tileentity = iblockdata.hasBlockEntity() ? this.getBlockEntity(pos) : null;
+
+ Block.dropResources(blockstate, this.level, blockpos, blockentity, entity, ItemStack.EMPTY);
+ }
+@@ -344,6 +327,13 @@
+ @Override
+ @Override
+ public boolean addFreshEntity(Entity entity) {
++ // CraftBukkit start
++ return addFreshEntity(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
++ }
++
++ @Override
++ public boolean addFreshEntity(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
++ // CraftBukkit end
+ int i = SectionPos.blockToSectionCoord(entity.getBlockX());
+ int j = SectionPos.blockToSectionCoord(entity.getBlockZ());
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/LegacyQueryHandler.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/LegacyQueryHandler.java.patch
new file mode 100644
index 0000000000..da24fc74b6
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/LegacyQueryHandler.java.patch
@@ -0,0 +1,45 @@
+--- a/net/minecraft/server/network/LegacyQueryHandler.java
++++ b/net/minecraft/server/network/LegacyQueryHandler.java
+@@ -36,10 +35,11 @@
+ SocketAddress socketaddress = channelhandlercontext.channel().remoteAddress();
+ int i = bytebuf.readableBytes();
+ String s;
++ org.bukkit.event.server.ServerListPingEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callServerListPingEvent(socketaddress, server.getMotd(), server.getPlayerCount(), server.getMaxPlayers()); // CraftBukkit
+
+ if (i == 0) {
+ LegacyQueryHandler.LOGGER.debug("Ping: (<1.3.x) from {}", socketaddress);
+- s = createVersion0Response(this.server);
++ s = createVersion0Response(this.server, event); // CraftBukkit
+ sendFlushAndClose(channelhandlercontext, createLegacyDisconnectPacket(channelhandlercontext.alloc(), s));
+ } else {
+ if (bytebuf.readUnsignedByte() != 1) {
+@@ -56,7 +56,7 @@
+ LegacyQueryHandler.LOGGER.debug("Ping: (1.4-1.5.x) from {}", socketaddress);
+ }
+
+- s = createVersion1Response(this.server);
++ s = createVersion1Response(this.server, event); // CraftBukkit
+ sendFlushAndClose(channelhandlercontext, createLegacyDisconnectPacket(channelhandlercontext.alloc(), s));
+ }
+
+@@ -107,12 +107,16 @@
+ }
+ }
+
+- private static String createVersion0Response(ServerInfo serverinfo) {
+- return String.format(Locale.ROOT, "%s\u00a7%d\u00a7%d", serverinfo.getMotd(), serverinfo.getPlayerCount(), serverinfo.getMaxPlayers());
++ // CraftBukkit start
++ private static String createVersion0Response(ServerInfo serverinfo, org.bukkit.event.server.ServerListPingEvent event) {
++ return String.format(Locale.ROOT, "%s\u00a7%d\u00a7%d", event.getMotd(), event.getNumPlayers(), event.getMaxPlayers());
++ // CraftBukkit end
+ }
+
+- private static String createVersion1Response(ServerInfo serverinfo) {
+- return String.format(Locale.ROOT, "\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", 127, serverinfo.getServerVersion(), serverinfo.getMotd(), serverinfo.getPlayerCount(), serverinfo.getMaxPlayers());
++ // CraftBukkit start
++ private static String createVersion1Response(ServerInfo serverinfo, org.bukkit.event.server.ServerListPingEvent event) {
++ return String.format(Locale.ROOT, "\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", 127, serverinfo.getServerVersion(), event.getMotd(), event.getNumPlayers(), event.getMaxPlayers());
++ // CraftBukkit end
+ }
+
+ private static void sendFlushAndClose(ChannelHandlerContext channelhandlercontext, ByteBuf bytebuf) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch
new file mode 100644
index 0000000000..3db848b5ec
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch
@@ -0,0 +1,208 @@
+--- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
++++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
+@@ -26,6 +30,17 @@
+ import net.minecraft.util.thread.BlockableEventLoop;
+ import org.slf4j.Logger;
+
++// CraftBukkit start
++import io.netty.buffer.ByteBuf;
++import java.util.concurrent.ExecutionException;
++import org.bukkit.craftbukkit.entity.CraftPlayer;
++import org.bukkit.craftbukkit.util.CraftChatMessage;
++import org.bukkit.craftbukkit.util.CraftLocation;
++import org.bukkit.craftbukkit.util.Waitable;
++import org.bukkit.event.player.PlayerKickEvent;
++import org.bukkit.event.player.PlayerResourcePackStatusEvent;
++// CraftBukkit end
++
+ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPacketListener {
+
+ private static final Logger LOGGER = LogUtils.getLogger();
+@@ -39,13 +54,21 @@
+ private int latency;
+ private volatile boolean suspendFlushingOnServerThread = false;
+
+- public ServerCommonPacketListenerImpl(MinecraftServer minecraftserver, Connection connection, CommonListenerCookie commonlistenercookie) {
++ public ServerCommonPacketListenerImpl(MinecraftServer minecraftserver, Connection networkmanager, CommonListenerCookie commonlistenercookie, ServerPlayer player) { // CraftBukkit
+ this.server = minecraftserver;
+ this.connection = connection;
+ this.keepAliveTime = Util.getMillis();
+ this.latency = commonlistenercookie.latency();
++ // CraftBukkit start - add fields and methods
++ this.player = player;
++ this.cserver = minecraftserver.server;
+ }
+
++ public CraftPlayer getCraftPlayer() {
++ return (this.player == null) ? null : (CraftPlayer) this.player.getBukkitEntity();
++ // CraftBukkit end
++ }
++
+ @Override
+ @Override
+ public void onDisconnect(Component component) {
+@@ -59,6 +83,7 @@
+ @Override
+ @Override
+ public void handleKeepAlive(ServerboundKeepAlivePacket serverboundkeepalivepacket) {
++ PacketUtils.ensureRunningOnSameThread(serverboundkeepalivepacket, this, this.player.serverLevel()); // CraftBukkit
+ if (this.keepAlivePending && serverboundkeepalivepacket.getId() == this.keepAliveChallenge) {
+ int i = (int) (Util.getMillis() - this.keepAliveTime);
+
+@@ -74,10 +98,52 @@
+ @Override
+ public void handlePong(ServerboundPongPacket serverboundpongpacket) {}
+
++ // CraftBukkit start
++ private static final ResourceLocation CUSTOM_REGISTER = new ResourceLocation("register");
++ private static final ResourceLocation CUSTOM_UNREGISTER = new ResourceLocation("unregister");
++
+ @Override
+ @Override
+ public void handleCustomPayload(ServerboundCustomPayloadPacket serverboundcustompayloadpacket) {}
+
++ if (identifier.equals(CUSTOM_REGISTER)) {
++ try {
++ String channels = payload.toString(com.google.common.base.Charsets.UTF_8);
++ for (String channel : channels.split("\0")) {
++ getCraftPlayer().addChannel(channel);
++ }
++ } catch (Exception ex) {
++ ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t register custom payload", ex);
++ this.disconnect("Invalid payload REGISTER!");
++ }
++ } else if (identifier.equals(CUSTOM_UNREGISTER)) {
++ try {
++ String channels = payload.toString(com.google.common.base.Charsets.UTF_8);
++ for (String channel : channels.split("\0")) {
++ getCraftPlayer().removeChannel(channel);
++ }
++ } catch (Exception ex) {
++ ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t unregister custom payload", ex);
++ this.disconnect("Invalid payload UNREGISTER!");
++ }
++ } else {
++ try {
++ byte[] data = new byte[payload.readableBytes()];
++ payload.readBytes(data);
++ cserver.getMessenger().dispatchIncomingMessage(player.getBukkitEntity(), identifier.toString(), data);
++ } catch (Exception ex) {
++ ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t dispatch custom payload", ex);
++ this.disconnect("Invalid custom payload!");
++ }
++ }
++
++ }
++
++ public final boolean isDisconnected() {
++ return !this.player.joining && !this.connection.isConnected();
++ }
++ // CraftBukkit end
++
+ @Override
+ @Override
+ public void handleResourcePackResponse(ServerboundResourcePackPacket serverboundresourcepackpacket) {
+@@ -86,6 +156,7 @@
+ ServerCommonPacketListenerImpl.LOGGER.info("Disconnecting {} due to resource pack {} rejection", this.playerProfile().getName(), serverboundresourcepackpacket.id());
+ this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect"));
+ }
++ this.cserver.getPluginManager().callEvent(new PlayerResourcePackStatusEvent(getCraftPlayer(), serverboundresourcepackpacket.id(), PlayerResourcePackStatusEvent.Status.values()[serverboundresourcepackpacket.action().ordinal()])); // CraftBukkit
+
+ }
+
+@@ -93,7 +164,7 @@
+ this.server.getProfiler().push("keepAlive");
+ long i = Util.getMillis();
+
+- if (i - this.keepAliveTime >= 15000L) {
++ if (i - this.keepAliveTime >= 25000L) { // CraftBukkit
+ if (this.keepAlivePending) {
+ this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE);
+ } else {
+@@ -121,6 +192,14 @@
+ }
+
+ public void send(Packet<?> packet, @Nullable PacketSendListener packetsendlistener) {
++ // CraftBukkit start
++ if (packet == null) {
++ return;
++ } else if (packet instanceof ClientboundSetDefaultSpawnPositionPacket) {
++ ClientboundSetDefaultSpawnPositionPacket packet6 = (ClientboundSetDefaultSpawnPositionPacket) packet;
++ this.player.compassTarget = CraftLocation.toBukkit(packet6.pos, this.getCraftPlayer().getWorld());
++ }
++ // CraftBukkit end
+ boolean flag = !this.suspendFlushingOnServerThread || !this.server.isSameThread();
+
+ try {
+@@ -136,16 +215,67 @@
+ }
+ }
+
+- public void disconnect(Component component) {
+- this.connection.send(new ClientboundDisconnectPacket(component), PacketSendListener.thenRun(() -> {
+- this.connection.disconnect(component);
++ // CraftBukkit start
++ @Deprecated
++ public void disconnect(Component ichatbasecomponent) {
++ disconnect(CraftChatMessage.fromComponent(ichatbasecomponent));
++ }
++ // CraftBukkit end
++
++ public void disconnect(String s) {
++ // CraftBukkit start - fire PlayerKickEvent
++ if (this.processedDisconnect) {
++ return;
++ }
++ if (!this.cserver.isPrimaryThread()) {
++ Waitable waitable = new Waitable() {
++ @Override
++ protected Object evaluate() {
++ ServerCommonPacketListenerImpl.this.disconnect(s);
++ return null;
++ }
++ };
++
++ this.server.processQueue.add(waitable);
++
++ try {
++ waitable.get();
++ } catch (InterruptedException e) {
++ Thread.currentThread().interrupt();
++ } catch (ExecutionException e) {
++ throw new RuntimeException(e);
++ }
++ return;
++ }
++
++ String leaveMessage = ChatFormatting.YELLOW + this.player.getScoreboardName() + " left the game.";
++
++ PlayerKickEvent event = new PlayerKickEvent(this.player.getBukkitEntity(), s, leaveMessage);
++
++ if (this.cserver.getServer().isRunning()) {
++ this.cserver.getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ // Do not kick the player
++ return;
++ }
++ this.player.kickLeaveMessage = event.getLeaveMessage(); // CraftBukkit - SPIGOT-3034: Forward leave message to PlayerQuitEvent
++ // Send the possibly modified leave message
++ final Component ichatbasecomponent = CraftChatMessage.fromString(event.getReason(), true)[0];
++ // CraftBukkit end
++
++ this.connection.send(new ClientboundDisconnectPacket(ichatbasecomponent), PacketSendListener.thenRun(() -> {
++ this.connection.disconnect(ichatbasecomponent);
+ }));
++ this.onDisconnect(ichatbasecomponent); // CraftBukkit - fire quit instantly
+ this.connection.setReadOnly();
+ MinecraftServer minecraftserver = this.server;
+ Connection connection = this.connection;
+
+ Objects.requireNonNull(this.connection);
+- minecraftserver.executeBlocking(connection::handleDisconnection);
++ // CraftBukkit - Don't wait
++ minecraftserver.wrapRunnable(networkmanager::handleDisconnection);
+ }
+
+ protected boolean isSingleplayerOwner() {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch
new file mode 100644
index 0000000000..fa1a085dbb
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch
@@ -0,0 +1,30 @@
+--- a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
++++ b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
+@@ -44,8 +44,8 @@
+ private ConfigurationTask currentTask;
+ private ClientInformation clientInformation;
+
+- public ServerConfigurationPacketListenerImpl(MinecraftServer minecraftserver, Connection connection, CommonListenerCookie commonlistenercookie) {
+- super(minecraftserver, connection, commonlistenercookie);
++ public ServerConfigurationPacketListenerImpl(MinecraftServer minecraftserver, Connection networkmanager, CommonListenerCookie commonlistenercookie, ServerPlayer player) { // CraftBukkit
++ super(minecraftserver, networkmanager, commonlistenercookie, player); // CraftBukkit
+ this.gameProfile = commonlistenercookie.gameProfile();
+ this.clientInformation = commonlistenercookie.clientInformation();
+ }
+@@ -123,14 +117,14 @@
+ return;
+ }
+
+- Component component = playerlist.canPlayerLogin(this.connection.getRemoteAddress(), this.gameProfile);
++ Component ichatbasecomponent = null; // CraftBukkit - login checks already completed
+
+ if (component != null) {
+ this.disconnect(component);
+ return;
+ }
+
+- ServerPlayer serverplayer = playerlist.getPlayerForLogin(this.gameProfile, this.clientInformation);
++ ServerPlayer entityplayer = playerlist.getPlayerForLogin(this.gameProfile, this.clientInformation, this.player); // CraftBukkit
+
+ playerlist.placeNewPlayer(this.connection, serverplayer, this.createCookie(this.clientInformation));
+ this.connection.resumeInboundAfterProtocolChange();
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/ServerConnectionListener.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/ServerConnectionListener.java.patch
new file mode 100644
index 0000000000..ee30186f96
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/ServerConnectionListener.java.patch
@@ -0,0 +1,24 @@
+--- a/net/minecraft/server/network/ServerConnectionListener.java
++++ b/net/minecraft/server/network/ServerConnectionListener.java
+@@ -105,10 +104,20 @@
+ ((Connection) object).configurePacketHandler(channelpipeline);
+ ((Connection) object).setListenerForServerboundHandshake(new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, (Connection) object));
+ }
+- }).group(eventloopgroup).localAddress(inetaddress, i)).bind().syncUninterruptibly());
++ }).group(eventloopgroup).localAddress(address, port)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit
+ }
+ }
+
++ // CraftBukkit start
++ public void acceptConnections() {
++ synchronized (this.channels) {
++ for (ChannelFuture future : this.channels) {
++ future.channel().config().setAutoRead(true);
++ }
++ }
++ }
++ // CraftBukkit end
++
+ public SocketAddress startMemoryChannel() {
+ List list = this.channels;
+ ChannelFuture channelfuture;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
new file mode 100644
index 0000000000..89935a450c
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
@@ -0,0 +1,1682 @@
+--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
++++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+@@ -184,6 +185,62 @@
+ import net.minecraft.world.phys.shapes.VoxelShape;
+ import org.slf4j.Logger;
+
++// CraftBukkit start
++import com.mojang.datafixers.util.Pair;
++import java.util.Arrays;
++import java.util.concurrent.ExecutionException;
++import java.util.concurrent.atomic.AtomicInteger;
++import java.util.function.Function;
++import net.minecraft.network.chat.OutgoingChatMessage;
++import net.minecraft.world.entity.animal.Bucketable;
++import net.minecraft.world.entity.animal.allay.Allay;
++import net.minecraft.world.entity.item.ItemEntity;
++import net.minecraft.world.inventory.InventoryClickType;
++import net.minecraft.world.inventory.MerchantMenu;
++import net.minecraft.world.inventory.RecipeBookMenu;
++import net.minecraft.world.inventory.Slot;
++import net.minecraft.world.item.crafting.RecipeHolder;
++import org.bukkit.Location;
++import org.bukkit.craftbukkit.entity.CraftEntity;
++import org.bukkit.craftbukkit.entity.CraftPlayer;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.craftbukkit.util.CraftChatMessage;
++import org.bukkit.craftbukkit.util.CraftMagicNumbers;
++import org.bukkit.craftbukkit.util.CraftNamespacedKey;
++import org.bukkit.craftbukkit.util.LazyPlayerSet;
++import org.bukkit.craftbukkit.util.Waitable;
++import org.bukkit.entity.Player;
++import org.bukkit.event.Event;
++import org.bukkit.event.block.Action;
++import org.bukkit.event.inventory.ClickType;
++import org.bukkit.event.inventory.CraftItemEvent;
++import org.bukkit.event.inventory.InventoryAction;
++import org.bukkit.event.inventory.InventoryClickEvent;
++import org.bukkit.event.inventory.InventoryCreativeEvent;
++import org.bukkit.event.inventory.InventoryType.SlotType;
++import org.bukkit.event.inventory.SmithItemEvent;
++import org.bukkit.event.player.AsyncPlayerChatEvent;
++import org.bukkit.event.player.PlayerAnimationEvent;
++import org.bukkit.event.player.PlayerAnimationType;
++import org.bukkit.event.player.PlayerChatEvent;
++import org.bukkit.event.player.PlayerCommandPreprocessEvent;
++import org.bukkit.event.player.PlayerInteractAtEntityEvent;
++import org.bukkit.event.player.PlayerInteractEntityEvent;
++import org.bukkit.event.player.PlayerItemHeldEvent;
++import org.bukkit.event.player.PlayerMoveEvent;
++import org.bukkit.event.player.PlayerRespawnEvent.RespawnReason;
++import org.bukkit.event.player.PlayerSwapHandItemsEvent;
++import org.bukkit.event.player.PlayerTeleportEvent;
++import org.bukkit.event.player.PlayerToggleFlightEvent;
++import org.bukkit.event.player.PlayerToggleSneakEvent;
++import org.bukkit.event.player.PlayerToggleSprintEvent;
++import org.bukkit.inventory.CraftingInventory;
++import org.bukkit.inventory.EquipmentSlot;
++import org.bukkit.inventory.InventoryView;
++import org.bukkit.inventory.SmithingInventory;
++// CraftBukkit end
++
+ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl implements ServerGamePacketListener, ServerPlayerConnection, TickablePacketListener {
+
+ static final Logger LOGGER = LogUtils.getLogger();
+@@ -195,7 +252,9 @@
+ public final PlayerChunkSender chunkSender;
+ private int tickCount;
+ private int ackBlockChangesUpTo = -1;
+- private int chatSpamTickCount;
++ // CraftBukkit start - multithreaded fields
++ private final AtomicInteger chatSpamTickCount = new AtomicInteger();
++ // CraftBukkit end
+ private int dropSpamTickCount;
+ private double firstGoodX;
+ private double firstGoodY;
+@@ -229,20 +288,36 @@
+ private final FutureChain chatMessageChain;
+ private boolean waitingForSwitchToConfig;
+
+- public ServerGamePacketListenerImpl(MinecraftServer minecraftserver, Connection connection, ServerPlayer serverplayer, CommonListenerCookie commonlistenercookie) {
+- super(minecraftserver, connection, commonlistenercookie);
+- this.chunkSender = new PlayerChunkSender(connection.isMemoryConnection());
+- connection.setListener(this);
+- this.player = serverplayer;
+- serverplayer.connection = this;
+- serverplayer.getTextFilter().join();
+- UUID uuid = serverplayer.getUUID();
++ public ServerGamePacketListenerImpl(MinecraftServer minecraftserver, Connection networkmanager, ServerPlayer entityplayer, CommonListenerCookie commonlistenercookie) {
++ super(minecraftserver, networkmanager, commonlistenercookie, entityplayer); // CraftBukkit
++ this.chunkSender = new PlayerChunkSender(networkmanager.isMemoryConnection());
++ networkmanager.setListener(this);
++ this.player = entityplayer;
++ entityplayer.connection = this;
++ entityplayer.getTextFilter().join();
++ UUID uuid = entityplayer.getUUID();
+
+ Objects.requireNonNull(minecraftserver);
+ this.signedMessageDecoder = SignedMessageChain.Decoder.unsigned(uuid, minecraftserver::enforceSecureProfile);
+- this.chatMessageChain = new FutureChain(minecraftserver);
++ this.chatMessageChain = new FutureChain(minecraftserver.chatExecutor); // CraftBukkit - async chat
+ }
+
++ // CraftBukkit start - add fields
++ private int lastTick = MinecraftServer.currentTick;
++ private int allowedPlayerTicks = 1;
++ private int lastDropTick = MinecraftServer.currentTick;
++ private int lastBookTick = MinecraftServer.currentTick;
++ private int dropCount = 0;
++
++ // Get position of last block hit for BlockDamageLevel.STOPPED
++ private double lastPosX = Double.MAX_VALUE;
++ private double lastPosY = Double.MAX_VALUE;
++ private double lastPosZ = Double.MAX_VALUE;
++ private float lastPitch = Float.MAX_VALUE;
++ private float lastYaw = Float.MAX_VALUE;
++ private boolean justTeleported = false;
++ // CraftBukkit end
++
+ @Override
+ @Override
+ public void tick() {
+@@ -295,15 +369,21 @@
+ }
+
+ this.keepConnectionAlive();
++ // CraftBukkit start
++ for (int spam; (spam = this.chatSpamTickCount.get()) > 0 && !chatSpamTickCount.compareAndSet(spam, spam - 1); ) ;
++ /* Use thread-safe field access instead
+ if (this.chatSpamTickCount > 0) {
+ --this.chatSpamTickCount;
+ }
++ */
++ // CraftBukkit end
+
+ if (this.dropSpamTickCount > 0) {
+ --this.dropSpamTickCount;
+ }
+
+ if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L) {
++ this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854
+ this.disconnect(Component.translatable("multiplayer.disconnect.idling"));
+ }
+
+@@ -399,7 +474,34 @@
+ double d9 = entity.getDeltaMovement().lengthSqr();
+ double d10 = d6 * d6 + d7 * d7 + d8 * d8;
+
+- if (d10 - d9 > 100.0D && !this.isSingleplayerOwner()) {
++
++ // CraftBukkit start - handle custom speeds and skipped ticks
++ this.allowedPlayerTicks += (System.currentTimeMillis() / 50) - this.lastTick;
++ this.allowedPlayerTicks = Math.max(this.allowedPlayerTicks, 1);
++ this.lastTick = (int) (System.currentTimeMillis() / 50);
++
++ ++this.receivedMovePacketCount;
++ int i = this.receivedMovePacketCount - this.knownMovePacketCount;
++ if (i > Math.max(this.allowedPlayerTicks, 5)) {
++ ServerGamePacketListenerImpl.LOGGER.debug(this.player.getScoreboardName() + " is sending move packets too frequently (" + i + " packets since last tick)");
++ i = 1;
++ }
++
++ if (d10 > 0) {
++ allowedPlayerTicks -= 1;
++ } else {
++ allowedPlayerTicks = 20;
++ }
++ double speed;
++ if (player.getAbilities().flying) {
++ speed = player.getAbilities().flyingSpeed * 20f;
++ } else {
++ speed = player.getAbilities().walkingSpeed * 10f;
++ }
++ speed *= 2f; // TODO: Get the speed of the vehicle instead of the player
++
++ if (d10 - d9 > Math.max(100.0D, Math.pow((double) (10.0F * (float) i * speed), 2)) && !this.isSingleplayerOwner()) {
++ // CraftBukkit end
+ ServerGamePacketListenerImpl.LOGGER.warn("{} (vehicle of {}) moved too quickly! {},{},{}", new Object[]{entity.getName().getString(), this.player.getName().getString(), d6, d7, d8});
+ this.send(new ClientboundMoveVehiclePacket(entity));
+ return;
+@@ -439,14 +541,72 @@
+ }
+
+ entity.absMoveTo(d3, d4, d5, f, f1);
+- boolean flag3 = serverlevel.noCollision(entity, entity.getBoundingBox().deflate(0.0625D));
++ player.absMoveTo(d3, d4, d5, this.player.getYRot(), this.player.getXRot()); // CraftBukkit
++ boolean flag3 = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D));
+
+ if (flag && (flag2 || !flag3)) {
+ entity.absMoveTo(d0, d1, d2, f, f1);
++ player.absMoveTo(d0, d1, d2, this.player.getYRot(), this.player.getXRot()); // CraftBukkit
+ this.send(new ClientboundMoveVehiclePacket(entity));
+ return;
+ }
+
++ // CraftBukkit start - fire PlayerMoveEvent
++ Player player = this.getCraftPlayer();
++ Location from = new Location(player.getWorld(), lastPosX, lastPosY, lastPosZ, lastYaw, lastPitch); // Get the Players previous Event location.
++ Location to = player.getLocation().clone(); // Start off the To location as the Players current location.
++
++ // If the packet contains movement information then we update the To location with the correct XYZ.
++ to.setX(packet.getX());
++ to.setY(packet.getY());
++ to.setZ(packet.getZ());
++
++
++ // If the packet contains look information then we update the To location with the correct Yaw & Pitch.
++ to.setYaw(packet.getYRot());
++ to.setPitch(packet.getXRot());
++
++ // Prevent 40 event-calls for less than a single pixel of movement >.>
++ double delta = Math.pow(this.lastPosX - to.getX(), 2) + Math.pow(this.lastPosY - to.getY(), 2) + Math.pow(this.lastPosZ - to.getZ(), 2);
++ float deltaAngle = Math.abs(this.lastYaw - to.getYaw()) + Math.abs(this.lastPitch - to.getPitch());
++
++ if ((delta > 1f / 256 || deltaAngle > 10f) && !this.player.isImmobile()) {
++ this.lastPosX = to.getX();
++ this.lastPosY = to.getY();
++ this.lastPosZ = to.getZ();
++ this.lastYaw = to.getYaw();
++ this.lastPitch = to.getPitch();
++
++ // Skip the first time we do this
++ if (from.getX() != Double.MAX_VALUE) {
++ Location oldTo = to.clone();
++ PlayerMoveEvent event = new PlayerMoveEvent(player, from, to);
++ this.cserver.getPluginManager().callEvent(event);
++
++ // If the event is cancelled we move the player back to their old location.
++ if (event.isCancelled()) {
++ teleport(from);
++ return;
++ }
++
++ // If a Plugin has changed the To destination then we teleport the Player
++ // there to avoid any 'Moved wrongly' or 'Moved too quickly' errors.
++ // We only do this if the Event was not cancelled.
++ if (!oldTo.equals(event.getTo()) && !event.isCancelled()) {
++ this.player.getBukkitEntity().teleport(event.getTo(), PlayerTeleportEvent.TeleportCause.PLUGIN);
++ return;
++ }
++
++ // Check to see if the Players Location has some how changed during the call of the event.
++ // This can happen due to a plugin teleporting the player instead of using .setTo()
++ if (!from.equals(this.getCraftPlayer().getLocation()) && this.justTeleported) {
++ this.justTeleported = false;
++ return;
++ }
++ }
++ }
++ // CraftBukkit end
++
+ this.player.serverLevel().getChunkSource().move(this.player);
+ this.player.checkMovementStatistics(this.player.getX() - d0, this.player.getY() - d1, this.player.getZ() - d2);
+ this.clientVehicleIsFloating = d11 >= -0.03125D && !flag1 && !this.server.isFlightAllowed() && !entity.isNoGravity() && this.noBlocksAround(entity);
+@@ -481,6 +640,7 @@
+ }
+
+ this.awaitingPositionFromClient = null;
++ this.player.serverLevel().getChunkSource().move(this.player); // CraftBukkit
+ }
+
+ }
+@@ -497,10 +656,10 @@
+ }
+
+ @Override
+- @Override
+- public void handleRecipeBookChangeSettingsPacket(ServerboundRecipeBookChangeSettingsPacket serverboundrecipebookchangesettingspacket) {
+- PacketUtils.ensureRunningOnSameThread(serverboundrecipebookchangesettingspacket, this, this.player.serverLevel());
+- this.player.getRecipeBook().setBookSetting(serverboundrecipebookchangesettingspacket.getBookType(), serverboundrecipebookchangesettingspacket.isOpen(), serverboundrecipebookchangesettingspacket.isFiltering());
++ public void handleRecipeBookChangeSettingsPacket(ServerboundRecipeBookChangeSettingsPacket packet) {
++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
++ CraftEventFactory.callRecipeBookSettingsEvent(this.player, packet.getBookType(), packet.isOpen(), packet.isFiltering()); // CraftBukkit
++ this.player.getRecipeBook().setBookSetting(packet.getBookType(), packet.isOpen(), packet.isFiltering());
+ }
+
+ @Override
+@@ -519,10 +677,15 @@
+ }
+
+ @Override
+- @Override
+- public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket serverboundcommandsuggestionpacket) {
+- PacketUtils.ensureRunningOnSameThread(serverboundcommandsuggestionpacket, this, this.player.serverLevel());
+- StringReader stringreader = new StringReader(serverboundcommandsuggestionpacket.getCommand());
++ public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket packet) {
++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
++ // CraftBukkit start
++ if (chatSpamTickCount.addAndGet(1) > 500 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) {
++ this.disconnect(Component.translatable("disconnect.spam"));
++ return;
++ }
++ // CraftBukkit end
++ StringReader stringreader = new StringReader(packet.getCommand());
+
+ if (stringreader.canRead() && stringreader.peek() == '/') {
+ stringreader.skip();
+@@ -531,7 +694,8 @@
+ ParseResults<CommandSourceStack> parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack());
+
+ this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> {
+- this.send(new ClientboundCommandSuggestionsPacket(serverboundcommandsuggestionpacket.getId(), suggestions));
++ if (suggestions.isEmpty()) return; // CraftBukkit - don't send through empty suggestions - prevents [<args>] from showing for plugins with nothing more to offer
++ this.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestions));
+ });
+ }
+
+@@ -785,8 +940,15 @@
+ int i = serverboundselecttradepacket.getItem();
+ AbstractContainerMenu abstractcontainermenu = this.player.containerMenu;
+
+- if (abstractcontainermenu instanceof MerchantMenu) {
+- MerchantMenu merchantmenu = (MerchantMenu) abstractcontainermenu;
++ if (container instanceof MerchantMenu) {
++ MerchantMenu containermerchant = (MerchantMenu) container;
++ // CraftBukkit start
++ final org.bukkit.event.inventory.TradeSelectEvent tradeSelectEvent = CraftEventFactory.callTradeSelectEvent(this.player, i, containermerchant);
++ if (tradeSelectEvent.isCancelled()) {
++ this.player.getBukkitEntity().updateInventory();
++ return;
++ }
++ // CraftBukkit end
+
+ if (!merchantmenu.stillValid(this.player)) {
+ ServerGamePacketListenerImpl.LOGGER.debug("Player {} interacted with invalid menu {}", this.player, merchantmenu);
+@@ -800,9 +962,15 @@
+ }
+
+ @Override
+- @Override
+- public void handleEditBook(ServerboundEditBookPacket serverboundeditbookpacket) {
+- int i = serverboundeditbookpacket.getSlot();
++ public void handleEditBook(ServerboundEditBookPacket packet) {
++ // CraftBukkit start
++ if (this.lastBookTick + 20 > MinecraftServer.currentTick) {
++ this.disconnect("Book edited too quickly!");
++ return;
++ }
++ this.lastBookTick = MinecraftServer.currentTick;
++ // CraftBukkit end
++ int i = packet.getSlot();
+
+ if (Inventory.isHotbarSlot(i) || i == 40) {
+ List<String> list = Lists.newArrayList();
+@@ -828,7 +996,7 @@
+ ItemStack itemstack = this.player.getInventory().getItem(i);
+
+ if (itemstack.is(Items.WRITABLE_BOOK)) {
+- this.updateBookPages(list, UnaryOperator.identity(), itemstack);
++ this.updateBookPages(pages, UnaryOperator.identity(), itemstack.copy(), index, itemstack); // CraftBukkit
+ }
+ }
+
+@@ -853,13 +1021,13 @@
+
+ this.updateBookPages(list, (s) -> {
+ return Component.Serializer.toJson(Component.literal(s));
+- }, itemstack1);
+- this.player.getInventory().setItem(i, itemstack1);
++ }, itemstack1, index, itemstack); // CraftBukkit
++ this.player.getInventory().setItem(index, itemstack); // CraftBukkit - event factory updates the hand book
+ }
+ }
+
+- private void updateBookPages(List<FilteredText> list, UnaryOperator<String> unaryoperator, ItemStack itemstack) {
+- ListTag listtag = new ListTag();
++ private void updateBookPages(List<FilteredText> list, UnaryOperator<String> unaryoperator, ItemStack itemstack, int slot, ItemStack handItem) { // CraftBukkit
++ ListTag nbttaglist = new ListTag();
+
+ if (this.player.isTextFilteringEnabled()) {
+ Stream stream = list.stream().map((filteredtext) -> {
+@@ -887,7 +1055,8 @@
+ }
+ }
+
+- itemstack.addTagElement("pages", listtag);
++ itemstack.addTagElement("pages", nbttaglist);
++ CraftEventFactory.handleEditBookEvent(player, slot, handItem, itemstack); // CraftBukkit
+ }
+
+ @Override
+@@ -948,7 +1113,7 @@
+ } else {
+ ServerLevel serverlevel = this.player.serverLevel();
+
+- if (!this.player.wonGame) {
++ if (!this.player.wonGame && !this.player.isImmobile()) { // CraftBukkit
+ if (this.tickCount == 0) {
+ this.resetPosition();
+ }
+@@ -958,7 +1123,7 @@
+ this.awaitingTeleportTime = this.tickCount;
+ this.teleport(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot());
+ }
+-
++ this.allowedPlayerTicks = 20; // CraftBukkit
+ } else {
+ this.awaitingTeleportTime = this.tickCount;
+ double d0 = clampHorizontal(serverboundmoveplayerpacket.getX(this.player.getX()));
+@@ -970,7 +1135,15 @@
+ if (this.player.isPassenger()) {
+ this.player.absMoveTo(this.player.getX(), this.player.getY(), this.player.getZ(), f, f1);
+ this.player.serverLevel().getChunkSource().move(this.player);
++ this.allowedPlayerTicks = 20; // CraftBukkit
+ } else {
++ // CraftBukkit - Make sure the move is valid but then reset it for plugins to modify
++ double prevX = player.getX();
++ double prevY = player.getY();
++ double prevZ = player.getZ();
++ float prevYaw = player.getYRot();
++ float prevPitch = player.getXRot();
++ // CraftBukkit end
+ double d3 = this.player.getX();
+ double d4 = this.player.getY();
+ double d5 = this.player.getZ();
+@@ -990,7 +1163,12 @@
+ ++this.receivedMovePacketCount;
+ int i = this.receivedMovePacketCount - this.knownMovePacketCount;
+
+- if (i > 5) {
++ // CraftBukkit start - handle custom speeds and skipped ticks
++ this.allowedPlayerTicks += (System.currentTimeMillis() / 50) - this.lastTick;
++ this.allowedPlayerTicks = Math.max(this.allowedPlayerTicks, 1);
++ this.lastTick = (int) (System.currentTimeMillis() / 50);
++
++ if (i > Math.max(this.allowedPlayerTicks, 5)) {
+ ServerGamePacketListenerImpl.LOGGER.debug("{} is sending move packets too frequently ({} packets since last tick)", this.player.getName().getString(), i);
+ i = 1;
+ }
+@@ -998,7 +1188,8 @@
+ if (!this.player.isChangingDimension() && (!this.player.level().getGameRules().getBoolean(GameRules.RULE_DISABLE_ELYTRA_MOVEMENT_CHECK) || !this.player.isFallFlying())) {
+ float f2 = this.player.isFallFlying() ? 300.0F : 100.0F;
+
+- if (d10 - d9 > (double) (f2 * (float) i) && !this.isSingleplayerOwner()) {
++ if (d10 - d9 > Math.max(f2, Math.pow((double) (10.0F * (float) i * speed), 2)) && !this.isSingleplayerOwner()) {
++ // CraftBukkit end
+ ServerGamePacketListenerImpl.LOGGER.warn("{} moved too quickly! {},{},{}", new Object[]{this.player.getName().getString(), d6, d7, d8});
+ this.teleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot());
+ return;
+@@ -1019,7 +1210,8 @@
+
+ boolean flag1 = this.player.verticalCollisionBelow;
+
+- this.player.move(MoverType.PLAYER, new Vec3(d6, d7, d8));
++ this.player.move(EnumMoveType.PLAYER, new Vec3(d6, d7, d8));
++ this.player.onGround = packet.isOnGround(); // CraftBukkit - SPIGOT-5810, SPIGOT-5835, SPIGOT-6828: reset by this.player.move
+ double d11 = d7;
+
+ d6 = d0 - this.player.getX();
+@@ -1037,10 +1229,71 @@
+ ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!", this.player.getName().getString());
+ }
+
+- if (!this.player.noPhysics && !this.player.isSleeping() && (flag2 && serverlevel.noCollision(this.player, aabb) || this.isPlayerCollidingWithAnythingNew(serverlevel, aabb, d0, d1, d2))) {
+- this.teleport(d3, d4, d5, f, f1);
+- this.player.doCheckFallDamage(this.player.getX() - d3, this.player.getY() - d4, this.player.getZ() - d5, serverboundmoveplayerpacket.isOnGround());
++ if (!this.player.noPhysics && !this.player.isSleeping() && (flag2 && worldserver.noCollision(this.player, axisalignedbb) || this.isPlayerCollidingWithAnythingNew(worldserver, axisalignedbb, d0, d1, d2))) {
++ this.internalTeleport(d3, d4, d5, f, f1, Collections.emptySet()); // CraftBukkit - SPIGOT-1807: Don't call teleport event, when the client thinks the player is falling, because the chunks are not loaded on the client yet.
++ this.player.doCheckFallDamage(this.player.getX() - d3, this.player.getY() - d4, this.player.getZ() - d5, packet.isOnGround());
+ } else {
++ // CraftBukkit start - fire PlayerMoveEvent
++ // Reset to old location first
++ this.player.absMoveTo(prevX, prevY, prevZ, prevYaw, prevPitch);
++
++ Player player = this.getCraftPlayer();
++ Location from = new Location(player.getWorld(), lastPosX, lastPosY, lastPosZ, lastYaw, lastPitch); // Get the Players previous Event location.
++ Location to = player.getLocation().clone(); // Start off the To location as the Players current location.
++
++ // If the packet contains movement information then we update the To location with the correct XYZ.
++ if (packet.hasPos) {
++ to.setX(packet.x);
++ to.setY(packet.y);
++ to.setZ(packet.z);
++ }
++
++ // If the packet contains look information then we update the To location with the correct Yaw & Pitch.
++ if (packet.hasRot) {
++ to.setYaw(packet.yRot);
++ to.setPitch(packet.xRot);
++ }
++
++ // Prevent 40 event-calls for less than a single pixel of movement >.>
++ double delta = Math.pow(this.lastPosX - to.getX(), 2) + Math.pow(this.lastPosY - to.getY(), 2) + Math.pow(this.lastPosZ - to.getZ(), 2);
++ float deltaAngle = Math.abs(this.lastYaw - to.getYaw()) + Math.abs(this.lastPitch - to.getPitch());
++
++ if ((delta > 1f / 256 || deltaAngle > 10f) && !this.player.isImmobile()) {
++ this.lastPosX = to.getX();
++ this.lastPosY = to.getY();
++ this.lastPosZ = to.getZ();
++ this.lastYaw = to.getYaw();
++ this.lastPitch = to.getPitch();
++
++ // Skip the first time we do this
++ if (from.getX() != Double.MAX_VALUE) {
++ Location oldTo = to.clone();
++ PlayerMoveEvent event = new PlayerMoveEvent(player, from, to);
++ this.cserver.getPluginManager().callEvent(event);
++
++ // If the event is cancelled we move the player back to their old location.
++ if (event.isCancelled()) {
++ teleport(from);
++ return;
++ }
++
++ // If a Plugin has changed the To destination then we teleport the Player
++ // there to avoid any 'Moved wrongly' or 'Moved too quickly' errors.
++ // We only do this if the Event was not cancelled.
++ if (!oldTo.equals(event.getTo()) && !event.isCancelled()) {
++ this.player.getBukkitEntity().teleport(event.getTo(), PlayerTeleportEvent.TeleportCause.PLUGIN);
++ return;
++ }
++
++ // Check to see if the Players Location has some how changed during the call of the event.
++ // This can happen due to a plugin teleporting the player instead of using .setTo()
++ if (!from.equals(this.getCraftPlayer().getLocation()) && this.justTeleported) {
++ this.justTeleported = false;
++ return;
++ }
++ }
++ }
++ // CraftBukkit end
+ this.player.absMoveTo(d0, d1, d2, f, f1);
+ this.clientIsFloating = d11 >= -0.03125D && !flag1 && this.player.gameMode.getGameModeForPlayer() != GameType.SPECTATOR && !this.server.isFlightAllowed() && !this.player.getAbilities().mayfly && !this.player.hasEffect(MobEffects.LEVITATION) && !this.player.isFallFlying() && !this.player.isAutoSpinAttack() && this.noBlocksAround(this.player);
+ this.player.serverLevel().getChunkSource().move(this.player);
+@@ -1081,11 +1334,68 @@
+ return true;
+ }
+
+- public void teleport(double d0, double d1, double d2, float f, float f1) {
+- this.teleport(d0, d1, d2, f, f1, Collections.emptySet());
++ // CraftBukkit start - Delegate to teleport(Location)
++ public void teleport(double x, double d1, double y, float f, float z) {
++ this.teleport(x, d1, y, f, z, PlayerTeleportEvent.TeleportCause.UNKNOWN);
+ }
+
+- public void teleport(double d0, double d1, double d2, float f, float f1, Set<RelativeMovement> set) {
++ public void teleport(double d0, double d1, double d2, float f, float f1, PlayerTeleportEvent.TeleportCause cause) {
++ this.teleport(d0, d1, d2, f, f1, Collections.emptySet(), cause);
++ }
++
++ public void teleport(double x, double d1, double y, float f, float z, Set<RelativeMovement> set) {
++ this.teleport(x, d1, y, f, z, set, PlayerTeleportEvent.TeleportCause.UNKNOWN);
++ }
++
++ public boolean teleport(double d0, double d1, double d2, float f, float f1, Set<RelativeMovement> set, PlayerTeleportEvent.TeleportCause cause) { // CraftBukkit - Return event status
++ Player player = this.getCraftPlayer();
++ Location from = player.getLocation();
++
++ double x = d0;
++ double y = d1;
++ double z = d2;
++ float yaw = f;
++ float pitch = f1;
++
++ Location to = new Location(this.getCraftPlayer().getWorld(), x, y, z, yaw, pitch);
++ // SPIGOT-5171: Triggered on join
++ if (from.equals(to)) {
++ this.internalTeleport(d0, d1, d2, f, f1, set);
++ return false; // CraftBukkit - Return event status
++ }
++
++ PlayerTeleportEvent event = new PlayerTeleportEvent(player, from.clone(), to.clone(), cause);
++ this.cserver.getPluginManager().callEvent(event);
++
++ if (event.isCancelled() || !to.equals(event.getTo())) {
++ set.clear(); // Can't relative teleport
++ to = event.isCancelled() ? event.getFrom() : event.getTo();
++ d0 = to.getX();
++ d1 = to.getY();
++ d2 = to.getZ();
++ f = to.getYaw();
++ f1 = to.getPitch();
++ }
++
++ this.internalTeleport(d0, d1, d2, f, f1, set);
++ return event.isCancelled(); // CraftBukkit - Return event status
++ }
++
++ public void teleport(Location dest) {
++ internalTeleport(dest.getX(), dest.getY(), dest.getZ(), dest.getYaw(), dest.getPitch(), Collections.emptySet());
++ }
++
++ private void internalTeleport(double d0, double d1, double d2, float f, float f1, Set<RelativeMovement> set) {
++ // CraftBukkit start
++ if (Float.isNaN(f)) {
++ f = 0;
++ }
++ if (Float.isNaN(f1)) {
++ f1 = 0;
++ }
++
++ this.justTeleported = true;
++ // CraftBukkit end
+ double d3 = set.contains(RelativeMovement.X) ? this.player.getX() : 0.0D;
+ double d4 = set.contains(RelativeMovement.Y) ? this.player.getY() : 0.0D;
+ double d5 = set.contains(RelativeMovement.Z) ? this.player.getZ() : 0.0D;
+@@ -1097,16 +1407,24 @@
+ this.awaitingTeleport = 0;
+ }
+
++ // CraftBukkit start - update last location
++ this.lastPosX = this.awaitingPositionFromClient.x;
++ this.lastPosY = this.awaitingPositionFromClient.y;
++ this.lastPosZ = this.awaitingPositionFromClient.z;
++ this.lastYaw = f;
++ this.lastPitch = f1;
++ // CraftBukkit end
++
+ this.awaitingTeleportTime = this.tickCount;
+ this.player.absMoveTo(d0, d1, d2, f, f1);
+ this.player.connection.send(new ClientboundPlayerPositionPacket(d0 - d3, d1 - d4, d2 - d5, f - f2, f1 - f3, set, this.awaitingTeleport));
+ }
+
+ @Override
+- @Override
+- public void handlePlayerAction(ServerboundPlayerActionPacket serverboundplayeractionpacket) {
+- PacketUtils.ensureRunningOnSameThread(serverboundplayeractionpacket, this, this.player.serverLevel());
+- BlockPos blockpos = serverboundplayeractionpacket.getPos();
++ public void handlePlayerAction(ServerboundPlayerActionPacket packet) {
++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
++ if (this.player.isImmobile()) return; // CraftBukkit
++ BlockPos blockposition = packet.getPos();
+
+ this.player.resetLastActionTime();
+ ServerboundPlayerActionPacket.Action serverboundplayeractionpacket_action = serverboundplayeractionpacket.getAction();
+@@ -1116,14 +1434,46 @@
+ if (!this.player.isSpectator()) {
+ ItemStack itemstack = this.player.getItemInHand(InteractionHand.OFF_HAND);
+
+- this.player.setItemInHand(InteractionHand.OFF_HAND, this.player.getItemInHand(InteractionHand.MAIN_HAND));
+- this.player.setItemInHand(InteractionHand.MAIN_HAND, itemstack);
++ // CraftBukkit start - inspiration taken from DispenserRegistry (See SpigotCraft#394)
++ CraftItemStack mainHand = CraftItemStack.asCraftMirror(itemstack);
++ CraftItemStack offHand = CraftItemStack.asCraftMirror(this.player.getItemInHand(EnumHand.MAIN_HAND));
++ PlayerSwapHandItemsEvent swapItemsEvent = new PlayerSwapHandItemsEvent(getCraftPlayer(), mainHand.clone(), offHand.clone());
++ this.cserver.getPluginManager().callEvent(swapItemsEvent);
++ if (swapItemsEvent.isCancelled()) {
++ return;
++ }
++ if (swapItemsEvent.getOffHandItem().equals(offHand)) {
++ this.player.setItemInHand(EnumHand.OFF_HAND, this.player.getItemInHand(EnumHand.MAIN_HAND));
++ } else {
++ this.player.setItemInHand(EnumHand.OFF_HAND, CraftItemStack.asNMSCopy(swapItemsEvent.getOffHandItem()));
++ }
++ if (swapItemsEvent.getMainHandItem().equals(mainHand)) {
++ this.player.setItemInHand(EnumHand.MAIN_HAND, itemstack);
++ } else {
++ this.player.setItemInHand(EnumHand.MAIN_HAND, CraftItemStack.asNMSCopy(swapItemsEvent.getMainHandItem()));
++ }
++ // CraftBukkit end
+ this.player.stopUsingItem();
+ }
+
+ return;
+ case DROP_ITEM:
+ if (!this.player.isSpectator()) {
++ // limit how quickly items can be dropped
++ // If the ticks aren't the same then the count starts from 0 and we update the lastDropTick.
++ if (this.lastDropTick != MinecraftServer.currentTick) {
++ this.dropCount = 0;
++ this.lastDropTick = MinecraftServer.currentTick;
++ } else {
++ // Else we increment the drop count and check the amount.
++ this.dropCount++;
++ if (this.dropCount >= 20) {
++ LOGGER.warn(this.player.getScoreboardName() + " dropped their items too quickly!");
++ this.disconnect("You dropped your items too quickly (Hacking?)");
++ return;
++ }
++ }
++ // CraftBukkit end
+ this.player.drop(false);
+ }
+
+@@ -1159,13 +1509,13 @@
+ }
+
+ @Override
+- @Override
+- public void handleUseItemOn(ServerboundUseItemOnPacket serverbounduseitemonpacket) {
+- PacketUtils.ensureRunningOnSameThread(serverbounduseitemonpacket, this, this.player.serverLevel());
+- this.player.connection.ackBlockChangesUpTo(serverbounduseitemonpacket.getSequence());
+- ServerLevel serverlevel = this.player.serverLevel();
+- InteractionHand interactionhand = serverbounduseitemonpacket.getHand();
+- ItemStack itemstack = this.player.getItemInHand(interactionhand);
++ public void handleUseItemOn(ServerboundUseItemOnPacket packet) {
++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
++ if (this.player.isImmobile()) return; // CraftBukkit
++ this.player.connection.ackBlockChangesUpTo(packet.getSequence());
++ ServerLevel worldserver = this.player.serverLevel();
++ EnumHand enumhand = packet.getHand();
++ ItemStack itemstack = this.player.getItemInHand(enumhand);
+
+ if (itemstack.isItemEnabled(serverlevel.enabledFeatures())) {
+ BlockHitResult blockhitresult = serverbounduseitemonpacket.getHitResult();
+@@ -1183,9 +1533,10 @@
+ this.player.resetLastActionTime();
+ int i = this.player.level().getMaxBuildHeight();
+
+- if (blockpos.getY() < i) {
+- if (this.awaitingPositionFromClient == null && this.player.distanceToSqr((double) blockpos.getX() + 0.5D, (double) blockpos.getY() + 0.5D, (double) blockpos.getZ() + 0.5D) < 64.0D && serverlevel.mayInteract(this.player, blockpos)) {
+- InteractionResult interactionresult = this.player.gameMode.useItemOn(this.player, serverlevel, itemstack, interactionhand, blockhitresult);
++ if (blockposition.getY() < i) {
++ if (this.awaitingPositionFromClient == null && this.player.distanceToSqr((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D) < 64.0D && worldserver.mayInteract(this.player, blockposition)) {
++ this.player.stopUsingItem(); // CraftBukkit - SPIGOT-4706
++ InteractionResult enuminteractionresult = this.player.gameMode.useItemOn(this.player, worldserver, itemstack, enumhand, movingobjectpositionblock);
+
+ if (direction == Direction.UP && !interactionresult.consumesAction() && blockpos.getY() >= i - 1 && wasBlockPlacementAttempt(this.player, itemstack)) {
+ MutableComponent mutablecomponent = Component.translatable("build.tooHigh", i - 1).withStyle(ChatFormatting.RED);
+@@ -1211,22 +1562,44 @@
+ }
+
+ @Override
+- @Override
+- public void handleUseItem(ServerboundUseItemPacket serverbounduseitempacket) {
+- PacketUtils.ensureRunningOnSameThread(serverbounduseitempacket, this, this.player.serverLevel());
+- this.ackBlockChangesUpTo(serverbounduseitempacket.getSequence());
+- ServerLevel serverlevel = this.player.serverLevel();
+- InteractionHand interactionhand = serverbounduseitempacket.getHand();
+- ItemStack itemstack = this.player.getItemInHand(interactionhand);
++ public void handleUseItem(ServerboundUseItemPacket packet) {
++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
++ if (this.player.isImmobile()) return; // CraftBukkit
++ this.ackBlockChangesUpTo(packet.getSequence());
++ ServerLevel worldserver = this.player.serverLevel();
++ EnumHand enumhand = packet.getHand();
++ ItemStack itemstack = this.player.getItemInHand(enumhand);
+
+ this.player.resetLastActionTime();
+- if (!itemstack.isEmpty() && itemstack.isItemEnabled(serverlevel.enabledFeatures())) {
+- InteractionResult interactionresult = this.player.gameMode.useItem(this.player, serverlevel, itemstack, interactionhand);
++ if (!itemstack.isEmpty() && itemstack.isItemEnabled(worldserver.enabledFeatures())) {
++ // CraftBukkit start
++ // Raytrace to look for 'rogue armswings'
++ float f1 = this.player.getXRot();
++ float f2 = this.player.getYRot();
++ double d0 = this.player.getX();
++ double d1 = this.player.getY() + (double) this.player.getEyeHeight();
++ double d2 = this.player.getZ();
++ Vec3 vec3d = new Vec3(d0, d1, d2);
+
+ if (interactionresult.shouldSwing()) {
+ this.player.swing(interactionhand, true);
+ }
+
++ if (cancelled) {
++ this.player.getBukkitEntity().updateInventory(); // SPIGOT-2524
++ return;
++ }
++ itemstack = this.player.getItemInHand(enumhand); // Update in case it was changed in the event
++ if (itemstack.isEmpty()) {
++ return;
++ }
++ // CraftBukkit end
++ InteractionResult enuminteractionresult = this.player.gameMode.useItem(this.player, worldserver, itemstack, enumhand);
++
++ if (enuminteractionresult.shouldSwing()) {
++ this.player.swing(enumhand, true);
++ }
++
+ }
+ }
+
+@@ -1242,7 +1635,7 @@
+ Entity entity = serverboundteleporttoentitypacket.getEntity(serverlevel);
+
+ if (entity != null) {
+- this.player.teleportTo(serverlevel, entity.getX(), entity.getY(), entity.getZ(), entity.getYRot(), entity.getXRot());
++ this.player.teleportTo(worldserver, entity.getX(), entity.getY(), entity.getZ(), entity.getYRot(), entity.getXRot(), org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.SPECTATE); // CraftBukkit
+ return;
+ }
+ }
+@@ -1265,19 +1657,31 @@
+ }
+
+ @Override
+- @Override
+- public void onDisconnect(Component component) {
+- ServerGamePacketListenerImpl.LOGGER.info("{} lost connection: {}", this.player.getName().getString(), component.getString());
++ public void onDisconnect(Component reason) {
++ // CraftBukkit start - Rarely it would send a disconnect line twice
++ if (this.processedDisconnect) {
++ return;
++ } else {
++ this.processedDisconnect = true;
++ }
++ // CraftBukkit end
++ ServerGamePacketListenerImpl.LOGGER.info("{} lost connection: {}", this.player.getName().getString(), reason.getString());
+ this.removePlayerFromWorld();
+ super.onDisconnect(component);
+ }
+
+ private void removePlayerFromWorld() {
+ this.chatMessageChain.close();
++ // CraftBukkit start - Replace vanilla quit message handling with our own.
++ /*
+ this.server.invalidateStatus();
+ this.server.getPlayerList().broadcastSystemMessage(Component.translatable("multiplayer.player.left", this.player.getDisplayName()).withStyle(ChatFormatting.YELLOW), false);
+ this.player.disconnect();
+- this.server.getPlayerList().remove(this.player);
++ String quitMessage = this.server.getPlayerList().remove(this.player);
++ if ((quitMessage != null) && (quitMessage.length() > 0)) {
++ this.server.getPlayerList().broadcastMessage(CraftChatMessage.fromString(quitMessage));
++ }
++ // CraftBukkit end
+ this.player.getTextFilter().leave();
+ }
+
+@@ -1290,11 +1696,19 @@
+ }
+
+ @Override
+- @Override
+- public void handleSetCarriedItem(ServerboundSetCarriedItemPacket serverboundsetcarrieditempacket) {
+- PacketUtils.ensureRunningOnSameThread(serverboundsetcarrieditempacket, this, this.player.serverLevel());
+- if (serverboundsetcarrieditempacket.getSlot() >= 0 && serverboundsetcarrieditempacket.getSlot() < Inventory.getSelectionSize()) {
+- if (this.player.getInventory().selected != serverboundsetcarrieditempacket.getSlot() && this.player.getUsedItemHand() == InteractionHand.MAIN_HAND) {
++ public void handleSetCarriedItem(ServerboundSetCarriedItemPacket packet) {
++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
++ if (this.player.isImmobile()) return; // CraftBukkit
++ if (packet.getSlot() >= 0 && packet.getSlot() < Inventory.getSelectionSize()) {
++ PlayerItemHeldEvent event = new PlayerItemHeldEvent(this.getCraftPlayer(), this.player.getInventory().selected, packet.getSlot());
++ this.cserver.getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ this.send(new ClientboundSetCarriedItemPacket(this.player.getInventory().selected));
++ this.player.resetLastActionTime();
++ return;
++ }
++ // CraftBukkit end
++ if (this.player.getInventory().selected != packet.getSlot() && this.player.getUsedItemHand() == EnumHand.MAIN_HAND) {
+ this.player.stopUsingItem();
+ }
+
+@@ -1302,19 +1716,25 @@
+ this.player.resetLastActionTime();
+ } else {
+ ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString());
++ this.disconnect("Invalid hotbar selection (Hacking?)"); // CraftBukkit
+ }
+ }
+
+ @Override
+- @Override
+- public void handleChat(ServerboundChatPacket serverboundchatpacket) {
+- if (isChatMessageIllegal(serverboundchatpacket.message())) {
++ public void handleChat(ServerboundChatPacket packet) {
++ // CraftBukkit start - async chat
++ // SPIGOT-3638
++ if (this.server.isStopped()) {
++ return;
++ }
++ // CraftBukkit end
++ if (isChatMessageIllegal(packet.message())) {
+ this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters"));
+ } else {
+ Optional<LastSeenMessages> optional = this.tryHandleChat(serverboundchatpacket.lastSeenMessages());
+
+ if (optional.isPresent()) {
+- this.server.submit(() -> {
++ // this.server.submit(() -> { // CraftBukkit - async chat
+ PlayerChatMessage playerchatmessage;
+
+ try {
+@@ -1324,15 +1744,15 @@
+ return;
+ }
+
+- CompletableFuture<FilteredText> completablefuture = this.filterTextPacket(playerchatmessage.signedContent());
+- Component component = this.server.getChatDecorator().decorate(this.player, playerchatmessage.decoratedContent());
++ CompletableFuture<FilteredText> completablefuture = this.filterTextPacket(playerchatmessage.signedContent()).thenApplyAsync(Function.identity(), this.server.chatExecutor); // CraftBukkit - async chat
++ Component ichatbasecomponent = this.server.getChatDecorator().decorate(this.player, playerchatmessage.decoratedContent());
+
+ this.chatMessageChain.append(completablefuture, (filteredtext) -> {
+ PlayerChatMessage playerchatmessage1 = playerchatmessage.withUnsignedContent(component).filter(filteredtext.mask());
+
+ this.broadcastChatMessage(playerchatmessage1);
+ });
+- });
++ // }); // CraftBukkit - async chat
+ }
+
+ }
+@@ -1348,7 +1767,13 @@
+
+ if (optional.isPresent()) {
+ this.server.submit(() -> {
+- this.performChatCommand(serverboundchatcommandpacket, (LastSeenMessages) optional.get());
++ // CraftBukkit start - SPIGOT-7346: Prevent disconnected players from executing commands
++ if (player.hasDisconnected()) {
++ return;
++ }
++ // CraftBukkit end
++
++ this.performChatCommand(packet, (LastSeenMessages) optional.get());
+ this.detectRateSpam();
+ });
+ }
+@@ -1356,15 +1781,28 @@
+ }
+ }
+
+- private void performChatCommand(ServerboundChatCommandPacket serverboundchatcommandpacket, LastSeenMessages lastseenmessages) {
+- ParseResults parseresults = this.parseCommand(serverboundchatcommandpacket.command());
++ private void performChatCommand(ServerboundChatCommandPacket packet, LastSeenMessages lastSeenMessages) {
++ // CraftBukkit start
++ String command = "/" + packet.command();
++ ServerGamePacketListenerImpl.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + command);
+
++ PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(getCraftPlayer(), command, new LazyPlayerSet(server));
++ this.cserver.getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return;
++ }
++ command = event.getMessage().substring(1);
++
++ ParseResults parseresults = this.parseCommand(command);
++ // CraftBukkit end
++
+ Map map;
+
+ try {
+- map = this.collectSignedArguments(serverboundchatcommandpacket, SignableCommand.of(parseresults), lastseenmessages);
+- } catch (SignedMessageChain.DecodeException signedmessagechain_decodeexception) {
+- this.handleMessageDecodeFailure(signedmessagechain_decodeexception);
++ map = (packet.command().equals(command)) ? this.collectSignedArguments(packet, SignableCommand.of(parseresults), lastSeenMessages) : Collections.emptyMap(); // CraftBukkit
++ } catch (SignedMessageChain.DecodeException signedmessagechain_a) {
++ this.handleMessageDecodeFailure(signedmessagechain_a);
+ return;
+ }
+
+@@ -1373,7 +1811,7 @@
+ parseresults = Commands.mapSource(parseresults, (commandsourcestack) -> {
+ return commandsourcestack.withSigningContext(commandsigningcontext_signedarguments, this.chatMessageChain);
+ });
+- this.server.getCommands().performCommand(parseresults, serverboundchatcommandpacket.command());
++ this.server.getCommands().performCommand(parseresults, command); // CraftBukkit
+ }
+
+ private void handleMessageDecodeFailure(SignedMessageChain.DecodeException signedmessagechain_decodeexception) {
+@@ -1410,7 +1848,7 @@
+ private Optional<LastSeenMessages> tryHandleChat(LastSeenMessages.Update lastseenmessages_update) {
+ Optional<LastSeenMessages> optional = this.unpackAndApplyLastSeen(lastseenmessages_update);
+
+- if (this.player.getChatVisibility() == ChatVisiblity.HIDDEN) {
++ if (this.player.isRemoved() || this.player.getChatVisibility() == ChatVisiblity.HIDDEN) { // CraftBukkit - dead men tell no tales
+ this.send(new ClientboundSystemChatPacket(Component.translatable("chat.disabled.options").withStyle(ChatFormatting.RED), false));
+ return Optional.empty();
+ } else {
+@@ -1444,20 +1882,74 @@
+ return false;
+ }
+
+- private PlayerChatMessage getSignedMessage(ServerboundChatPacket serverboundchatpacket, LastSeenMessages lastseenmessages) throws SignedMessageChain.DecodeException {
+- SignedMessageBody signedmessagebody = new SignedMessageBody(serverboundchatpacket.message(), serverboundchatpacket.timeStamp(), serverboundchatpacket.salt(), lastseenmessages);
++ // CraftBukkit start - add method
++ public void chat(String s, PlayerChatMessage original, boolean async) {
++ if (s.isEmpty() || this.player.getChatVisibility() == ChatVisiblity.HIDDEN) {
++ return;
++ }
++ OutgoingChatMessage outgoing = OutgoingChatMessage.create(original);
+
+ return this.signedMessageDecoder.unpack(serverboundchatpacket.signature(), signedmessagebody);
+ }
+
+- private void broadcastChatMessage(PlayerChatMessage playerchatmessage) {
+- this.server.getPlayerList().broadcastChatMessage(playerchatmessage, this.player, ChatType.bind(ChatType.CHAT, (Entity) this.player));
++ private void handleCommand(String s) {
++ this.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + s);
++
++ CraftPlayer player = this.getCraftPlayer();
++
++ PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(player, s, new LazyPlayerSet(server));
++ this.cserver.getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return;
++ }
++
++ try {
++ if (this.cserver.dispatchCommand(event.getPlayer(), event.getMessage().substring(1))) {
++ return;
++ }
++ } catch (org.bukkit.command.CommandException ex) {
++ player.sendMessage(org.bukkit.ChatColor.RED + "An internal error occurred while attempting to perform this command");
++ java.util.logging.Logger.getLogger(ServerGamePacketListenerImpl.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
++ return;
++ }
++ }
++ // CraftBukkit end
++
++ private PlayerChatMessage getSignedMessage(ServerboundChatPacket packet, LastSeenMessages lastSeenMessages) throws SignedMessageChain.DecodeException {
++ SignedMessageBody signedmessagebody = new SignedMessageBody(packet.message(), packet.timeStamp(), packet.salt(), lastSeenMessages);
++
++ return this.signedMessageDecoder.unpack(packet.signature(), signedmessagebody);
++ }
++
++ private void broadcastChatMessage(PlayerChatMessage message) {
++ // CraftBukkit start
++ String s = message.signedContent();
++ if (s.isEmpty()) {
++ LOGGER.warn(this.player.getScoreboardName() + " tried to send an empty message");
++ } else if (getCraftPlayer().isConversing()) {
++ final String conversationInput = s;
++ this.server.processQueue.add(new Runnable() {
++ @Override
++ public void run() {
++ getCraftPlayer().acceptConversationInput(conversationInput);
++ }
++ });
++ } else if (this.player.getChatVisibility() == ChatVisiblity.SYSTEM) { // Re-add "Command Only" flag check
++ this.send(new ClientboundSystemChatPacket(Component.translatable("chat.cannotSend").withStyle(ChatFormatting.RED), false));
++ } else {
++ this.chat(s, message, true);
++ }
++ // this.server.getPlayerList().broadcastChatMessage(playerchatmessage, this.player, ChatMessageType.bind(ChatMessageType.CHAT, (Entity) this.player));
++ // CraftBukkit end
+ this.detectRateSpam();
+ }
+
+ private void detectRateSpam() {
+- this.chatSpamTickCount += 20;
+- if (this.chatSpamTickCount > 200 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) {
++ // CraftBukkit start - replaced with thread safe throttle
++ // this.chatSpamTickCount += 20;
++ if (this.chatSpamTickCount.addAndGet(20) > 200 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) {
++ // CraftBukkit end
+ this.disconnect(Component.translatable("disconnect.spam"));
+ }
+
+@@ -1478,17 +2045,64 @@
+ }
+
+ @Override
+- @Override
+- public void handleAnimate(ServerboundSwingPacket serverboundswingpacket) {
+- PacketUtils.ensureRunningOnSameThread(serverboundswingpacket, this, this.player.serverLevel());
++ public void handleAnimate(ServerboundSwingPacket packet) {
++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
++ if (this.player.isImmobile()) return; // CraftBukkit
+ this.player.resetLastActionTime();
+- this.player.swing(serverboundswingpacket.getHand());
++ // CraftBukkit start - Raytrace to look for 'rogue armswings'
++ float f1 = this.player.getXRot();
++ float f2 = this.player.getYRot();
++ double d0 = this.player.getX();
++ double d1 = this.player.getY() + (double) this.player.getEyeHeight();
++ double d2 = this.player.getZ();
++ Location origin = new Location(this.player.level().getWorld(), d0, d1, d2, f2, f1);
++
++ double d3 = player.gameMode.getGameModeForPlayer() == GameType.CREATIVE ? 5.0D : 4.5D;
++ // SPIGOT-5607: Only call interact event if no block or entity is being clicked. Use bukkit ray trace method, because it handles blocks and entities at the same time
++ // SPIGOT-7429: Make sure to call PlayerInteractEvent for spectators and non-pickable entities
++ org.bukkit.util.RayTraceResult result = this.player.level().getWorld().rayTrace(origin, origin.getDirection(), d3, org.bukkit.FluidCollisionMode.NEVER, false, 0.1, entity -> {
++ Entity handle = ((CraftEntity) entity).getHandle();
++ return entity != this.player.getBukkitEntity() && this.player.getBukkitEntity().canSee(entity) && !handle.isSpectator() && handle.isPickable() && !handle.isPassengerOfSameVehicle(player);
++ });
++ if (result == null) {
++ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_AIR, this.player.getInventory().getSelected(), EnumHand.MAIN_HAND);
++ }
++
++ // Arm swing animation
++ PlayerAnimationEvent event = new PlayerAnimationEvent(this.getCraftPlayer(), (packet.getHand() == EnumHand.MAIN_HAND) ? PlayerAnimationType.ARM_SWING : PlayerAnimationType.OFF_ARM_SWING);
++ this.cserver.getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) return;
++ // CraftBukkit end
++ this.player.swing(packet.getHand());
+ }
+
+ @Override
+- @Override
+- public void handlePlayerCommand(ServerboundPlayerCommandPacket serverboundplayercommandpacket) {
+- PacketUtils.ensureRunningOnSameThread(serverboundplayercommandpacket, this, this.player.serverLevel());
++ public void handlePlayerCommand(ServerboundPlayerCommandPacket packet) {
++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
++ // CraftBukkit start
++ if (this.player.isRemoved()) return;
++ switch (packet.getAction()) {
++ case PRESS_SHIFT_KEY:
++ case RELEASE_SHIFT_KEY:
++ PlayerToggleSneakEvent event = new PlayerToggleSneakEvent(this.getCraftPlayer(), packet.getAction() == ServerboundPlayerCommandPacket.EnumPlayerAction.PRESS_SHIFT_KEY);
++ this.cserver.getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return;
++ }
++ break;
++ case START_SPRINTING:
++ case STOP_SPRINTING:
++ PlayerToggleSprintEvent e2 = new PlayerToggleSprintEvent(this.getCraftPlayer(), packet.getAction() == ServerboundPlayerCommandPacket.EnumPlayerAction.START_SPRINTING);
++ this.cserver.getPluginManager().callEvent(e2);
++
++ if (e2.isCancelled()) {
++ return;
++ }
++ break;
++ }
++ // CraftBukkit end
+ this.player.resetLastActionTime();
+ Entity entity;
+ PlayerRideableJumping playerrideablejumping;
+@@ -1569,9 +2183,15 @@
+ }
+ }
+
+- public void sendPlayerChatMessage(PlayerChatMessage playerchatmessage, ChatType.Bound chattype_bound) {
+- this.send(new ClientboundPlayerChatPacket(playerchatmessage.link().sender(), playerchatmessage.link().index(), playerchatmessage.signature(), playerchatmessage.signedBody().pack(this.messageSignatureCache), playerchatmessage.unsignedContent(), playerchatmessage.filterMask(), chattype_bound.toNetwork(this.player.level().registryAccess())));
+- this.addPendingMessage(playerchatmessage);
++ public void sendPlayerChatMessage(PlayerChatMessage chatMessage, ChatType.Bound boundType) {
++ // CraftBukkit start - SPIGOT-7262: if hidden we have to send as disguised message. Query whether we should send at all (but changing this may not be expected).
++ if (!getCraftPlayer().canSee(chatMessage.link().sender())) {
++ sendDisguisedChatMessage(chatMessage.decoratedContent(), boundType);
++ return;
++ }
++ // CraftBukkit end
++ this.send(new ClientboundPlayerChatPacket(chatMessage.link().sender(), chatMessage.link().index(), chatMessage.signature(), chatMessage.signedBody().pack(this.messageSignatureCache), chatMessage.unsignedContent(), chatMessage.filterMask(), boundType.toNetwork(this.player.level().registryAccess())));
++ this.addPendingMessage(chatMessage);
+ }
+
+ public void sendDisguisedChatMessage(Component component, ChatType.Bound chattype_bound) {
+@@ -1595,11 +2214,11 @@
+ }
+
+ @Override
+- @Override
+- public void handleInteract(ServerboundInteractPacket serverboundinteractpacket) {
+- PacketUtils.ensureRunningOnSameThread(serverboundinteractpacket, this, this.player.serverLevel());
+- final ServerLevel serverlevel = this.player.serverLevel();
+- final Entity entity = serverboundinteractpacket.getTarget(serverlevel);
++ public void handleInteract(ServerboundInteractPacket packet) {
++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
++ if (this.player.isImmobile()) return; // CraftBukkit
++ final ServerLevel worldserver = this.player.serverLevel();
++ final Entity entity = packet.getTarget(worldserver);
+
+ this.player.resetLastActionTime();
+ this.player.setShiftKeyDown(serverboundinteractpacket.isUsingSecondaryAction());
+@@ -1610,16 +2229,54 @@
+
+ AABB aabb = entity.getBoundingBox();
+
+- if (aabb.distanceToSqr(this.player.getEyePosition()) < ServerGamePacketListenerImpl.MAX_INTERACTION_DISTANCE) {
+- serverboundinteractpacket.dispatch(new ServerboundInteractPacket.Handler() {
+- private void performInteraction(InteractionHand interactionhand, ServerGamePacketListenerImpl.EntityInteraction servergamepacketlistenerimpl_entityinteraction) {
+- ItemStack itemstack = ServerGamePacketListenerImpl.this.player.getItemInHand(interactionhand);
++ if (axisalignedbb.distanceToSqr(this.player.getEyePosition()) < ServerGamePacketListenerImpl.MAX_INTERACTION_DISTANCE) {
++ packet.dispatch(new ServerboundInteractPacket.Handler() {
++ private void performInteraction(EnumHand enumhand, ServerGamePacketListenerImpl.EntityInteraction playerconnection_a, PlayerInteractEntityEvent event) { // CraftBukkit
++ ItemStack itemstack = ServerGamePacketListenerImpl.this.player.getItemInHand(enumhand);
+
+ if (itemstack.isItemEnabled(serverlevel.enabledFeatures())) {
+ ItemStack itemstack1 = itemstack.copy();
+- InteractionResult interactionresult = servergamepacketlistenerimpl_entityinteraction.run(ServerGamePacketListenerImpl.this.player, entity, interactionhand);
++ // CraftBukkit start
++ ItemStack itemInHand = ServerGamePacketListenerImpl.this.player.getItemInHand(enumhand);
++ boolean triggerLeashUpdate = itemInHand != null && itemInHand.getItem() == Items.LEAD && entity instanceof Mob;
++ Item origItem = player.getInventory().getSelected() == null ? null : player.getInventory().getSelected().getItem();
+
+- if (interactionresult.consumesAction()) {
++ cserver.getPluginManager().callEvent(event);
++
++ // Entity in bucket - SPIGOT-4048 and SPIGOT-6859a
++ if ((entity instanceof Bucketable && entity instanceof LivingEntity && origItem != null && origItem.asItem() == Items.WATER_BUCKET) && (event.isCancelled() || player.getInventory().getSelected() == null || player.getInventory().getSelected().getItem() != origItem)) {
++ send(new ClientboundAddEntityPacket(entity));
++ player.containerMenu.sendAllDataToRemote();
++ }
++
++ if (triggerLeashUpdate && (event.isCancelled() || player.getInventory().getSelected() == null || player.getInventory().getSelected().getItem() != origItem)) {
++ // Refresh the current leash state
++ send(new ClientboundSetEntityLinkPacket(entity, ((Mob) entity).getLeashHolder()));
++ }
++
++ if (event.isCancelled() || player.getInventory().getSelected() == null || player.getInventory().getSelected().getItem() != origItem) {
++ // Refresh the current entity metadata
++ entity.getEntityData().refresh(player);
++ // SPIGOT-7136 - Allays
++ if (entity instanceof Allay) {
++ send(new ClientboundSetEquipmentPacket(entity.getId(), Arrays.stream(net.minecraft.world.entity.EquipmentSlot.values()).map((slot) -> Pair.of(slot, ((LivingEntity) entity).getItemBySlot(slot).copy())).collect(Collectors.toList())));
++ player.containerMenu.sendAllDataToRemote();
++ }
++ }
++
++ if (event.isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
++ InteractionResult enuminteractionresult = playerconnection_a.run(ServerGamePacketListenerImpl.this.player, entity, enumhand);
++
++ // CraftBukkit start
++ if (!itemInHand.isEmpty() && itemInHand.getCount() <= -1) {
++ player.containerMenu.sendAllDataToRemote();
++ }
++ // CraftBukkit end
++
++ if (enuminteractionresult.consumesAction()) {
+ CriteriaTriggers.PLAYER_INTERACTED_WITH_ENTITY.trigger(ServerGamePacketListenerImpl.this.player, itemstack1, entity);
+ if (interactionresult.shouldSwing()) {
+ ServerGamePacketListenerImpl.this.player.swing(interactionhand, true);
+@@ -1630,27 +2287,31 @@
+ }
+
+ @Override
+- @Override
+- public void onInteraction(InteractionHand interactionhand) {
+- this.performInteraction(interactionhand, Player::interactOn);
++ public void onInteraction(EnumHand hand) {
++ this.performInteraction(hand, net.minecraft.world.entity.player.Player::interactOn, new PlayerInteractEntityEvent(getCraftPlayer(), entity.getBukkitEntity(), (hand == EnumHand.OFF_HAND) ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND)); // CraftBukkit
+ }
+
+ @Override
+- @Override
+- public void onInteraction(InteractionHand interactionhand, Vec3 vec3) {
+- this.performInteraction(interactionhand, (serverplayer, entity1, interactionhand1) -> {
+- return entity1.interactAt(serverplayer, vec3, interactionhand1);
+- });
++ public void onInteraction(EnumHand hand, Vec3 interactionLocation) {
++ this.performInteraction(hand, (entityplayer, entity1, enumhand1) -> {
++ return entity1.interactAt(entityplayer, interactionLocation, enumhand1);
++ }, new PlayerInteractAtEntityEvent(getCraftPlayer(), entity.getBukkitEntity(), new org.bukkit.util.Vector(interactionLocation.x, interactionLocation.y, interactionLocation.z), (hand == EnumHand.OFF_HAND) ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND)); // CraftBukkit
+ }
+
+ @Override
+ @Override
+ public void onAttack() {
+- if (!(entity instanceof ItemEntity) && !(entity instanceof ExperienceOrb) && !(entity instanceof AbstractArrow) && entity != ServerGamePacketListenerImpl.this.player) {
+- ItemStack itemstack = ServerGamePacketListenerImpl.this.player.getItemInHand(InteractionHand.MAIN_HAND);
++ // CraftBukkit
++ if (!(entity instanceof ItemEntity) && !(entity instanceof ExperienceOrb) && !(entity instanceof AbstractArrow) && (entity != ServerGamePacketListenerImpl.this.player || player.isSpectator())) {
++ ItemStack itemstack = ServerGamePacketListenerImpl.this.player.getItemInHand(EnumHand.MAIN_HAND);
+
+ if (itemstack.isItemEnabled(serverlevel.enabledFeatures())) {
+ ServerGamePacketListenerImpl.this.player.attack(entity);
++ // CraftBukkit start
++ if (!itemstack.isEmpty() && itemstack.getCount() <= -1) {
++ player.containerMenu.sendAllDataToRemote();
++ }
++ // CraftBukkit end
+ }
+ } else {
+ ServerGamePacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.invalid_entity_attacked"));
+@@ -1695,19 +2354,23 @@
+ }
+
+ @Override
+- @Override
+- public void handleContainerClose(ServerboundContainerClosePacket serverboundcontainerclosepacket) {
+- PacketUtils.ensureRunningOnSameThread(serverboundcontainerclosepacket, this, this.player.serverLevel());
++ public void handleContainerClose(ServerboundContainerClosePacket packet) {
++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
++
++ if (this.player.isImmobile()) return; // CraftBukkit
++ CraftEventFactory.handleInventoryCloseEvent(this.player); // CraftBukkit
++
+ this.player.doCloseContainer();
+ }
+
+ @Override
+- @Override
+- public void handleContainerClick(ServerboundContainerClickPacket serverboundcontainerclickpacket) {
+- PacketUtils.ensureRunningOnSameThread(serverboundcontainerclickpacket, this, this.player.serverLevel());
++ public void handleContainerClick(ServerboundContainerClickPacket packet) {
++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
++ if (this.player.isImmobile()) return; // CraftBukkit
+ this.player.resetLastActionTime();
+- if (this.player.containerMenu.containerId == serverboundcontainerclickpacket.getContainerId()) {
+- if (this.player.isSpectator()) {
++ if (this.player.containerMenu.containerId == packet.getContainerId() && this.player.containerMenu.stillValid(this.player)) { // CraftBukkit
++ boolean cancelled = this.player.isSpectator(); // CraftBukkit - see below if
++ if (false/*this.player.isSpectator()*/) { // CraftBukkit
+ this.player.containerMenu.sendAllDataToRemote();
+ } else if (!this.player.containerMenu.stillValid(this.player)) {
+ ServerGamePacketListenerImpl.LOGGER.debug("Player {} interacted with invalid menu {}", this.player, this.player.containerMenu);
+@@ -1720,9 +2383,286 @@
+ boolean flag = serverboundcontainerclickpacket.getStateId() != this.player.containerMenu.getStateId();
+
+ this.player.containerMenu.suppressRemoteUpdates();
+- this.player.containerMenu.clicked(i, serverboundcontainerclickpacket.getButtonNum(), serverboundcontainerclickpacket.getClickType(), this.player);
+- ObjectIterator objectiterator = Int2ObjectMaps.fastIterable(serverboundcontainerclickpacket.getChangedSlots()).iterator();
++ // CraftBukkit start - Call InventoryClickEvent
++ if (packet.getSlotNum() < -1 && packet.getSlotNum() != -999) {
++ return;
++ }
+
++ InventoryView inventory = this.player.containerMenu.getBukkitView();
++ SlotType type = inventory.getSlotType(packet.getSlotNum());
++
++ InventoryClickEvent event;
++ ClickType click = ClickType.UNKNOWN;
++ InventoryAction action = InventoryAction.UNKNOWN;
++
++ ItemStack itemstack = ItemStack.EMPTY;
++
++ switch (packet.getClickType()) {
++ case PICKUP:
++ if (packet.getButtonNum() == 0) {
++ click = ClickType.LEFT;
++ } else if (packet.getButtonNum() == 1) {
++ click = ClickType.RIGHT;
++ }
++ if (packet.getButtonNum() == 0 || packet.getButtonNum() == 1) {
++ action = InventoryAction.NOTHING; // Don't want to repeat ourselves
++ if (packet.getSlotNum() == -999) {
++ if (!player.containerMenu.getCarried().isEmpty()) {
++ action = packet.getButtonNum() == 0 ? InventoryAction.DROP_ALL_CURSOR : InventoryAction.DROP_ONE_CURSOR;
++ }
++ } else if (packet.getSlotNum() < 0) {
++ action = InventoryAction.NOTHING;
++ } else {
++ Slot slot = this.player.containerMenu.getSlot(packet.getSlotNum());
++ if (slot != null) {
++ ItemStack clickedItem = slot.getItem();
++ ItemStack cursor = player.containerMenu.getCarried();
++ if (clickedItem.isEmpty()) {
++ if (!cursor.isEmpty()) {
++ action = packet.getButtonNum() == 0 ? InventoryAction.PLACE_ALL : InventoryAction.PLACE_ONE;
++ }
++ } else if (slot.mayPickup(player)) {
++ if (cursor.isEmpty()) {
++ action = packet.getButtonNum() == 0 ? InventoryAction.PICKUP_ALL : InventoryAction.PICKUP_HALF;
++ } else if (slot.mayPlace(cursor)) {
++ if (ItemStack.isSameItemSameTags(clickedItem, cursor)) {
++ int toPlace = packet.getButtonNum() == 0 ? cursor.getCount() : 1;
++ toPlace = Math.min(toPlace, clickedItem.getMaxStackSize() - clickedItem.getCount());
++ toPlace = Math.min(toPlace, slot.container.getMaxStackSize() - clickedItem.getCount());
++ if (toPlace == 1) {
++ action = InventoryAction.PLACE_ONE;
++ } else if (toPlace == cursor.getCount()) {
++ action = InventoryAction.PLACE_ALL;
++ } else if (toPlace < 0) {
++ action = toPlace != -1 ? InventoryAction.PICKUP_SOME : InventoryAction.PICKUP_ONE; // this happens with oversized stacks
++ } else if (toPlace != 0) {
++ action = InventoryAction.PLACE_SOME;
++ }
++ } else if (cursor.getCount() <= slot.getMaxStackSize()) {
++ action = InventoryAction.SWAP_WITH_CURSOR;
++ }
++ } else if (ItemStack.isSameItemSameTags(cursor, clickedItem)) {
++ if (clickedItem.getCount() >= 0) {
++ if (clickedItem.getCount() + cursor.getCount() <= cursor.getMaxStackSize()) {
++ // As of 1.5, this is result slots only
++ action = InventoryAction.PICKUP_ALL;
++ }
++ }
++ }
++ }
++ }
++ }
++ }
++ break;
++ // TODO check on updates
++ case QUICK_MOVE:
++ if (packet.getButtonNum() == 0) {
++ click = ClickType.SHIFT_LEFT;
++ } else if (packet.getButtonNum() == 1) {
++ click = ClickType.SHIFT_RIGHT;
++ }
++ if (packet.getButtonNum() == 0 || packet.getButtonNum() == 1) {
++ if (packet.getSlotNum() < 0) {
++ action = InventoryAction.NOTHING;
++ } else {
++ Slot slot = this.player.containerMenu.getSlot(packet.getSlotNum());
++ if (slot != null && slot.mayPickup(this.player) && slot.hasItem()) {
++ action = InventoryAction.MOVE_TO_OTHER_INVENTORY;
++ } else {
++ action = InventoryAction.NOTHING;
++ }
++ }
++ }
++ break;
++ case SWAP:
++ if ((packet.getButtonNum() >= 0 && packet.getButtonNum() < 9) || packet.getButtonNum() == 40) {
++ click = (packet.getButtonNum() == 40) ? ClickType.SWAP_OFFHAND : ClickType.NUMBER_KEY;
++ Slot clickedSlot = this.player.containerMenu.getSlot(packet.getSlotNum());
++ if (clickedSlot.mayPickup(player)) {
++ ItemStack hotbar = this.player.getInventory().getItem(packet.getButtonNum());
++ boolean canCleanSwap = hotbar.isEmpty() || (clickedSlot.container == player.getInventory() && clickedSlot.mayPlace(hotbar)); // the slot will accept the hotbar item
++ if (clickedSlot.hasItem()) {
++ if (canCleanSwap) {
++ action = InventoryAction.HOTBAR_SWAP;
++ } else {
++ action = InventoryAction.HOTBAR_MOVE_AND_READD;
++ }
++ } else if (!clickedSlot.hasItem() && !hotbar.isEmpty() && clickedSlot.mayPlace(hotbar)) {
++ action = InventoryAction.HOTBAR_SWAP;
++ } else {
++ action = InventoryAction.NOTHING;
++ }
++ } else {
++ action = InventoryAction.NOTHING;
++ }
++ }
++ break;
++ case CLONE:
++ if (packet.getButtonNum() == 2) {
++ click = ClickType.MIDDLE;
++ if (packet.getSlotNum() < 0) {
++ action = InventoryAction.NOTHING;
++ } else {
++ Slot slot = this.player.containerMenu.getSlot(packet.getSlotNum());
++ if (slot != null && slot.hasItem() && player.getAbilities().instabuild && player.containerMenu.getCarried().isEmpty()) {
++ action = InventoryAction.CLONE_STACK;
++ } else {
++ action = InventoryAction.NOTHING;
++ }
++ }
++ } else {
++ click = ClickType.UNKNOWN;
++ action = InventoryAction.UNKNOWN;
++ }
++ break;
++ case THROW:
++ if (packet.getSlotNum() >= 0) {
++ if (packet.getButtonNum() == 0) {
++ click = ClickType.DROP;
++ Slot slot = this.player.containerMenu.getSlot(packet.getSlotNum());
++ if (slot != null && slot.hasItem() && slot.mayPickup(player) && !slot.getItem().isEmpty() && slot.getItem().getItem() != Item.byBlock(Blocks.AIR)) {
++ action = InventoryAction.DROP_ONE_SLOT;
++ } else {
++ action = InventoryAction.NOTHING;
++ }
++ } else if (packet.getButtonNum() == 1) {
++ click = ClickType.CONTROL_DROP;
++ Slot slot = this.player.containerMenu.getSlot(packet.getSlotNum());
++ if (slot != null && slot.hasItem() && slot.mayPickup(player) && !slot.getItem().isEmpty() && slot.getItem().getItem() != Item.byBlock(Blocks.AIR)) {
++ action = InventoryAction.DROP_ALL_SLOT;
++ } else {
++ action = InventoryAction.NOTHING;
++ }
++ }
++ } else {
++ // Sane default (because this happens when they are holding nothing. Don't ask why.)
++ click = ClickType.LEFT;
++ if (packet.getButtonNum() == 1) {
++ click = ClickType.RIGHT;
++ }
++ action = InventoryAction.NOTHING;
++ }
++ break;
++ case QUICK_CRAFT:
++ this.player.containerMenu.clicked(packet.getSlotNum(), packet.getButtonNum(), packet.getClickType(), this.player);
++ break;
++ case PICKUP_ALL:
++ click = ClickType.DOUBLE_CLICK;
++ action = InventoryAction.NOTHING;
++ if (packet.getSlotNum() >= 0 && !this.player.containerMenu.getCarried().isEmpty()) {
++ ItemStack cursor = this.player.containerMenu.getCarried();
++ action = InventoryAction.NOTHING;
++ // Quick check for if we have any of the item
++ if (inventory.getTopInventory().contains(CraftMagicNumbers.getMaterial(cursor.getItem())) || inventory.getBottomInventory().contains(CraftMagicNumbers.getMaterial(cursor.getItem()))) {
++ action = InventoryAction.COLLECT_TO_CURSOR;
++ }
++ }
++ break;
++ default:
++ break;
++ }
++
++ if (packet.getClickType() != InventoryClickType.QUICK_CRAFT) {
++ if (click == ClickType.NUMBER_KEY) {
++ event = new InventoryClickEvent(inventory, type, packet.getSlotNum(), click, action, packet.getButtonNum());
++ } else {
++ event = new InventoryClickEvent(inventory, type, packet.getSlotNum(), click, action);
++ }
++
++ org.bukkit.inventory.Inventory top = inventory.getTopInventory();
++ if (packet.getSlotNum() == 0 && top instanceof CraftingInventory) {
++ org.bukkit.inventory.Recipe recipe = ((CraftingInventory) top).getRecipe();
++ if (recipe != null) {
++ if (click == ClickType.NUMBER_KEY) {
++ event = new CraftItemEvent(recipe, inventory, type, packet.getSlotNum(), click, action, packet.getButtonNum());
++ } else {
++ event = new CraftItemEvent(recipe, inventory, type, packet.getSlotNum(), click, action);
++ }
++ }
++ }
++
++ if (packet.getSlotNum() == 3 && top instanceof SmithingInventory) {
++ org.bukkit.inventory.ItemStack result = ((SmithingInventory) top).getResult();
++ if (result != null) {
++ if (click == ClickType.NUMBER_KEY) {
++ event = new SmithItemEvent(inventory, type, packet.getSlotNum(), click, action, packet.getButtonNum());
++ } else {
++ event = new SmithItemEvent(inventory, type, packet.getSlotNum(), click, action);
++ }
++ }
++ }
++
++ event.setCancelled(cancelled);
++ AbstractContainerMenu oldContainer = this.player.containerMenu; // SPIGOT-1224
++ cserver.getPluginManager().callEvent(event);
++ if (this.player.containerMenu != oldContainer) {
++ return;
++ }
++
++ switch (event.getResult()) {
++ case ALLOW:
++ case DEFAULT:
++ this.player.containerMenu.clicked(i, packet.getButtonNum(), packet.getClickType(), this.player);
++ break;
++ case DENY:
++ /* Needs enum constructor in InventoryAction
++ if (action.modifiesOtherSlots()) {
++
++ } else {
++ if (action.modifiesCursor()) {
++ this.player.playerConnection.sendPacket(new Packet103SetSlot(-1, -1, this.player.inventory.getCarried()));
++ }
++ if (action.modifiesClicked()) {
++ this.player.playerConnection.sendPacket(new Packet103SetSlot(this.player.activeContainer.windowId, packet102windowclick.slot, this.player.activeContainer.getSlot(packet102windowclick.slot).getItem()));
++ }
++ }*/
++ switch (action) {
++ // Modified other slots
++ case PICKUP_ALL:
++ case MOVE_TO_OTHER_INVENTORY:
++ case HOTBAR_MOVE_AND_READD:
++ case HOTBAR_SWAP:
++ case COLLECT_TO_CURSOR:
++ case UNKNOWN:
++ this.player.containerMenu.sendAllDataToRemote();
++ break;
++ // Modified cursor and clicked
++ case PICKUP_SOME:
++ case PICKUP_HALF:
++ case PICKUP_ONE:
++ case PLACE_ALL:
++ case PLACE_SOME:
++ case PLACE_ONE:
++ case SWAP_WITH_CURSOR:
++ this.player.connection.send(new ClientboundContainerSetSlotPacket(-1, -1, this.player.inventoryMenu.incrementStateId(), this.player.containerMenu.getCarried()));
++ this.player.connection.send(new ClientboundContainerSetSlotPacket(this.player.containerMenu.containerId, this.player.inventoryMenu.incrementStateId(), packet.getSlotNum(), this.player.containerMenu.getSlot(packet.getSlotNum()).getItem()));
++ break;
++ // Modified clicked only
++ case DROP_ALL_SLOT:
++ case DROP_ONE_SLOT:
++ this.player.connection.send(new ClientboundContainerSetSlotPacket(this.player.containerMenu.containerId, this.player.inventoryMenu.incrementStateId(), packet.getSlotNum(), this.player.containerMenu.getSlot(packet.getSlotNum()).getItem()));
++ break;
++ // Modified cursor only
++ case DROP_ALL_CURSOR:
++ case DROP_ONE_CURSOR:
++ case CLONE_STACK:
++ this.player.connection.send(new ClientboundContainerSetSlotPacket(-1, -1, this.player.inventoryMenu.incrementStateId(), this.player.containerMenu.getCarried()));
++ break;
++ // Nothing
++ case NOTHING:
++ break;
++ }
++ }
++
++ if (event instanceof CraftItemEvent || event instanceof SmithItemEvent) {
++ // Need to update the inventory on crafting to
++ // correctly support custom recipes
++ player.containerMenu.sendAllDataToRemote();
++ }
++ }
++ // CraftBukkit end
++ ObjectIterator objectiterator = Int2ObjectMaps.fastIterable(packet.getChangedSlots()).iterator();
++
+ while (objectiterator.hasNext()) {
+ Entry<ItemStack> entry = (Entry) objectiterator.next();
+
+@@ -1751,17 +2690,26 @@
+ if (!this.player.containerMenu.stillValid(this.player)) {
+ ServerGamePacketListenerImpl.LOGGER.debug("Player {} interacted with invalid menu {}", this.player, this.player.containerMenu);
+ } else {
+- this.server.getRecipeManager().byKey(serverboundplacerecipepacket.getRecipe()).ifPresent((recipeholder) -> {
+- ((RecipeBookMenu) this.player.containerMenu).handlePlacement(serverboundplacerecipepacket.isShiftDown(), recipeholder, this.player);
++ // CraftBukkit start - implement PlayerRecipeBookClickEvent
++ org.bukkit.inventory.Recipe recipe = this.cserver.getRecipe(CraftNamespacedKey.fromMinecraft(packet.getRecipe()));
++ if (recipe == null) {
++ return;
++ }
++ org.bukkit.event.player.PlayerRecipeBookClickEvent event = CraftEventFactory.callRecipeBookClickEvent(this.player, recipe, packet.isShiftDown());
++
++ // Cast to keyed should be safe as the recipe will never be a MerchantRecipe.
++ this.server.getRecipeManager().byKey(CraftNamespacedKey.toMinecraft(((org.bukkit.Keyed) event.getRecipe()).getKey())).ifPresent((recipeholder) -> {
++ ((RecipeBookMenu) this.player.containerMenu).handlePlacement(event.isShiftClick(), recipeholder, this.player);
+ });
++ // CraftBukkit end
+ }
+ }
+ }
+
+ @Override
+- @Override
+- public void handleContainerButtonClick(ServerboundContainerButtonClickPacket serverboundcontainerbuttonclickpacket) {
+- PacketUtils.ensureRunningOnSameThread(serverboundcontainerbuttonclickpacket, this, this.player.serverLevel());
++ public void handleContainerButtonClick(ServerboundContainerButtonClickPacket packet) {
++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
++ if (this.player.isImmobile()) return; // CraftBukkit
+ this.player.resetLastActionTime();
+ if (this.player.containerMenu.containerId == serverboundcontainerbuttonclickpacket.getContainerId() && !this.player.isSpectator()) {
+ if (!this.player.containerMenu.stillValid(this.player)) {
+@@ -1805,7 +2752,44 @@
+
+ boolean flag1 = serverboundsetcreativemodeslotpacket.getSlotNum() >= 1 && serverboundsetcreativemodeslotpacket.getSlotNum() <= 45;
+ boolean flag2 = itemstack.isEmpty() || itemstack.getDamageValue() >= 0 && itemstack.getCount() <= 64 && !itemstack.isEmpty();
++ if (flag || (flag1 && !ItemStack.matches(this.player.inventoryMenu.getSlot(packet.getSlotNum()).getItem(), packet.getItem()))) { // Insist on valid slot
++ // CraftBukkit start - Call click event
++ InventoryView inventory = this.player.inventoryMenu.getBukkitView();
++ org.bukkit.inventory.ItemStack item = CraftItemStack.asBukkitCopy(packet.getItem());
+
++ SlotType type = SlotType.QUICKBAR;
++ if (flag) {
++ type = SlotType.OUTSIDE;
++ } else if (packet.getSlotNum() < 36) {
++ if (packet.getSlotNum() >= 5 && packet.getSlotNum() < 9) {
++ type = SlotType.ARMOR;
++ } else {
++ type = SlotType.CONTAINER;
++ }
++ }
++ InventoryCreativeEvent event = new InventoryCreativeEvent(inventory, type, flag ? -999 : packet.getSlotNum(), item);
++ cserver.getPluginManager().callEvent(event);
++
++ itemstack = CraftItemStack.asNMSCopy(event.getCursor());
++
++ switch (event.getResult()) {
++ case ALLOW:
++ // Plugin cleared the id / stacksize checks
++ flag2 = true;
++ break;
++ case DEFAULT:
++ break;
++ case DENY:
++ // Reset the slot
++ if (packet.getSlotNum() >= 0) {
++ this.player.connection.send(new ClientboundContainerSetSlotPacket(this.player.inventoryMenu.containerId, this.player.inventoryMenu.incrementStateId(), packet.getSlotNum(), this.player.inventoryMenu.getSlot(packet.getSlotNum()).getItem()));
++ this.player.connection.send(new ClientboundContainerSetSlotPacket(-1, this.player.inventoryMenu.incrementStateId(), -1, ItemStack.EMPTY));
++ }
++ return;
++ }
++ }
++ // CraftBukkit end
++
+ if (flag1 && flag2) {
+ this.player.inventoryMenu.getSlot(serverboundsetcreativemodeslotpacket.getSlotNum()).setByPlayer(itemstack);
+ this.player.inventoryMenu.broadcastChanges();
+@@ -1827,7 +2810,8 @@
+ }, this.server);
+ }
+
+- private void updateSignText(ServerboundSignUpdatePacket serverboundsignupdatepacket, List<FilteredText> list) {
++ private void updateSignText(ServerboundSignUpdatePacket packet, List<FilteredText> filteredText) {
++ if (this.player.isImmobile()) return; // CraftBukkit
+ this.player.resetLastActionTime();
+ ServerLevel serverlevel = this.player.serverLevel();
+ BlockPos blockpos = serverboundsignupdatepacket.getPos();
+@@ -1847,10 +2831,19 @@
+ }
+
+ @Override
+- @Override
+- public void handlePlayerAbilities(ServerboundPlayerAbilitiesPacket serverboundplayerabilitiespacket) {
+- PacketUtils.ensureRunningOnSameThread(serverboundplayerabilitiespacket, this, this.player.serverLevel());
+- this.player.getAbilities().flying = serverboundplayerabilitiespacket.isFlying() && this.player.getAbilities().mayfly;
++ public void handlePlayerAbilities(ServerboundPlayerAbilitiesPacket packet) {
++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
++ // CraftBukkit start
++ if (this.player.getAbilities().mayfly && this.player.getAbilities().flying != packet.isFlying()) {
++ PlayerToggleFlightEvent event = new PlayerToggleFlightEvent(this.player.getBukkitEntity(), packet.isFlying());
++ this.cserver.getPluginManager().callEvent(event);
++ if (!event.isCancelled()) {
++ this.player.getAbilities().flying = packet.isFlying(); // Actually set the player's flying status
++ } else {
++ this.player.onUpdateAbilities(); // Tell the player their ability was reverted
++ }
++ }
++ // CraftBukkit end
+ }
+
+ @Override
+@@ -1914,7 +2902,7 @@
+ if (!this.waitingForSwitchToConfig) {
+ throw new IllegalStateException("Client acknowledged config, but none was requested");
+ } else {
+- this.connection.setListener(new ServerConfigurationPacketListenerImpl(this.server, this.connection, this.createCookie(this.player.clientInformation())));
++ this.connection.setListener(new ServerConfigurationPacketListenerImpl(this.server, this.connection, this.createCookie(this.player.clientInformation()), this.player)); // CraftBukkit
+ }
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch
new file mode 100644
index 0000000000..d65ceccd51
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch
@@ -0,0 +1,78 @@
+--- a/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java
++++ b/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java
+@@ -11,8 +11,17 @@
+ import net.minecraft.network.protocol.status.ServerStatus;
+ import net.minecraft.server.MinecraftServer;
+
++// CraftBukkit start
++import java.net.InetAddress;
++import java.util.HashMap;
++// CraftBukkit end
++
+ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketListener {
+
++ // CraftBukkit start - add fields
++ private static final HashMap<InetAddress, Long> throttleTracker = new HashMap<InetAddress, Long>();
++ private static int throttleCounter = 0;
++ // CraftBukkit end
+ private static final Component IGNORE_STATUS_REASON = Component.translatable("disconnect.ignoring_status_request");
+ private final MinecraftServer server;
+ private final Connection connection;
+@@ -23,16 +32,50 @@
+ }
+
+ @Override
+- @Override
+- public void handleIntention(ClientIntentionPacket clientintentionpacket) {
+- switch (clientintentionpacket.intention()) {
++ public void handleIntention(ClientIntentionPacket packet) {
++ this.connection.hostname = packet.hostName() + ":" + packet.port(); // CraftBukkit - set hostname
++ switch (packet.intention()) {
+ case LOGIN:
+ this.connection.setClientboundProtocolAfterHandshake(ClientIntent.LOGIN);
+- if (clientintentionpacket.protocolVersion() != SharedConstants.getCurrentVersion().getProtocolVersion()) {
+- MutableComponent mutablecomponent;
++ // CraftBukkit start - Connection throttle
++ try {
++ long currentTime = System.currentTimeMillis();
++ long connectionThrottle = this.server.server.getConnectionThrottle();
++ InetAddress address = ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getAddress();
+
+- if (clientintentionpacket.protocolVersion() < 754) {
+- mutablecomponent = Component.translatable("multiplayer.disconnect.outdated_client", SharedConstants.getCurrentVersion().getName());
++ synchronized (throttleTracker) {
++ if (throttleTracker.containsKey(address) && !"127.0.0.1".equals(address.getHostAddress()) && currentTime - throttleTracker.get(address) < connectionThrottle) {
++ throttleTracker.put(address, currentTime);
++ MutableComponent chatmessage = Component.literal("Connection throttled! Please wait before reconnecting.");
++ this.connection.send(new ClientboundLoginDisconnectPacket(chatmessage));
++ this.connection.disconnect(chatmessage);
++ return;
++ }
++
++ throttleTracker.put(address, currentTime);
++ throttleCounter++;
++ if (throttleCounter > 200) {
++ throttleCounter = 0;
++
++ // Cleanup stale entries
++ java.util.Iterator iter = throttleTracker.entrySet().iterator();
++ while (iter.hasNext()) {
++ java.util.Map.Entry<InetAddress, Long> entry = (java.util.Map.Entry) iter.next();
++ if (entry.getValue() > connectionThrottle) {
++ iter.remove();
++ }
++ }
++ }
++ }
++ } catch (Throwable t) {
++ org.apache.logging.log4j.LogManager.getLogger().debug("Failed to check connection throttle", t);
++ }
++ // CraftBukkit end
++ if (packet.protocolVersion() != SharedConstants.getCurrentVersion().getProtocolVersion()) {
++ MutableComponent ichatmutablecomponent;
++
++ if (packet.protocolVersion() < 754) {
++ ichatmutablecomponent = Component.translatable("multiplayer.disconnect.outdated_client", SharedConstants.getCurrentVersion().getName());
+ } else {
+ mutablecomponent = Component.translatable("multiplayer.disconnect.incompatible", SharedConstants.getCurrentVersion().getName());
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch
new file mode 100644
index 0000000000..3a317236ad
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch
@@ -0,0 +1,125 @@
+--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
++++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+@@ -39,6 +40,10 @@
+ import net.minecraft.world.entity.player.Player;
+ import org.apache.commons.lang3.Validate;
+ import org.slf4j.Logger;
++import org.bukkit.craftbukkit.util.Waitable;
++import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
++import org.bukkit.event.player.PlayerPreLoginEvent;
++// CraftBukkit end
+
+ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, TickablePacketListener {
+
+@@ -56,6 +61,7 @@
+ @Nullable
+ private GameProfile authenticatedProfile;
+ private final String serverId;
++ private ServerPlayer player; // CraftBukkit
+
+ public ServerLoginPacketListenerImpl(MinecraftServer minecraftserver, Connection connection) {
+ this.state = ServerLoginPacketListenerImpl.State.HELLO;
+@@ -82,6 +87,13 @@
+
+ }
+
++ // CraftBukkit start
++ @Deprecated
++ public void disconnect(String s) {
++ disconnect(Component.literal(s));
++ }
++ // CraftBukkit end
++
+ @Override
+ @Override
+ public boolean isAcceptingMessages() {
+@@ -143,10 +152,12 @@
+
+ private void verifyLoginAndFinishConnectionSetup(GameProfile gameprofile) {
+ PlayerList playerlist = this.server.getPlayerList();
+- Component component = playerlist.canPlayerLogin(this.connection.getRemoteAddress(), gameprofile);
++ // CraftBukkit start - fire PlayerLoginEvent
++ this.player = playerlist.canPlayerLogin(this, gameprofile); // CraftBukkit
+
+- if (component != null) {
+- this.disconnect(component);
++ if (this.player == null) {
++ // this.disconnect(ichatbasecomponent);
++ // CraftBukkit end
+ } else {
+ if (this.server.getCompressionThreshold() >= 0 && !this.connection.isMemoryConnection()) {
+ this.connection.send(new ClientboundLoginCompressionPacket(this.server.getCompressionThreshold()), PacketSendListener.thenRun(() -> {
+@@ -154,7 +165,7 @@
+ }));
+ }
+
+- boolean flag = playerlist.disconnectAllPlayersWithProfile(gameprofile);
++ boolean flag = playerlist.disconnectAllPlayersWithProfile(gameprofile, this.player); // CraftBukkit - add player reference
+
+ if (flag) {
+ this.state = ServerLoginPacketListenerImpl.State.WAITING_FOR_DUPE_DISCONNECT;
+@@ -206,6 +215,43 @@
+ if (profileresult != null) {
+ GameProfile gameprofile = profileresult.profile();
+
++ // CraftBukkit start - fire PlayerPreLoginEvent
++ if (!connection.isConnected()) {
++ return;
++ }
++
++ String playerName = gameprofile.getName();
++ java.net.InetAddress address = ((java.net.InetSocketAddress) connection.getRemoteAddress()).getAddress();
++ java.util.UUID uniqueId = gameprofile.getId();
++ final org.bukkit.craftbukkit.CraftServer server = ServerLoginPacketListenerImpl.this.server.server;
++
++ AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, uniqueId);
++ server.getPluginManager().callEvent(asyncEvent);
++
++ if (PlayerPreLoginEvent.getHandlerList().getRegisteredListeners().length != 0) {
++ final PlayerPreLoginEvent event = new PlayerPreLoginEvent(playerName, address, uniqueId);
++ if (asyncEvent.getResult() != PlayerPreLoginEvent.Result.ALLOWED) {
++ event.disallow(asyncEvent.getResult(), asyncEvent.getKickMessage());
++ }
++ Waitable<PlayerPreLoginEvent.Result> waitable = new Waitable<PlayerPreLoginEvent.Result>() {
++ @Override
++ protected PlayerPreLoginEvent.Result evaluate() {
++ server.getPluginManager().callEvent(event);
++ return event.getResult();
++ }};
++
++ ServerLoginPacketListenerImpl.this.server.processQueue.add(waitable);
++ if (waitable.get() != PlayerPreLoginEvent.Result.ALLOWED) {
++ disconnect(event.getKickMessage());
++ return;
++ }
++ } else {
++ if (asyncEvent.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) {
++ disconnect(asyncEvent.getKickMessage());
++ return;
++ }
++ }
++ // CraftBukkit end
+ ServerLoginPacketListenerImpl.LOGGER.info("UUID of player {} is {}", gameprofile.getName(), gameprofile.getId());
+ ServerLoginPacketListenerImpl.this.startClientVerification(gameprofile);
+ } else if (ServerLoginPacketListenerImpl.this.server.isSingleplayer()) {
+@@ -223,6 +269,11 @@
+ ServerLoginPacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.authservers_down"));
+ ServerLoginPacketListenerImpl.LOGGER.error("Couldn't verify username because servers are unavailable");
+ }
++ // CraftBukkit start - catch all exceptions
++ } catch (Exception exception) {
++ disconnect("Failed to verify username!");
++ server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + s1, exception);
++ // CraftBukkit end
+ }
+
+ }
+@@ -250,7 +299,7 @@
+ public void handleLoginAcknowledgement(ServerboundLoginAcknowledgedPacket serverboundloginacknowledgedpacket) {
+ Validate.validState(this.state == ServerLoginPacketListenerImpl.State.PROTOCOL_SWITCHING, "Unexpected login acknowledgement packet", new Object[0]);
+ CommonListenerCookie commonlistenercookie = CommonListenerCookie.createInitial((GameProfile) Objects.requireNonNull(this.authenticatedProfile));
+- ServerConfigurationPacketListenerImpl serverconfigurationpacketlistenerimpl = new ServerConfigurationPacketListenerImpl(this.server, this.connection, commonlistenercookie);
++ ServerConfigurationPacketListenerImpl serverconfigurationpacketlistenerimpl = new ServerConfigurationPacketListenerImpl(this.server, this.connection, commonlistenercookie, this.player); // CraftBukkit
+
+ this.connection.setListener(serverconfigurationpacketlistenerimpl);
+ serverconfigurationpacketlistenerimpl.startConfiguration();
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch
new file mode 100644
index 0000000000..921c4d81ca
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch
@@ -0,0 +1,131 @@
+--- a/net/minecraft/server/network/ServerStatusPacketListenerImpl.java
++++ b/net/minecraft/server/network/ServerStatusPacketListenerImpl.java
+@@ -1,5 +1,12 @@
+ package net.minecraft.server.network;
+
++// CraftBukkit start
++import com.mojang.authlib.GameProfile;
++import java.net.InetSocketAddress;
++import java.util.Collections;
++import java.util.Iterator;
++import java.util.Optional;
++import net.minecraft.SharedConstants;
+ import net.minecraft.network.Connection;
+ import net.minecraft.network.chat.Component;
+ import net.minecraft.network.protocol.status.ClientboundPongResponsePacket;
+@@ -8,6 +15,12 @@
+ import net.minecraft.network.protocol.status.ServerStatusPacketListener;
+ import net.minecraft.network.protocol.status.ServerboundPingRequestPacket;
+ import net.minecraft.network.protocol.status.ServerboundStatusRequestPacket;
++import net.minecraft.server.MinecraftServer;
++import net.minecraft.server.level.ServerPlayer;
++import org.bukkit.craftbukkit.util.CraftChatMessage;
++import org.bukkit.craftbukkit.util.CraftIconCache;
++import org.bukkit.entity.Player;
++// CraftBukkit end
+
+ public class ServerStatusPacketListenerImpl implements ServerStatusPacketListener {
+
+@@ -38,7 +48,101 @@
+ this.connection.disconnect(ServerStatusPacketListenerImpl.DISCONNECT_REASON);
+ } else {
+ this.hasRequestedStatus = true;
+- this.connection.send(new ClientboundStatusResponsePacket(this.status));
++ // CraftBukkit start
++ // this.connection.send(new PacketStatusOutServerInfo(this.status));
++ MinecraftServer server = MinecraftServer.getServer();
++ final Object[] players = server.getPlayerList().players.toArray();
++ class ServerListPingEvent extends org.bukkit.event.server.ServerListPingEvent {
++
++ CraftIconCache icon = server.server.getServerIcon();
++
++ ServerListPingEvent() {
++ super(connection.hostname, ((InetSocketAddress) connection.getRemoteAddress()).getAddress(), server.getMotd(), server.getPlayerList().getMaxPlayers());
++ }
++
++ @Override
++ public void setServerIcon(org.bukkit.util.CachedServerIcon icon) {
++ if (!(icon instanceof CraftIconCache)) {
++ throw new IllegalArgumentException(icon + " was not created by " + org.bukkit.craftbukkit.CraftServer.class);
++ }
++ this.icon = (CraftIconCache) icon;
++ }
++
++ @Override
++ public Iterator<Player> iterator() throws UnsupportedOperationException {
++ return new Iterator<Player>() {
++ int i;
++ int ret = Integer.MIN_VALUE;
++ ServerPlayer player;
++
++ @Override
++ public boolean hasNext() {
++ if (player != null) {
++ return true;
++ }
++ final Object[] currentPlayers = players;
++ for (int length = currentPlayers.length, i = this.i; i < length; i++) {
++ final ServerPlayer player = (ServerPlayer) currentPlayers[i];
++ if (player != null) {
++ this.i = i + 1;
++ this.player = player;
++ return true;
++ }
++ }
++ return false;
++ }
++
++ @Override
++ public Player next() {
++ if (!hasNext()) {
++ throw new java.util.NoSuchElementException();
++ }
++ final ServerPlayer player = this.player;
++ this.player = null;
++ this.ret = this.i - 1;
++ return player.getBukkitEntity();
++ }
++
++ @Override
++ public void remove() {
++ final Object[] currentPlayers = players;
++ final int i = this.ret;
++ if (i < 0 || currentPlayers[i] == null) {
++ throw new IllegalStateException();
++ }
++ currentPlayers[i] = null;
++ }
++ };
++ }
++ }
++
++ ServerListPingEvent event = new ServerListPingEvent();
++ server.server.getPluginManager().callEvent(event);
++
++ java.util.List<GameProfile> profiles = new java.util.ArrayList<GameProfile>(players.length);
++ for (Object player : players) {
++ if (player != null) {
++ ServerPlayer entityPlayer = ((ServerPlayer) player);
++ if (entityPlayer.allowsListing()) {
++ profiles.add(entityPlayer.getGameProfile());
++ } else {
++ profiles.add(MinecraftServer.ANONYMOUS_PLAYER_PROFILE);
++ }
++ }
++ }
++
++ ServerStatus.ServerPingPlayerSample playerSample = new ServerStatus.ServerPingPlayerSample(event.getMaxPlayers(), profiles.size(), (server.hidesOnlinePlayers()) ? Collections.emptyList() : profiles);
++
++ ServerStatus ping = new ServerStatus(
++ CraftChatMessage.fromString(event.getMotd(), true)[0],
++ Optional.of(playerSample),
++ Optional.of(new ServerStatus.Version(server.getServerModName() + " " + server.getServerVersion(), SharedConstants.getCurrentVersion().getProtocolVersion())),
++ (event.icon.value != null) ? Optional.of(new ServerStatus.a(event.icon.value)) : Optional.empty(),
++ server.enforceSecureProfile()
++ );
++
++ this.connection.send(new ClientboundStatusResponsePacket(ping));
++ // CraftBukkit end
+ }
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/players/BanListEntry.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/players/BanListEntry.java.patch
new file mode 100644
index 0000000000..68f43cd7f0
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/players/BanListEntry.java.patch
@@ -0,0 +1,36 @@
+--- a/net/minecraft/server/players/BanListEntry.java
++++ b/net/minecraft/server/players/BanListEntry.java
+@@ -26,8 +26,8 @@
+ this.reason = s1 == null ? "Banned by an operator." : s1;
+ }
+
+- protected BanListEntry(@Nullable T t0, JsonObject jsonobject) {
+- super(t0);
++ protected BanListEntry(@Nullable T user, JsonObject entryData) {
++ super(checkExpiry(user, entryData)); // CraftBukkit
+
+ Date date;
+
+@@ -85,4 +83,22 @@
+ jsonobject.addProperty("expires", this.expires == null ? "forever" : BanListEntry.DATE_FORMAT.format(this.expires));
+ jsonobject.addProperty("reason", this.reason);
+ }
++
++ // CraftBukkit start
++ private static <T> T checkExpiry(T object, JsonObject jsonobject) {
++ Date expires = null;
++
++ try {
++ expires = jsonobject.has("expires") ? DATE_FORMAT.parse(jsonobject.get("expires").getAsString()) : null;
++ } catch (ParseException ex) {
++ // Guess we don't have a date
++ }
++
++ if (expires == null || expires.after(new Date())) {
++ return object;
++ } else {
++ return null;
++ }
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/players/GameProfileCache.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/players/GameProfileCache.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/players/GameProfileCache.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/players/OldUsersConverter.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/players/OldUsersConverter.java.patch
new file mode 100644
index 0000000000..b4efbb4805
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/players/OldUsersConverter.java.patch
@@ -0,0 +1,71 @@
+--- a/net/minecraft/server/players/OldUsersConverter.java
++++ b/net/minecraft/server/players/OldUsersConverter.java
+@@ -85,7 +88,7 @@
+ try {
+ userbanlist.load();
+ } catch (IOException ioexception) {
+- OldUsersConverter.LOGGER.warn("Could not load existing file {}", userbanlist.getFile().getName(), ioexception);
++ OldUsersConverter.LOGGER.warn("Could not load existing file {}", gameprofilebanlist.getFile().getName()); // CraftBukkit - don't print stacktrace
+ }
+ }
+
+@@ -145,7 +146,7 @@
+ try {
+ ipbanlist.load();
+ } catch (IOException ioexception) {
+- OldUsersConverter.LOGGER.warn("Could not load existing file {}", ipbanlist.getFile().getName(), ioexception);
++ OldUsersConverter.LOGGER.warn("Could not load existing file {}", ipbanlist.getFile().getName()); // CraftBukkit - don't print stacktrace
+ }
+ }
+
+@@ -186,7 +187,7 @@
+ try {
+ serveroplist.load();
+ } catch (IOException ioexception) {
+- OldUsersConverter.LOGGER.warn("Could not load existing file {}", serveroplist.getFile().getName(), ioexception);
++ OldUsersConverter.LOGGER.warn("Could not load existing file {}", oplist.getFile().getName()); // CraftBukkit - don't print stacktrace
+ }
+ }
+
+@@ -232,7 +231,7 @@
+ try {
+ userwhitelist.load();
+ } catch (IOException ioexception) {
+- OldUsersConverter.LOGGER.warn("Could not load existing file {}", userwhitelist.getFile().getName(), ioexception);
++ OldUsersConverter.LOGGER.warn("Could not load existing file {}", whitelist.getFile().getName()); // CraftBukkit - don't print stacktrace
+ }
+ }
+
+@@ -357,7 +350,31 @@
+ File file5 = new File(file, s2 + ".dat");
+ File file6 = new File(file4, s3 + ".dat");
+
+- OldUsersConverter.ensureDirectoryExists(file4);
++ // CraftBukkit start - Use old file name to seed lastKnownName
++ CompoundTag root = null;
++
++ try {
++ root = NbtIo.readCompressed(new java.io.FileInputStream(file5), NbtAccounter.unlimitedHeap());
++ } catch (Exception exception) {
++ exception.printStackTrace();
++ }
++
++ if (root != null) {
++ if (!root.contains("bukkit")) {
++ root.put("bukkit", new CompoundTag());
++ }
++ CompoundTag data = root.getCompound("bukkit");
++ data.putString("lastKnownName", oldFileName);
++
++ try {
++ NbtIo.writeCompressed(root, new java.io.FileOutputStream(file2));
++ } catch (Exception exception) {
++ exception.printStackTrace();
++ }
++ }
++ // CraftBukkit end
++
++ OldUsersConverter.ensureDirectoryExists(file);
+ if (!file5.renameTo(file6)) {
+ throw new OldUsersConverter.ConversionError("Could not convert file for " + s2);
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/players/PlayerList.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/players/PlayerList.java.patch
new file mode 100644
index 0000000000..a902891a3c
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/players/PlayerList.java.patch
@@ -0,0 +1,856 @@
+--- a/net/minecraft/server/players/PlayerList.java
++++ b/net/minecraft/server/players/PlayerList.java
+@@ -101,6 +101,25 @@
+ import net.minecraft.world.scores.PlayerTeam;
+ import org.slf4j.Logger;
+
++// CraftBukkit start
++import java.util.stream.Collectors;
++import net.minecraft.server.dedicated.DedicatedServer;
++import org.bukkit.Location;
++import org.bukkit.craftbukkit.CraftServer;
++import org.bukkit.craftbukkit.CraftWorld;
++import org.bukkit.craftbukkit.entity.CraftPlayer;
++import org.bukkit.craftbukkit.util.CraftChatMessage;
++import org.bukkit.craftbukkit.util.CraftLocation;
++import org.bukkit.entity.Player;
++import org.bukkit.event.player.PlayerChangedWorldEvent;
++import org.bukkit.event.player.PlayerJoinEvent;
++import org.bukkit.event.player.PlayerLoginEvent;
++import org.bukkit.event.player.PlayerQuitEvent;
++import org.bukkit.event.player.PlayerRespawnEvent;
++import org.bukkit.event.player.PlayerRespawnEvent.RespawnReason;
++import org.bukkit.event.player.PlayerSpawnChangeEvent;
++// CraftBukkit end
++
+ public abstract class PlayerList {
+
+ public static final File USERBANLIST_FILE = new File("banned-players.json");
+@@ -113,15 +132,17 @@
+ private static final int SEND_PLAYER_INFO_INTERVAL = 600;
+ private static final SimpleDateFormat BAN_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z");
+ private final MinecraftServer server;
+- private final List<ServerPlayer> players = Lists.newArrayList();
++ public final List<ServerPlayer> players = new java.util.concurrent.CopyOnWriteArrayList(); // CraftBukkit - ArrayList -> CopyOnWriteArrayList: Iterator safety
+ private final Map<UUID, ServerPlayer> playersByUUID = Maps.newHashMap();
+ private final UserBanList bans;
+ private final IpBanList ipBans;
+ private final ServerOpList ops;
+ private final UserWhiteList whitelist;
+- private final Map<UUID, ServerStatsCounter> stats;
+- private final Map<UUID, PlayerAdvancements> advancements;
+- private final PlayerDataStorage playerIo;
++ // CraftBukkit start
++ // private final Map<UUID, ServerStatisticManager> stats;
++ // private final Map<UUID, AdvancementDataPlayer> advancements;
++ // CraftBukkit end
++ public final PlayerDataStorage playerIo;
+ private boolean doWhiteList;
+ private final LayeredRegistryAccess<RegistryLayer> registries;
+ protected final int maxPlayers;
+@@ -131,17 +152,27 @@
+ private static final boolean ALLOW_LOGOUTIVATOR = false;
+ private int sendAllPlayerInfoIn;
+
+- public PlayerList(MinecraftServer minecraftserver, LayeredRegistryAccess<RegistryLayer> layeredregistryaccess, PlayerDataStorage playerdatastorage, int i) {
++ // CraftBukkit start
++ private CraftServer cserver;
++
++ public PlayerList(MinecraftServer server, LayeredRegistryAccess<RegistryLayer> registries, PlayerDataStorage playerIo, int maxPlayers) {
++ this.cserver = server.server = new CraftServer((DedicatedServer) server, this);
++ server.console = org.bukkit.craftbukkit.command.ColouredConsoleSender.getInstance();
++ server.reader.addCompleter(new org.bukkit.craftbukkit.command.ConsoleCommandCompleter(server.server));
++ // CraftBukkit end
++
+ this.bans = new UserBanList(PlayerList.USERBANLIST_FILE);
+ this.ipBans = new IpBanList(PlayerList.IPBANLIST_FILE);
+ this.ops = new ServerOpList(PlayerList.OPLIST_FILE);
+ this.whitelist = new UserWhiteList(PlayerList.WHITELIST_FILE);
+- this.stats = Maps.newHashMap();
+- this.advancements = Maps.newHashMap();
+- this.server = minecraftserver;
+- this.registries = layeredregistryaccess;
+- this.maxPlayers = i;
+- this.playerIo = playerdatastorage;
++ // CraftBukkit start
++ // this.stats = Maps.newHashMap();
++ // this.advancements = Maps.newHashMap();
++ // CraftBukkit end
++ this.server = server;
++ this.registries = registries;
++ this.maxPlayers = maxPlayers;
++ this.playerIo = playerIo;
+ }
+
+ public void placeNewPlayer(Connection connection, ServerPlayer serverplayer, CommonListenerCookie commonlistenercookie) {
+@@ -160,15 +191,21 @@
+
+ CompoundTag compoundtag = this.load(serverplayer);
+ ResourceKey resourcekey;
++ // CraftBukkit start - Better rename detection
++ if (nbttagcompound != null && nbttagcompound.contains("bukkit")) {
++ CompoundTag bukkit = nbttagcompound.getCompound("bukkit");
++ s = bukkit.contains("lastKnownName", 8) ? bukkit.getString("lastKnownName") : s;
++ }
++ // CraftBukkit end
+
+ if (compoundtag != null) {
+ DataResult dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, compoundtag.get("Dimension")));
+ Logger logger = PlayerList.LOGGER;
+
+ Objects.requireNonNull(logger);
+- resourcekey = (ResourceKey) dataresult.resultOrPartial(logger::error).orElse(Level.OVERWORLD);
++ resourcekey = (ResourceKey) dataresult.resultOrPartial(logger::error).orElse(entityplayer.serverLevel().dimension()); // CraftBukkit - SPIGOT-7507: If no dimension, fall back to existing dimension loaded from "WorldUUID", which in turn defaults to World.OVERWORLD
+ } else {
+- resourcekey = Level.OVERWORLD;
++ resourcekey = entityplayer.serverLevel().dimension(); // CraftBukkit - SPIGOT-7507: If no dimension, fall back to existing dimension loaded from "WorldUUID", which in turn defaults to World.OVERWORLD
+ }
+
+ ResourceKey<Level> resourcekey1 = resourcekey;
+@@ -185,8 +222,9 @@
+ serverplayer.setServerLevel(serverlevel1);
+ String s1 = connection.getLoggableAddress(this.server.logIPs());
+
+- PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ({}, {}, {})", new Object[]{serverplayer.getName().getString(), s1, serverplayer.getId(), serverplayer.getX(), serverplayer.getY(), serverplayer.getZ()});
+- LevelData leveldata = serverlevel1.getLevelData();
++ // CraftBukkit - Moved message to after join
++ // PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ({}, {}, {})", new Object[]{entityplayer.getName().getString(), s1, entityplayer.getId(), entityplayer.getX(), entityplayer.getY(), entityplayer.getZ()});
++ LevelData worlddata = worldserver1.getLevelData();
+
+ serverplayer.loadGameTypes(compoundtag);
+ ServerGamePacketListenerImpl servergamepacketlistenerimpl = new ServerGamePacketListenerImpl(this.server, connection, serverplayer, commonlistenercookie);
+@@ -195,15 +233,16 @@
+ boolean flag1 = gamerules.getBoolean(GameRules.RULE_REDUCEDDEBUGINFO);
+ boolean flag2 = gamerules.getBoolean(GameRules.RULE_LIMITED_CRAFTING);
+
+- servergamepacketlistenerimpl.send(new ClientboundLoginPacket(serverplayer.getId(), leveldata.isHardcore(), this.server.levelKeys(), this.getMaxPlayers(), this.viewDistance, this.simulationDistance, flag1, !flag, flag2, serverplayer.createCommonSpawnInfo(serverlevel1)));
+- servergamepacketlistenerimpl.send(new ClientboundChangeDifficultyPacket(leveldata.getDifficulty(), leveldata.isDifficultyLocked()));
+- servergamepacketlistenerimpl.send(new ClientboundPlayerAbilitiesPacket(serverplayer.getAbilities()));
+- servergamepacketlistenerimpl.send(new ClientboundSetCarriedItemPacket(serverplayer.getInventory().selected));
+- servergamepacketlistenerimpl.send(new ClientboundUpdateRecipesPacket(this.server.getRecipeManager().getRecipes()));
+- this.sendPlayerPermissionLevel(serverplayer);
+- serverplayer.getStats().markAllDirty();
+- serverplayer.getRecipeBook().sendInitialRecipeBook(serverplayer);
+- this.updateEntireScoreboard(serverlevel1.getScoreboard(), serverplayer);
++ playerconnection.send(new ClientboundLoginPacket(entityplayer.getId(), worlddata.isHardcore(), this.server.levelKeys(), this.getMaxPlayers(), this.viewDistance, this.simulationDistance, flag1, !flag, flag2, entityplayer.createCommonSpawnInfo(worldserver1)));
++ entityplayer.getBukkitEntity().sendSupportedChannels(); // CraftBukkit
++ playerconnection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked()));
++ playerconnection.send(new ClientboundPlayerAbilitiesPacket(entityplayer.getAbilities()));
++ playerconnection.send(new ClientboundSetCarriedItemPacket(entityplayer.getInventory().selected));
++ playerconnection.send(new ClientboundUpdateRecipesPacket(this.server.getRecipeManager().getRecipes()));
++ this.sendPlayerPermissionLevel(entityplayer);
++ entityplayer.getStats().markAllDirty();
++ entityplayer.getRecipeBook().sendInitialRecipeBook(entityplayer);
++ this.updateEntireScoreboard(worldserver1.getScoreboard(), entityplayer);
+ this.server.invalidateStatus();
+ MutableComponent mutablecomponent;
+
+@@ -212,6 +251,9 @@
+ } else {
+ mutablecomponent = Component.translatable("multiplayer.player.joined.renamed", serverplayer.getDisplayName(), s);
+ }
++ // CraftBukkit start
++ ichatmutablecomponent.withStyle(ChatFormatting.YELLOW);
++ String joinMessage = CraftChatMessage.fromComponent(ichatmutablecomponent);
+
+ this.broadcastSystemMessage(mutablecomponent.withStyle(ChatFormatting.YELLOW), false);
+ servergamepacketlistenerimpl.teleport(serverplayer.getX(), serverplayer.getY(), serverplayer.getZ(), serverplayer.getYRot(), serverplayer.getXRot());
+@@ -221,25 +262,79 @@
+ serverplayer.sendServerStatus(serverstatus);
+ }
+
+- serverplayer.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(this.players));
+- this.players.add(serverplayer);
+- this.playersByUUID.put(serverplayer.getUUID(), serverplayer);
+- this.broadcastAll(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(serverplayer)));
+- this.sendLevelInfo(serverplayer, serverlevel1);
+- serverlevel1.addNewPlayer(serverplayer);
+- this.server.getCustomBossEvents().onPlayerConnect(serverplayer);
+- Iterator iterator = serverplayer.getActiveEffects().iterator();
++ // entityplayer.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(this.players)); // CraftBukkit - replaced with loop below
++ this.players.add(entityplayer);
++ this.playersByUUID.put(entityplayer.getUUID(), entityplayer);
++ // this.broadcastAll(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(entityplayer))); // CraftBukkit - replaced with loop below
+
++ // CraftBukkit start
++ CraftPlayer bukkitPlayer = entityplayer.getBukkitEntity();
++
++ // Ensure that player inventory is populated with its viewer
++ entityplayer.containerMenu.transferTo(entityplayer.containerMenu, bukkitPlayer);
++
++ PlayerJoinEvent playerJoinEvent = new PlayerJoinEvent(bukkitPlayer, joinMessage);
++ cserver.getPluginManager().callEvent(playerJoinEvent);
++
++ if (!entityplayer.connection.isAcceptingMessages()) {
++ return;
++ }
++
++ joinMessage = playerJoinEvent.getJoinMessage();
++
++ if (joinMessage != null && joinMessage.length() > 0) {
++ for (Component line : org.bukkit.craftbukkit.util.CraftChatMessage.fromString(joinMessage)) {
++ server.getPlayerList().broadcastSystemMessage(line, false);
++ }
++ }
++ // CraftBukkit end
++
++ // CraftBukkit start - sendAll above replaced with this loop
++ ClientboundPlayerInfoUpdatePacket packet = ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(entityplayer));
++
++ for (int i = 0; i < this.players.size(); ++i) {
++ ServerPlayer entityplayer1 = (ServerPlayer) this.players.get(i);
++
++ if (entityplayer1.getBukkitEntity().canSee(bukkitPlayer)) {
++ entityplayer1.connection.send(packet);
++ }
++
++ if (!bukkitPlayer.canSee(entityplayer1.getBukkitEntity())) {
++ continue;
++ }
++
++ entityplayer.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(entityplayer1)));
++ }
++ entityplayer.sentListPacket = true;
++ // CraftBukkit end
++
++ entityplayer.getEntityData().refresh(entityplayer); // CraftBukkit - BungeeCord#2321, send complete data to self on spawn
++
++ this.sendLevelInfo(entityplayer, worldserver1);
++
++ // CraftBukkit start - Only add if the player wasn't moved in the event
++ if (entityplayer.level() == worldserver1 && !worldserver1.players().contains(entityplayer)) {
++ worldserver1.addNewPlayer(entityplayer);
++ this.server.getCustomBossEvents().onPlayerConnect(entityplayer);
++ }
++
++ worldserver1 = entityplayer.serverLevel(); // CraftBukkit - Update in case join event changed it
++ // CraftBukkit end
++ Iterator iterator = entityplayer.getActiveEffects().iterator();
++
+ while (iterator.hasNext()) {
+ MobEffectInstance mobeffectinstance = (MobEffectInstance) iterator.next();
+
+ servergamepacketlistenerimpl.send(new ClientboundUpdateMobEffectPacket(serverplayer.getId(), mobeffectinstance));
+ }
+
+- if (compoundtag != null && compoundtag.contains("RootVehicle", 10)) {
+- CompoundTag compoundtag1 = compoundtag.getCompound("RootVehicle");
+- Entity entity = EntityType.loadEntityRecursive(compoundtag1.getCompound("Entity"), serverlevel1, (entity1) -> {
+- return !serverlevel1.addWithUUID(entity1) ? null : entity1;
++ if (nbttagcompound != null && nbttagcompound.contains("RootVehicle", 10)) {
++ CompoundTag nbttagcompound1 = nbttagcompound.getCompound("RootVehicle");
++ // CraftBukkit start
++ ServerLevel finalWorldServer = worldserver1;
++ Entity entity = EntityType.loadEntityRecursive(nbttagcompound1.getCompound("Entity"), finalWorldServer, (entity1) -> {
++ return !finalWorldServer.addWithUUID(entity1) ? null : entity1;
++ // CraftBukkit end
+ });
+
+ if (entity != null) {
+@@ -281,7 +376,9 @@
+ }
+ }
+
+- serverplayer.initInventoryMenu();
++ entityplayer.initInventoryMenu();
++ // CraftBukkit - Moved from above, added world
++ PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", entityplayer.getName().getString(), s1, entityplayer.getId(), worldserver1.serverLevelData.getLevelName(), entityplayer.getX(), entityplayer.getY(), entityplayer.getZ());
+ }
+
+ protected void updateEntireScoreboard(ServerScoreboard serverscoreboard, ServerPlayer serverplayer) {
+@@ -317,36 +414,32 @@
+
+ }
+
+- public void addWorldborderListener(ServerLevel serverlevel) {
+- serverlevel.getWorldBorder().addListener(new BorderChangeListener() {
++ public void addWorldborderListener(ServerLevel level) {
++ if (playerIo != null) return; // CraftBukkit
++ level.getWorldBorder().addListener(new BorderChangeListener() {
+ @Override
+- @Override
+- public void onBorderSizeSet(WorldBorder worldborder, double d0) {
+- PlayerList.this.broadcastAll(new ClientboundSetBorderSizePacket(worldborder));
++ public void onBorderSizeSet(WorldBorder border, double size) {
++ PlayerList.this.broadcastAll(new ClientboundSetBorderSizePacket(border), border.world); // CraftBukkit
+ }
+
+ @Override
+- @Override
+- public void onBorderSizeLerping(WorldBorder worldborder, double d0, double d1, long i) {
+- PlayerList.this.broadcastAll(new ClientboundSetBorderLerpSizePacket(worldborder));
++ public void onBorderSizeLerping(WorldBorder border, double oldSize, double d1, long newSize) {
++ PlayerList.this.broadcastAll(new ClientboundSetBorderLerpSizePacket(border), border.world); // CraftBukkit
+ }
+
+ @Override
+- @Override
+- public void onBorderCenterSet(WorldBorder worldborder, double d0, double d1) {
+- PlayerList.this.broadcastAll(new ClientboundSetBorderCenterPacket(worldborder));
++ public void onBorderCenterSet(WorldBorder border, double x, double d1) {
++ PlayerList.this.broadcastAll(new ClientboundSetBorderCenterPacket(border), border.world); // CraftBukkit
+ }
+
+ @Override
+- @Override
+- public void onBorderSetWarningTime(WorldBorder worldborder, int i) {
+- PlayerList.this.broadcastAll(new ClientboundSetBorderWarningDelayPacket(worldborder));
++ public void onBorderSetWarningTime(WorldBorder border, int warningTime) {
++ PlayerList.this.broadcastAll(new ClientboundSetBorderWarningDelayPacket(border), border.world); // CraftBukkit
+ }
+
+ @Override
+- @Override
+- public void onBorderSetWarningBlocks(WorldBorder worldborder, int i) {
+- PlayerList.this.broadcastAll(new ClientboundSetBorderWarningDistancePacket(worldborder));
++ public void onBorderSetWarningBlocks(WorldBorder border, int warningBlocks) {
++ PlayerList.this.broadcastAll(new ClientboundSetBorderWarningDistancePacket(border), border.world); // CraftBukkit
+ }
+
+ @Override
+@@ -375,15 +466,16 @@
+ return compoundtag1;
+ }
+
+- protected void save(ServerPlayer serverplayer) {
+- this.playerIo.save(serverplayer);
+- ServerStatsCounter serverstatscounter = (ServerStatsCounter) this.stats.get(serverplayer.getUUID());
++ protected void save(ServerPlayer player) {
++ if (!player.getBukkitEntity().isPersistent()) return; // CraftBukkit
++ this.playerIo.save(player);
++ ServerStatsCounter serverstatisticmanager = (ServerStatsCounter) player.getStats(); // CraftBukkit
+
+ if (serverstatscounter != null) {
+ serverstatscounter.save();
+ }
+
+- PlayerAdvancements playeradvancements = (PlayerAdvancements) this.advancements.get(serverplayer.getUUID());
++ PlayerAdvancements advancementdataplayer = (PlayerAdvancements) player.getAdvancements(); // CraftBukkit
+
+ if (playeradvancements != null) {
+ playeradvancements.save();
+@@ -391,14 +483,31 @@
+
+ }
+
+- public void remove(ServerPlayer serverplayer) {
+- ServerLevel serverlevel = serverplayer.serverLevel();
++ public String remove(ServerPlayer entityplayer) { // CraftBukkit - return string
++ ServerLevel worldserver = entityplayer.serverLevel();
+
+ serverplayer.awardStat(Stats.LEAVE_GAME);
+ this.save(serverplayer);
+ if (serverplayer.isPassenger()) {
+ Entity entity = serverplayer.getRootVehicle();
+
++ // CraftBukkit start - Quitting must be before we do final save of data, in case plugins need to modify it
++ // See SPIGOT-5799, SPIGOT-6145
++ if (entityplayer.containerMenu != entityplayer.inventoryMenu) {
++ entityplayer.closeContainer();
++ }
++
++ PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), entityplayer.kickLeaveMessage != null ? entityplayer.kickLeaveMessage : "\u00A7e" + entityplayer.getScoreboardName() + " left the game");
++ cserver.getPluginManager().callEvent(playerQuitEvent);
++ entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage());
++
++ entityplayer.doTick(); // SPIGOT-924
++ // CraftBukkit end
++
++ this.save(entityplayer);
++ if (entityplayer.isPassenger()) {
++ Entity entity = entityplayer.getRootVehicle();
++
+ if (entity.hasExactlyOnePlayerPassenger()) {
+ PlayerList.LOGGER.debug("Removing player mount");
+ serverplayer.stopRiding();
+@@ -418,16 +524,34 @@
+
+ if (serverplayer1 == serverplayer) {
+ this.playersByUUID.remove(uuid);
+- this.stats.remove(uuid);
+- this.advancements.remove(uuid);
++ // CraftBukkit start
++ // this.stats.remove(uuid);
++ // this.advancements.remove(uuid);
++ // CraftBukkit end
+ }
+
+- this.broadcastAll(new ClientboundPlayerInfoRemovePacket(List.of(serverplayer.getUUID())));
++ // CraftBukkit start
++ // this.broadcastAll(new ClientboundPlayerInfoRemovePacket(List.of(entityplayer.getUUID())));
++ ClientboundPlayerInfoRemovePacket packet = new ClientboundPlayerInfoRemovePacket(List.of(entityplayer.getUUID()));
++ for (int i = 0; i < players.size(); i++) {
++ ServerPlayer entityplayer2 = (ServerPlayer) this.players.get(i);
++
++ if (entityplayer2.getBukkitEntity().canSee(entityplayer.getBukkitEntity())) {
++ entityplayer2.connection.send(packet);
++ } else {
++ entityplayer2.getBukkitEntity().onEntityRemove(entityplayer);
++ }
++ }
++ // This removes the scoreboard (and player reference) for the specific player in the manager
++ cserver.getScoreboardManager().removePlayer(entityplayer.getBukkitEntity());
++ // CraftBukkit end
++
++ return playerQuitEvent.getQuitMessage(); // CraftBukkit
+ }
+
+- @Nullable
+- public Component canPlayerLogin(SocketAddress socketaddress, GameProfile gameprofile) {
+- MutableComponent mutablecomponent;
++ // CraftBukkit start - Whole method, SocketAddress to LoginListener, added hostname to signature, return EntityPlayer
++ public ServerPlayer canPlayerLogin(ServerLoginPacketListenerImpl loginlistener, GameProfile gameprofile) {
++ MutableComponent ichatmutablecomponent;
+
+ if (this.bans.isBanned(gameprofile)) {
+ UserBanListEntry userbanlistentry = (UserBanListEntry) this.bans.get(gameprofile);
+@@ -437,7 +566,33 @@
+ mutablecomponent.append((Component) Component.translatable("multiplayer.disconnect.banned.expiration", PlayerList.BAN_DATE_FORMAT.format(userbanlistentry.getExpires())));
+ }
+
+- return mutablecomponent;
++ Iterator iterator = list.iterator();
++
++ while (iterator.hasNext()) {
++ entityplayer = (ServerPlayer) iterator.next();
++ save(entityplayer); // CraftBukkit - Force the player's inventory to be saved
++ entityplayer.connection.disconnect(Component.translatable("multiplayer.disconnect.duplicate_login"));
++ }
++
++ // Instead of kicking then returning, we need to store the kick reason
++ // in the event, check with plugins to see if it's ok, and THEN kick
++ // depending on the outcome.
++ SocketAddress socketaddress = loginlistener.connection.getRemoteAddress();
++
++ ServerPlayer entity = new ServerPlayer(this.server, this.server.getLevel(Level.OVERWORLD), gameprofile, ClientInformation.createDefault());
++ Player player = entity.getBukkitEntity();
++ PlayerLoginEvent event = new PlayerLoginEvent(player, loginlistener.connection.hostname, ((java.net.InetSocketAddress) socketaddress).getAddress());
++
++ if (getBans().isBanned(gameprofile) && !getBans().get(gameprofile).hasExpired()) {
++ UserBanListEntry gameprofilebanentry = (UserBanListEntry) this.bans.get(gameprofile);
++
++ ichatmutablecomponent = Component.translatable("multiplayer.disconnect.banned.reason", gameprofilebanentry.getReason());
++ if (gameprofilebanentry.getExpires() != null) {
++ ichatmutablecomponent.append((Component) Component.translatable("multiplayer.disconnect.banned.expiration", PlayerList.BAN_DATE_FORMAT.format(gameprofilebanentry.getExpires())));
++ }
++
++ // return chatmessage;
++ event.disallow(PlayerLoginEvent.Result.KICK_BANNED, CraftChatMessage.fromComponent(ichatmutablecomponent));
+ } else if (!this.isWhiteListed(gameprofile)) {
+ return Component.translatable("multiplayer.disconnect.not_whitelisted");
+ } else if (this.ipBans.isBanned(socketaddress)) {
+@@ -454,11 +621,15 @@
+ }
+ }
+
+- public ServerPlayer getPlayerForLogin(GameProfile gameprofile, ClientInformation clientinformation) {
+- return new ServerPlayer(this.server, this.server.overworld(), gameprofile, clientinformation);
++ // CraftBukkit start - added EntityPlayer
++ public ServerPlayer getPlayerForLogin(GameProfile gameprofile, ClientInformation clientinformation, ServerPlayer player) {
++ player.updateOptions(clientinformation);
++ return player;
++ // CraftBukkit end
+ }
+
+- public boolean disconnectAllPlayersWithProfile(GameProfile gameprofile) {
++ public boolean disconnectAllPlayersWithProfile(GameProfile gameprofile, ServerPlayer player) { // CraftBukkit - added EntityPlayer
++ /* CraftBukkit startMoved up
+ UUID uuid = gameprofile.getId();
+ Set<ServerPlayer> set = Sets.newIdentityHashSet();
+ Iterator iterator = this.players.iterator();
+@@ -486,15 +657,25 @@
+ }
+
+ return !set.isEmpty();
++ */
++ return player == null;
++ // CraftBukkit end
+ }
+
+- public ServerPlayer respawn(ServerPlayer serverplayer, boolean flag) {
+- this.players.remove(serverplayer);
+- serverplayer.serverLevel().removePlayerImmediately(serverplayer, Entity.RemovalReason.DISCARDED);
+- BlockPos blockpos = serverplayer.getRespawnPosition();
+- float f = serverplayer.getRespawnAngle();
+- boolean flag1 = serverplayer.isRespawnForced();
+- ServerLevel serverlevel = this.server.getLevel(serverplayer.getRespawnDimension());
++ // CraftBukkit start
++ public ServerPlayer respawn(ServerPlayer entityplayer, boolean flag, RespawnReason reason) {
++ return this.respawn(entityplayer, this.server.getLevel(entityplayer.getRespawnDimension()), flag, null, true, reason);
++ }
++
++ public ServerPlayer respawn(ServerPlayer entityplayer, ServerLevel worldserver, boolean flag, Location location, boolean avoidSuffocation, RespawnReason reason) {
++ entityplayer.stopRiding(); // CraftBukkit
++ this.players.remove(entityplayer);
++ entityplayer.serverLevel().removePlayerImmediately(entityplayer, Entity.RemovalReason.DISCARDED);
++ BlockPos blockposition = entityplayer.getRespawnPosition();
++ float f = entityplayer.getRespawnAngle();
++ boolean flag1 = entityplayer.isRespawnForced();
++ /* CraftBukkit start
++ WorldServer worldserver = this.server.getLevel(entityplayer.getRespawnDimension());
+ Optional optional;
+
+ if (serverlevel != null && blockpos != null) {
+@@ -503,8 +684,13 @@
+ optional = Optional.empty();
+ }
+
+- ServerLevel serverlevel1 = serverlevel != null && optional.isPresent() ? serverlevel : this.server.overworld();
+- ServerPlayer serverplayer1 = new ServerPlayer(this.server, serverlevel1, serverplayer.getGameProfile(), serverplayer.clientInformation());
++ WorldServer worldserver1 = worldserver != null && optional.isPresent() ? worldserver : this.server.overworld();
++ EntityPlayer entityplayer1 = new EntityPlayer(this.server, worldserver1, entityplayer.getGameProfile(), entityplayer.clientInformation());
++ // */
++ ServerPlayer entityplayer1 = entityplayer;
++ org.bukkit.World fromWorld = entityplayer.getBukkitEntity().getWorld();
++ entityplayer.wonGame = false;
++ // CraftBukkit end
+
+ serverplayer1.connection = serverplayer.connection;
+ serverplayer1.restoreFrom(serverplayer, flag);
+@@ -520,18 +706,40 @@
+
+ boolean flag2 = false;
+
+- if (optional.isPresent()) {
+- BlockState blockstate = serverlevel1.getBlockState(blockpos);
+- boolean flag3 = blockstate.is(Blocks.RESPAWN_ANCHOR);
+- Vec3 vec3 = (Vec3) optional.get();
+- float f1;
++ // CraftBukkit start - fire PlayerRespawnEvent
++ if (location == null) {
++ boolean isBedSpawn = false;
++ ServerLevel worldserver1 = this.server.getLevel(entityplayer.getRespawnDimension());
++ if (worldserver1 != null) {
++ Optional optional;
+
+ if (!blockstate.is(BlockTags.BEDS) && !flag3) {
+ f1 = f;
+ } else {
+ Vec3 vec31 = Vec3.atBottomCenterOf(blockpos).subtract(vec3).normalize();
+
+- f1 = (float) Mth.wrapDegrees(Mth.atan2(vec31.z, vec31.x) * 57.2957763671875D - 90.0D);
++ if (optional.isPresent()) {
++ IBlockData iblockdata = worldserver1.getBlockState(blockposition);
++ boolean flag3 = iblockdata.is(Blocks.RESPAWN_ANCHOR);
++ Vec3 vec3d = (Vec3) optional.get();
++ float f1;
++
++ if (!iblockdata.is(BlockTags.BEDS) && !flag3) {
++ f1 = f;
++ } else {
++ Vec3 vec3d1 = Vec3.atBottomCenterOf(blockposition).subtract(vec3d).normalize();
++
++ f1 = (float) Mth.wrapDegrees(Mth.atan2(vec3d1.z, vec3d1.x) * 57.2957763671875D - 90.0D);
++ }
++
++ // entityplayer1.setRespawnPosition(worldserver1.dimension(), blockposition, f, flag1, false); // CraftBukkit - not required, just copies old location into reused entity
++ flag2 = !flag && flag3;
++ isBedSpawn = true;
++ location = CraftLocation.toBukkit(vec3d, worldserver1.getWorld(), f1, 0.0F);
++ } else if (blockposition != null) {
++ entityplayer1.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.NO_RESPAWN_BLOCK_AVAILABLE, 0.0F));
++ entityplayer1.setRespawnPosition(null, null, 0f, false, false, PlayerSpawnChangeEvent.Cause.RESET); // CraftBukkit - SPIGOT-5988: Clear respawn location when obstructed
++ }
+ }
+
+ serverplayer1.moveTo(vec3.x, vec3.y, vec3.z, f1, 0.0F);
+@@ -541,31 +764,48 @@
+ serverplayer1.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.NO_RESPAWN_BLOCK_AVAILABLE, 0.0F));
+ }
+
+- while (!serverlevel1.noCollision((Entity) serverplayer1) && serverplayer1.getY() < (double) serverlevel1.getMaxBuildHeight()) {
+- serverplayer1.setPos(serverplayer1.getX(), serverplayer1.getY() + 1.0D, serverplayer1.getZ());
++ while (avoidSuffocation && !worldserver1.noCollision((Entity) entityplayer1) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) {
++ // CraftBukkit end
++ entityplayer1.setPos(entityplayer1.getX(), entityplayer1.getY() + 1.0D, entityplayer1.getZ());
+ }
+
+ int i = flag ? 1 : 0;
+ ServerLevel serverlevel2 = serverplayer1.serverLevel();
+ LevelData leveldata = serverlevel2.getLevelData();
+
+- serverplayer1.connection.send(new ClientboundRespawnPacket(serverplayer1.createCommonSpawnInfo(serverlevel2), (byte) i));
+- serverplayer1.connection.teleport(serverplayer1.getX(), serverplayer1.getY(), serverplayer1.getZ(), serverplayer1.getYRot(), serverplayer1.getXRot());
+- serverplayer1.connection.send(new ClientboundSetDefaultSpawnPositionPacket(serverlevel1.getSharedSpawnPos(), serverlevel1.getSharedSpawnAngle()));
+- serverplayer1.connection.send(new ClientboundChangeDifficultyPacket(leveldata.getDifficulty(), leveldata.isDifficultyLocked()));
+- serverplayer1.connection.send(new ClientboundSetExperiencePacket(serverplayer1.experienceProgress, serverplayer1.totalExperience, serverplayer1.experienceLevel));
+- this.sendLevelInfo(serverplayer1, serverlevel1);
+- this.sendPlayerPermissionLevel(serverplayer1);
+- serverlevel1.addRespawnedPlayer(serverplayer1);
+- this.players.add(serverplayer1);
+- this.playersByUUID.put(serverplayer1.getUUID(), serverplayer1);
+- serverplayer1.initInventoryMenu();
+- serverplayer1.setHealth(serverplayer1.getHealth());
++ entityplayer1.connection.send(new ClientboundRespawnPacket(entityplayer1.createCommonSpawnInfo(worldserver2), (byte) i));
++ entityplayer1.connection.teleport(CraftLocation.toBukkit(entityplayer1.position(), worldserver2.getWorld(), entityplayer1.getYRot(), entityplayer1.getXRot())); // CraftBukkit
++ entityplayer1.connection.send(new ClientboundSetDefaultSpawnPositionPacket(worldserver1.getSharedSpawnPos(), worldserver1.getSharedSpawnAngle()));
++ entityplayer1.connection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked()));
++ entityplayer1.connection.send(new ClientboundSetExperiencePacket(entityplayer1.experienceProgress, entityplayer1.totalExperience, entityplayer1.experienceLevel));
++ this.sendLevelInfo(entityplayer1, worldserver1);
++ this.sendPlayerPermissionLevel(entityplayer1);
++ if (!entityplayer.connection.isDisconnected()) {
++ worldserver1.addRespawnedPlayer(entityplayer1);
++ this.players.add(entityplayer1);
++ this.playersByUUID.put(entityplayer1.getUUID(), entityplayer1);
++ }
++ // entityplayer1.initInventoryMenu();
++ entityplayer1.setHealth(entityplayer1.getHealth());
+ if (flag2) {
+ serverplayer1.connection.send(new ClientboundSoundPacket(SoundEvents.RESPAWN_ANCHOR_DEPLETE, SoundSource.BLOCKS, (double) blockpos.getX(), (double) blockpos.getY(), (double) blockpos.getZ(), 1.0F, 1.0F, serverlevel1.getRandom().nextLong()));
+ }
+
+- return serverplayer1;
++ // Fire advancement trigger
++ entityplayer.triggerDimensionChangeTriggers(((CraftWorld) fromWorld).getHandle());
++
++ // Don't fire on respawn
++ if (fromWorld != location.getWorld()) {
++ PlayerChangedWorldEvent event = new PlayerChangedWorldEvent(entityplayer.getBukkitEntity(), fromWorld);
++ server.server.getPluginManager().callEvent(event);
++ }
++
++ // Save player file again if they were disconnected
++ if (entityplayer.connection.isDisconnected()) {
++ this.save(entityplayer);
++ }
++ // CraftBukkit end
++ return entityplayer1;
+ }
+
+ public void sendPlayerPermissionLevel(ServerPlayer serverplayer) {
+@@ -577,7 +823,18 @@
+
+ public void tick() {
+ if (++this.sendAllPlayerInfoIn > 600) {
+- this.broadcastAll(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY), this.players));
++ // CraftBukkit start
++ for (int i = 0; i < this.players.size(); ++i) {
++ final ServerPlayer target = (ServerPlayer) this.players.get(i);
++
++ target.connection.send(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY), this.players.stream().filter(new Predicate<ServerPlayer>() {
++ @Override
++ public boolean test(ServerPlayer input) {
++ return target.getBukkitEntity().canSee(input.getBukkitEntity());
++ }
++ }).collect(Collectors.toList())));
++ }
++ // CraftBukkit end
+ this.sendAllPlayerInfoIn = 0;
+ }
+
+@@ -594,7 +851,26 @@
+
+ }
+
+- public void broadcastAll(Packet<?> packet, ResourceKey<Level> resourcekey) {
++ // CraftBukkit start - add a world/entity limited version
++ public void broadcastAll(Packet packet, net.minecraft.world.entity.player.Player entityhuman) {
++ for (int i = 0; i < this.players.size(); ++i) {
++ ServerPlayer entityplayer = this.players.get(i);
++ if (entityhuman != null && !entityplayer.getBukkitEntity().canSee(entityhuman.getBukkitEntity())) {
++ continue;
++ }
++ ((ServerPlayer) this.players.get(i)).connection.send(packet);
++ }
++ }
++
++ public void broadcastAll(Packet packet, Level world) {
++ for (int i = 0; i < world.players().size(); ++i) {
++ ((ServerPlayer) world.players().get(i)).connection.send(packet);
++ }
++
++ }
++ // CraftBukkit end
++
++ public void broadcastAll(Packet<?> packet, ResourceKey<Level> dimension) {
+ Iterator iterator = this.players.iterator();
+
+ while (iterator.hasNext()) {
+@@ -696,7 +972,8 @@
+ serverplayer.connection.send(new ClientboundEntityEventPacket(serverplayer, b0));
+ }
+
+- this.server.getCommands().sendCommands(serverplayer);
++ player.getBukkitEntity().recalculatePermissions(); // CraftBukkit
++ this.server.getCommands().sendCommands(player);
+ }
+
+ public boolean isWhiteListed(GameProfile gameprofile) {
+@@ -726,10 +1003,11 @@
+ for (int i = 0; i < this.players.size(); ++i) {
+ ServerPlayer serverplayer = (ServerPlayer) this.players.get(i);
+
+- if (serverplayer != player && serverplayer.level().dimension() == resourcekey) {
+- double d4 = d0 - serverplayer.getX();
+- double d5 = d1 - serverplayer.getY();
+- double d6 = d2 - serverplayer.getZ();
++ // CraftBukkit start - Test if player receiving packet can see the source of the packet
++ if (except != null && !entityplayer.getBukkitEntity().canSee(except.getBukkitEntity())) {
++ continue;
++ }
++ // CraftBukkit end
+
+ if (d4 * d4 + d5 * d5 + d6 * d6 < d3 * d3) {
+ serverplayer.connection.send(packet);
+@@ -764,26 +1047,38 @@
+
+ public void reloadWhiteList() {}
+
+- public void sendLevelInfo(ServerPlayer serverplayer, ServerLevel serverlevel) {
+- WorldBorder worldborder = this.server.overworld().getWorldBorder();
++ public void sendLevelInfo(ServerPlayer player, ServerLevel level) {
++ WorldBorder worldborder = player.level().getWorldBorder(); // CraftBukkit
+
+- serverplayer.connection.send(new ClientboundInitializeBorderPacket(worldborder));
+- serverplayer.connection.send(new ClientboundSetTimePacket(serverlevel.getGameTime(), serverlevel.getDayTime(), serverlevel.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)));
+- serverplayer.connection.send(new ClientboundSetDefaultSpawnPositionPacket(serverlevel.getSharedSpawnPos(), serverlevel.getSharedSpawnAngle()));
+- if (serverlevel.isRaining()) {
+- serverplayer.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0.0F));
+- serverplayer.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, serverlevel.getRainLevel(1.0F)));
+- serverplayer.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, serverlevel.getThunderLevel(1.0F)));
++ player.connection.send(new ClientboundInitializeBorderPacket(worldborder));
++ player.connection.send(new ClientboundSetTimePacket(level.getGameTime(), level.getDayTime(), level.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)));
++ player.connection.send(new ClientboundSetDefaultSpawnPositionPacket(level.getSharedSpawnPos(), level.getSharedSpawnAngle()));
++ if (level.isRaining()) {
++ // CraftBukkit start - handle player weather
++ // entityplayer.connection.send(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.START_RAINING, 0.0F));
++ // entityplayer.connection.send(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.RAIN_LEVEL_CHANGE, worldserver.getRainLevel(1.0F)));
++ // entityplayer.connection.send(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.THUNDER_LEVEL_CHANGE, worldserver.getThunderLevel(1.0F)));
++ player.setPlayerWeather(org.bukkit.WeatherType.DOWNFALL, false);
++ player.updateWeather(-level.rainLevel, level.rainLevel, -level.thunderLevel, level.thunderLevel);
++ // CraftBukkit end
+ }
+
+ serverplayer.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.LEVEL_CHUNKS_LOAD_START, 0.0F));
+ this.server.tickRateManager().updateJoiningPlayer(serverplayer);
+ }
+
+- public void sendAllPlayerInfo(ServerPlayer serverplayer) {
+- serverplayer.inventoryMenu.sendAllDataToRemote();
+- serverplayer.resetSentInfo();
+- serverplayer.connection.send(new ClientboundSetCarriedItemPacket(serverplayer.getInventory().selected));
++ public void sendAllPlayerInfo(ServerPlayer player) {
++ player.inventoryMenu.sendAllDataToRemote();
++ // entityplayer.resetSentInfo();
++ player.getBukkitEntity().updateScaledHealth(); // CraftBukkit - Update scaled health on respawn and worldchange
++ player.getEntityData().refresh(player); // CraftBukkkit - SPIGOT-7218: sync metadata
++ player.connection.send(new ClientboundSetCarriedItemPacket(player.getInventory().selected));
++ // CraftBukkit start - from GameRules
++ int i = player.level().getGameRules().getBoolean(GameRules.RULE_REDUCEDDEBUGINFO) ? 22 : 23;
++ player.connection.send(new ClientboundEntityEventPacket(player, (byte) i));
++ float immediateRespawn = player.level().getGameRules().getBoolean(GameRules.RULE_DO_IMMEDIATE_RESPAWN) ? 1.0F: 0.0F;
++ player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.IMMEDIATE_RESPAWN, immediateRespawn));
++ // CraftBukkit end
+ }
+
+ public int getPlayerCount() {
+@@ -839,17 +1134,21 @@
+ }
+
+ public void removeAll() {
+- for (int i = 0; i < this.players.size(); ++i) {
+- ((ServerPlayer) this.players.get(i)).connection.disconnect(Component.translatable("multiplayer.disconnect.server_shutdown"));
++ // CraftBukkit start - disconnect safely
++ for (ServerPlayer player : this.players) {
++ player.connection.disconnect(this.server.server.getShutdownMessage()); // CraftBukkit - add custom shutdown message
+ }
++ // CraftBukkit end
+
+ }
+
+- public void broadcastSystemMessage(Component component, boolean flag) {
+- this.broadcastSystemMessage(component, (serverplayer) -> {
+- return component;
+- }, flag);
++ // CraftBukkit start
++ public void broadcastMessage(Component[] iChatBaseComponents) {
++ for (Component component : iChatBaseComponents) {
++ broadcastSystemMessage(component, false);
++ }
+ }
++ // CraftBukkit end
+
+ public void broadcastSystemMessage(Component component, Function<ServerPlayer, Component> function, boolean flag) {
+ this.server.sendSystemMessage(component);
+@@ -902,16 +1207,23 @@
+ return playerchatmessage.hasSignature() && !playerchatmessage.hasExpiredServer(Instant.now());
+ }
+
+- public ServerStatsCounter getPlayerStats(Player player) {
+- UUID uuid = player.getUUID();
+- ServerStatsCounter serverstatscounter = (ServerStatsCounter) this.stats.get(uuid);
++ // CraftBukkit start
++ public ServerStatsCounter getPlayerStats(ServerPlayer entityhuman) {
++ ServerStatsCounter serverstatisticmanager = entityhuman.getStats();
++ return serverstatisticmanager == null ? getPlayerStats(entityhuman.getUUID(), entityhuman.getDisplayName().getString()) : serverstatisticmanager;
++ }
+
+- if (serverstatscounter == null) {
++ public ServerStatsCounter getPlayerStats(UUID uuid, String displayName) {
++ ServerPlayer entityhuman = this.getPlayer(uuid);
++ ServerStatsCounter serverstatisticmanager = entityhuman == null ? null : (ServerStatsCounter) entityhuman.getStats();
++ // CraftBukkit end
++
++ if (serverstatisticmanager == null) {
+ File file = this.server.getWorldPath(LevelResource.PLAYER_STATS_DIR).toFile();
+ File file1 = new File(file, uuid + ".json");
+
+ if (!file1.exists()) {
+- File file2 = new File(file, player.getName().getString() + ".json");
++ File file2 = new File(file, displayName + ".json"); // CraftBukkit
+ Path path = file2.toPath();
+
+ if (FileUtil.isPathNormalized(path) && FileUtil.isPathPortable(path) && path.startsWith(file.getPath()) && file2.isFile()) {
+@@ -919,22 +1231,22 @@
+ }
+ }
+
+- serverstatscounter = new ServerStatsCounter(this.server, file1);
+- this.stats.put(uuid, serverstatscounter);
++ serverstatisticmanager = new ServerStatsCounter(this.server, file1);
++ // this.stats.put(uuid, serverstatisticmanager); // CraftBukkit
+ }
+
+ return serverstatscounter;
+ }
+
+- public PlayerAdvancements getPlayerAdvancements(ServerPlayer serverplayer) {
+- UUID uuid = serverplayer.getUUID();
+- PlayerAdvancements playeradvancements = (PlayerAdvancements) this.advancements.get(uuid);
++ public PlayerAdvancements getPlayerAdvancements(ServerPlayer player) {
++ UUID uuid = player.getUUID();
++ PlayerAdvancements advancementdataplayer = (PlayerAdvancements) player.getAdvancements(); // CraftBukkit
+
+ if (playeradvancements == null) {
+ Path path = this.server.getWorldPath(LevelResource.PLAYER_ADVANCEMENTS_DIR).resolve(uuid + ".json");
+
+- playeradvancements = new PlayerAdvancements(this.server.getFixerUpper(), this, this.server.getAdvancements(), path, serverplayer);
+- this.advancements.put(uuid, playeradvancements);
++ advancementdataplayer = new PlayerAdvancements(this.server.getFixerUpper(), this, this.server.getAdvancements(), path, player);
++ // this.advancements.put(uuid, advancementdataplayer); // CraftBukkit
+ }
+
+ playeradvancements.setPlayer(serverplayer);
+@@ -985,13 +1297,20 @@
+ }
+
+ public void reloadResources() {
+- Iterator iterator = this.advancements.values().iterator();
++ // CraftBukkit start
++ /*Iterator iterator = this.advancements.values().iterator();
+
+ while (iterator.hasNext()) {
+ PlayerAdvancements playeradvancements = (PlayerAdvancements) iterator.next();
+
+- playeradvancements.reload(this.server.getAdvancements());
++ advancementdataplayer.reload(this.server.getAdvancements());
++ }*/
++
++ for (ServerPlayer player : players) {
++ player.getAdvancements().reload(this.server.getAdvancements());
++ player.getAdvancements().flushDirty(player); // CraftBukkit - trigger immediate flush of advancements
+ }
++ // CraftBukkit end
+
+ this.broadcastAll(new ClientboundUpdateTagsPacket(TagNetworkSerialization.serializeTagsToNetwork(this.registries)));
+ ClientboundUpdateRecipesPacket clientboundupdaterecipespacket = new ClientboundUpdateRecipesPacket(this.server.getRecipeManager().getRecipes());
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/players/SleepStatus.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/players/SleepStatus.java.patch
new file mode 100644
index 0000000000..580cdaf078
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/players/SleepStatus.java.patch
@@ -0,0 +1,48 @@
+--- a/net/minecraft/server/players/SleepStatus.java
++++ b/net/minecraft/server/players/SleepStatus.java
+@@ -17,10 +17,13 @@
+ return this.sleepingPlayers >= this.sleepersNeeded(i);
+ }
+
+- public boolean areEnoughDeepSleeping(int i, List<ServerPlayer> list) {
+- int j = (int) list.stream().filter(Player::isSleepingLongEnough).count();
++ public boolean areEnoughDeepSleeping(int requiredSleepPercentage, List<ServerPlayer> sleepingPlayers) {
++ // CraftBukkit start
++ int j = (int) sleepingPlayers.stream().filter((eh) -> { return eh.isSleepingLongEnough() || eh.fauxSleeping; }).count();
++ boolean anyDeepSleep = sleepingPlayers.stream().anyMatch(Player::isSleepingLongEnough);
+
+- return j >= this.sleepersNeeded(i);
++ return anyDeepSleep && j >= this.sleepersNeeded(requiredSleepPercentage);
++ // CraftBukkit end
+ }
+
+ public int sleepersNeeded(int i) {
+@@ -41,19 +44,25 @@
+
+ this.activePlayers = 0;
+ this.sleepingPlayers = 0;
+- Iterator iterator = list.iterator();
++ Iterator iterator = players.iterator();
++ boolean anySleep = false; // CraftBukkit
+
+ while (iterator.hasNext()) {
+ ServerPlayer serverplayer = (ServerPlayer) iterator.next();
+
+ if (!serverplayer.isSpectator()) {
+ ++this.activePlayers;
+- if (serverplayer.isSleeping()) {
++ if (entityplayer.isSleeping() || entityplayer.fauxSleeping) { // CraftBukkit
+ ++this.sleepingPlayers;
+ }
++ // CraftBukkit start
++ if (entityplayer.isSleeping()) {
++ anySleep = true;
++ }
++ // CraftBukkit end
+ }
+ }
+
+- return (j > 0 || this.sleepingPlayers > 0) && (i != this.activePlayers || j != this.sleepingPlayers);
++ return anySleep && (j > 0 || this.sleepingPlayers > 0) && (i != this.activePlayers || j != this.sleepingPlayers); // CraftBukkit
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/players/StoredUserList.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/players/StoredUserList.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/players/StoredUserList.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/players/UserBanListEntry.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/players/UserBanListEntry.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/players/UserBanListEntry.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/rcon/RconConsoleSource.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/rcon/RconConsoleSource.java.patch
new file mode 100644
index 0000000000..489801bb83
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/rcon/RconConsoleSource.java.patch
@@ -0,0 +1,46 @@
+--- a/net/minecraft/server/rcon/RconConsoleSource.java
++++ b/net/minecraft/server/rcon/RconConsoleSource.java
+@@ -8,15 +8,23 @@
+ import net.minecraft.world.entity.Entity;
+ import net.minecraft.world.phys.Vec2;
+ import net.minecraft.world.phys.Vec3;
+-
++// CraftBukkit start
++import java.net.SocketAddress;
++import org.bukkit.craftbukkit.command.CraftRemoteConsoleCommandSender;
++// CraftBukkit end
+ public class RconConsoleSource implements CommandSource {
+
+ private static final String RCON = "Rcon";
+ private static final Component RCON_COMPONENT = Component.literal("Rcon");
+ private final StringBuffer buffer = new StringBuffer();
+ private final MinecraftServer server;
++ // CraftBukkit start
++ public final SocketAddress socketAddress;
++ private final CraftRemoteConsoleCommandSender remoteConsole = new CraftRemoteConsoleCommandSender(this);
+
+- public RconConsoleSource(MinecraftServer minecraftserver) {
++ public RconConsoleSource(MinecraftServer minecraftserver, SocketAddress socketAddress) {
++ this.socketAddress = socketAddress;
++ // CraftBukkit end
+ this.server = minecraftserver;
+ }
+
+@@ -34,7 +42,17 @@
+ return new CommandSourceStack(this, Vec3.atLowerCornerOf(serverlevel.getSharedSpawnPos()), Vec2.ZERO, serverlevel, 4, "Rcon", RconConsoleSource.RCON_COMPONENT, this.server, (Entity) null);
+ }
+
++ // CraftBukkit start - Send a String
++ public void sendMessage(String message) {
++ this.buffer.append(message);
++ }
++
+ @Override
++ public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) {
++ return this.remoteConsole;
++ }
++ // CraftBukkit end
++
+ @Override
+ public void sendSystemMessage(Component component) {
+ this.buffer.append(component.getString());
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/server/rcon/thread/RconClient.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/rcon/thread/RconClient.java.patch
new file mode 100644
index 0000000000..8ee4fae33b
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/server/rcon/thread/RconClient.java.patch
@@ -0,0 +1,74 @@
+--- a/net/minecraft/server/rcon/thread/RconClient.java
++++ b/net/minecraft/server/rcon/thread/RconClient.java
+@@ -9,6 +10,8 @@
+ import java.nio.charset.StandardCharsets;
+ import java.util.Locale;
+ import net.minecraft.server.ServerInterface;
++// CraftBukkit start
++import net.minecraft.server.dedicated.DedicatedServer;
+ import net.minecraft.server.rcon.PktUtils;
+ import org.slf4j.Logger;
+
+@@ -24,12 +27,15 @@
+ private final Socket client;
+ private final byte[] buf = new byte[1460];
+ private final String rconPassword;
+- private final ServerInterface serverInterface;
++ // CraftBukkit start
++ private final DedicatedServer serverInterface;
++ private final RconConsoleSource rconConsoleSource;
++ // CraftBukkit end
+
+- RconClient(ServerInterface serverinterface, String s, Socket socket) {
+- super("RCON Client " + socket.getInetAddress());
+- this.serverInterface = serverinterface;
+- this.client = socket;
++ RconClient(ServerInterface serverInterface, String rconPassword, Socket client) {
++ super("RCON Client " + client.getInetAddress());
++ this.serverInterface = (DedicatedServer) serverInterface; // CraftBukkit
++ this.client = client;
+
+ try {
+ this.client.setSoTimeout(0);
+@@ -37,13 +43,16 @@
+ this.running = false;
+ }
+
+- this.rconPassword = s;
++ this.rconPassword = rconPassword;
++ this.rconConsoleSource = new net.minecraft.server.rcon.RconConsoleSource(this.serverInterface, client.getRemoteSocketAddress()); // CraftBukkit
+ }
+
+ @Override
+ public void run() {
+- while (true) {
+- try {
++ // CraftBukkit start - decompile error: switch try / while statement
++ try {
++ while (true) {
++ // CraftBukkit end
+ if (!this.running) {
+ return;
+ }
+@@ -72,7 +80,7 @@
+ String s = PktUtils.stringFromByteArray(this.buf, k, i);
+
+ try {
+- this.sendCmdResponse(l, this.serverInterface.runCommand(s));
++ this.sendCmdResponse(l, this.serverInterface.runCommand(this.rconConsoleSource, s)); // CraftBukkit
+ } catch (Exception exception) {
+ this.sendCmdResponse(l, "Error executing: " + s + " (" + exception.getMessage() + ")");
+ }
+@@ -110,8 +119,10 @@
+ this.running = false;
+ }
+
+- return;
+- }
++ // CraftBukkit start - decompile error: switch try / while statement
++ // return;
++ // }
++ // CraftBukkit end
+ }
+
+ private void send(int i, int j, String s) throws IOException {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/stats/ServerRecipeBook.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/stats/ServerRecipeBook.java.patch
new file mode 100644
index 0000000000..8ffb0784be
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/stats/ServerRecipeBook.java.patch
@@ -0,0 +1,28 @@
+--- a/net/minecraft/stats/ServerRecipeBook.java
++++ b/net/minecraft/stats/ServerRecipeBook.java
+@@ -20,6 +20,8 @@
+ import net.minecraft.world.item.crafting.RecipeManager;
+ import org.slf4j.Logger;
+
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
++
+ public class ServerRecipeBook extends RecipeBook {
+
+ public static final String RECIPE_BOOK_TAG = "recipeBook";
+@@ -36,11 +38,11 @@
+ RecipeHolder<?> recipeholder = (RecipeHolder) iterator.next();
+ ResourceLocation resourcelocation = recipeholder.id();
+
+- if (!this.known.contains(resourcelocation) && !recipeholder.value().isSpecial()) {
+- this.add(resourcelocation);
+- this.addHighlight(resourcelocation);
+- list.add(resourcelocation);
+- CriteriaTriggers.RECIPE_UNLOCKED.trigger(serverplayer, recipeholder);
++ if (!this.known.contains(minecraftkey) && !recipeholder.value().isSpecial() && CraftEventFactory.handlePlayerRecipeListUpdateEvent(player, minecraftkey)) { // CraftBukkit
++ this.add(minecraftkey);
++ this.addHighlight(minecraftkey);
++ list.add(minecraftkey);
++ CriteriaTriggers.RECIPE_UNLOCKED.trigger(player, recipeholder);
+ ++i;
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/stats/ServerStatsCounter.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/stats/ServerStatsCounter.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/stats/ServerStatsCounter.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/stats/StatsCounter.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/stats/StatsCounter.java.patch
new file mode 100644
index 0000000000..8c5216bd18
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/stats/StatsCounter.java.patch
@@ -0,0 +1,15 @@
+--- a/net/minecraft/stats/StatsCounter.java
++++ b/net/minecraft/stats/StatsCounter.java
+@@ -16,6 +16,12 @@
+ public void increment(Player player, Stat<?> stat, int i) {
+ int j = (int) Math.min((long) this.getValue(stat) + (long) i, 2147483647L);
+
++ // CraftBukkit start - fire Statistic events
++ org.bukkit.event.Cancellable cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.handleStatisticsIncrease(player, stat, this.getValue(stat), j);
++ if (cancellable != null && cancellable.isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
+ this.setValue(player, stat, j);
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/util/SpawnUtil.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/util/SpawnUtil.java.patch
new file mode 100644
index 0000000000..3eb9602d45
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/util/SpawnUtil.java.patch
@@ -0,0 +1,31 @@
+--- a/net/minecraft/util/SpawnUtil.java
++++ b/net/minecraft/util/SpawnUtil.java
+@@ -20,9 +20,15 @@
+
+ public SpawnUtil() {}
+
+- public static <T extends Mob> Optional<T> trySpawnMob(EntityType<T> entitytype, MobSpawnType mobspawntype, ServerLevel serverlevel, BlockPos blockpos, int i, int j, int k, SpawnUtil.Strategy spawnutil_strategy) {
+- BlockPos.MutableBlockPos blockpos_mutableblockpos = blockpos.mutable();
++ public static <T extends Mob> Optional<T> trySpawnMob(EntityType<T> entityType, EnumMobSpawn spawnType, ServerLevel level, BlockPos pos, int attempts, int j, int yOffset, SpawnUtil.Strategy strategy) {
++ // CraftBukkit start
++ return trySpawnMob(entityType, spawnType, level, pos, attempts, j, yOffset, strategy, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
++ }
+
++ public static <T extends Mob> Optional<T> trySpawnMob(EntityType<T> entitytypes, EnumMobSpawn enummobspawn, ServerLevel worldserver, BlockPos blockposition, int i, int j, int k, SpawnUtil.Strategy spawnutil_a, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
++ // CraftBukkit end
++ BlockPos.MutableBlockPos blockposition_mutableblockposition = blockposition.mutable();
++
+ for (int l = 0; l < i; ++l) {
+ int i1 = Mth.randomBetweenInclusive(serverlevel.random, -j, j);
+ int j1 = Mth.randomBetweenInclusive(serverlevel.random, -j, j);
+@@ -32,8 +38,8 @@
+ T t0 = (Mob) entitytype.create(serverlevel, (CompoundTag) null, (Consumer) null, blockpos_mutableblockpos, mobspawntype, false, false);
+
+ if (t0 != null) {
+- if (t0.checkSpawnRules(serverlevel, mobspawntype) && t0.checkSpawnObstruction(serverlevel)) {
+- serverlevel.addFreshEntityWithPassengers(t0);
++ if (t0.checkSpawnRules(worldserver, enummobspawn) && t0.checkSpawnObstruction(worldserver)) {
++ worldserver.addFreshEntityWithPassengers(t0, reason); // CraftBukkit
+ return Optional.of(t0);
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/util/datafix/DataFixers.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/util/datafix/DataFixers.java.patch
new file mode 100644
index 0000000000..a30ad435a0
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/util/datafix/DataFixers.java.patch
@@ -0,0 +1,29 @@
+--- a/net/minecraft/util/datafix/DataFixers.java
++++ b/net/minecraft/util/datafix/DataFixers.java
+@@ -455,10 +279,22 @@
+ datafixerbuilder.addFixer(new EntityItemFrameDirectionFix(schema44, false));
+ Schema schema45 = datafixerbuilder.addSchema(1458, DataFixers.SAME_NAMESPACED);
+
+- datafixerbuilder.addFixer(new EntityCustomNameToComponentFix(schema45, false));
+- datafixerbuilder.addFixer(new ItemCustomNameToComponentFix(schema45, false));
+- datafixerbuilder.addFixer(new BlockEntityCustomNameToComponentFix(schema45, false));
+- Schema schema46 = datafixerbuilder.addSchema(1460, V1460::new);
++ // CraftBukkit start
++ builder.addFixer(new com.mojang.datafixers.DataFix(schema45, false) {
++ @Override
++ protected com.mojang.datafixers.TypeRewriteRule makeRule() {
++ return this.fixTypeEverywhereTyped("Player CustomName", this.getInputSchema().getType(DataConverterTypes.PLAYER), (typed) -> {
++ return typed.update(DSL.remainderFinder(), (dynamic) -> {
++ return EntityCustomNameToComponentFix.fixTagCustomName(dynamic);
++ });
++ });
++ }
++ });
++ // CraftBukkit end
++ builder.addFixer(new EntityCustomNameToComponentFix(schema45, false));
++ builder.addFixer(new ItemCustomNameToComponentFix(schema45, false));
++ builder.addFixer(new BlockEntityCustomNameToComponentFix(schema45, false));
++ Schema schema46 = builder.addSchema(1460, V1460::new);
+
+ datafixerbuilder.addFixer(new EntityPaintingMotiveFix(schema46, false));
+ Schema schema47 = datafixerbuilder.addSchema(1466, V1466::new);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java.patch
new file mode 100644
index 0000000000..e5e5128702
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java
++++ b/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java
+@@ -33,7 +32,7 @@
+ Typed<?> typed1 = typed.getOrCreateTyped(opticfinder1);
+ Dynamic<?> dynamic1 = (Dynamic) typed1.get(DSL.remainderFinder());
+
+- dynamic1 = dynamic1.set("map", dynamic1.createInt(dynamic.get("Damage").asInt(0)));
++ if (!dynamic1.getElement("map").result().isPresent()) dynamic1 = dynamic1.set("map", dynamic1.createInt(dynamic.get("Damage").asInt(0))); // CraftBukkit
+ return typed.set(opticfinder1, typed1.set(DSL.remainderFinder(), dynamic1));
+ } else {
+ return typed;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java.patch
new file mode 100644
index 0000000000..2153992c7f
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java
++++ b/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java
+@@ -377,7 +376,7 @@
+ Typed<?> typed2 = typed.getOrCreateTyped(opticfinder1);
+ Dynamic<?> dynamic1 = (Dynamic) typed2.get(DSL.remainderFinder());
+
+- dynamic1 = dynamic1.set("Damage", dynamic1.createInt(i));
++ if (i != 0) dynamic1 = dynamic1.set("Damage", dynamic1.createInt(i)); // CraftBukkit
+ typed1 = typed1.set(opticfinder1, typed2.set(DSL.remainderFinder(), dynamic1));
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/util/worldupdate/WorldUpgrader.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/util/worldupdate/WorldUpgrader.java.patch
new file mode 100644
index 0000000000..14d66bba93
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/util/worldupdate/WorldUpgrader.java.patch
@@ -0,0 +1,37 @@
+--- a/net/minecraft/util/worldupdate/WorldUpgrader.java
++++ b/net/minecraft/util/worldupdate/WorldUpgrader.java
+@@ -64,13 +64,13 @@
+ private static final Pattern REGEX = Pattern.compile("^r\\.(-?[0-9]+)\\.(-?[0-9]+)\\.mca$");
+ private final DimensionDataStorage overworldDataStorage;
+
+- public WorldUpgrader(LevelStorageSource.LevelStorageAccess levelstoragesource_levelstorageaccess, DataFixer datafixer, Registry<LevelStem> registry, boolean flag) {
+- this.dimensions = registry;
+- this.levels = (Set) registry.registryKeySet().stream().map(Registries::levelStemToLevel).collect(Collectors.toUnmodifiableSet());
+- this.eraseCache = flag;
+- this.dataFixer = datafixer;
+- this.levelStorage = levelstoragesource_levelstorageaccess;
+- this.overworldDataStorage = new DimensionDataStorage(this.levelStorage.getDimensionPath(Level.OVERWORLD).resolve("data").toFile(), datafixer);
++ public WorldUpgrader(LevelStorageSource.LevelStorageAccess levelStoarge, DataFixer dataFixer, Registry<LevelStem> dimensions, boolean eraseCache) {
++ this.dimensions = dimensions;
++ this.levels = (Set) java.util.stream.Stream.of(levelStoarge.dimensionType).map(Registries::levelStemToLevel).collect(Collectors.toUnmodifiableSet()); // CraftBukkit
++ this.eraseCache = eraseCache;
++ this.dataFixer = dataFixer;
++ this.levelStorage = levelStoarge;
++ this.overworldDataStorage = new DimensionDataStorage(this.levelStorage.getDimensionPath(Level.OVERWORLD).resolve("data").toFile(), dataFixer);
+ this.thread = WorldUpgrader.THREAD_FACTORY.newThread(this::work);
+ this.thread.setUncaughtExceptionHandler((thread, throwable) -> {
+ WorldUpgrader.LOGGER.error("Error upgrading world", throwable);
+@@ -145,10 +145,10 @@
+ if (compoundtag != null) {
+ int j = ChunkStorage.getVersion(compoundtag);
+ ChunkGenerator chunkgenerator = ((LevelStem) this.dimensions.getOrThrow(Registries.levelToLevelStem(resourcekey2))).generator();
+- CompoundTag compoundtag1 = chunkstorage.upgradeChunkTag(resourcekey2, () -> {
++ CompoundTag nbttagcompound1 = ichunkloader.upgradeChunkTag(Registries.levelToLevelStem(resourcekey2), () -> { // CraftBukkit
+ return this.overworldDataStorage;
+- }, compoundtag, chunkgenerator.getTypeNameForDataFixer());
+- ChunkPos chunkpos1 = new ChunkPos(compoundtag1.getInt("xPos"), compoundtag1.getInt("zPos"));
++ }, nbttagcompound, chunkgenerator.getTypeNameForDataFixer(), chunkcoordintpair, null); // CraftBukkit
++ ChunkPos chunkcoordintpair1 = new ChunkPos(nbttagcompound1.getInt("xPos"), nbttagcompound1.getInt("zPos"));
+
+ if (!chunkpos1.equals(chunkpos)) {
+ WorldUpgrader.LOGGER.warn("Chunk {} has invalid position {}", chunkpos, chunkpos1);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/CompoundContainer.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/CompoundContainer.java.patch
new file mode 100644
index 0000000000..0d3f513d34
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/CompoundContainer.java.patch
@@ -0,0 +1,57 @@
+--- a/net/minecraft/world/CompoundContainer.java
++++ b/net/minecraft/world/CompoundContainer.java
+@@ -3,17 +3,42 @@
+ import net.minecraft.world.entity.player.Player;
+ import net.minecraft.world.item.ItemStack;
+
++// CraftBukkit start
++import java.util.ArrayList;
++import java.util.List;
++import org.bukkit.Location;
++
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++// CraftBukkit end
++
+ public class CompoundContainer implements Container {
+
+ private final Container container1;
+ private final Container container2;
+
+- public CompoundContainer(Container container, Container container1) {
+- this.container1 = container;
+- this.container2 = container1;
++ // CraftBukkit start - add fields and methods
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++
++ public List<ItemStack> getContents() {
++ List<ItemStack> result = new ArrayList<ItemStack>(this.getContainerSize());
++ for (int i = 0; i < this.getContainerSize(); i++) {
++ result.add(this.getItem(i));
++ }
++ return result;
+ }
+
+ @Override
++ public Location getLocation() {
++ return container1.getLocation(); // TODO: right?
++ }
++ // CraftBukkit end
++
++ public CompoundContainer(Container container1, Container container2) {
++ this.container1 = container1;
++ this.container2 = container2;
++ }
++
+ @Override
+ public int getContainerSize() {
+ return this.container1.getContainerSize() + this.container2.getContainerSize();
+@@ -61,7 +105,7 @@
+ @Override
+ @Override
+ public int getMaxStackSize() {
+- return this.container1.getMaxStackSize();
++ return Math.min(this.container1.getMaxStackSize(), this.container2.getMaxStackSize()); // CraftBukkit - check both sides
+ }
+
+ @Override
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/Container.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/Container.java.patch
new file mode 100644
index 0000000000..7222f92079
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/Container.java.patch
@@ -0,0 +1,56 @@
+--- a/net/minecraft/world/Container.java
++++ b/net/minecraft/world/Container.java
+@@ -6,8 +6,12 @@
+ import net.minecraft.world.entity.player.Player;
+ import net.minecraft.world.item.Item;
+ import net.minecraft.world.item.ItemStack;
++// CraftBukkit start
++import net.minecraft.world.item.crafting.RecipeHolder;
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.block.entity.BlockEntity;
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++// CraftBukkit end
+
+ public interface Container extends Clearable {
+
+@@ -26,9 +30,7 @@
+
+ void setItem(int slot, ItemStack stack);
+
+- default int getMaxStackSize() {
+- return 64;
+- }
++ int getMaxStackSize(); // CraftBukkit
+
+ void setChanged();
+
+@@ -88,4 +90,29 @@
+
+ return level == null ? false : (level.getBlockEntity(blockpos) != blockentity ? false : player.distanceToSqr((double) blockpos.getX() + 0.5D, (double) blockpos.getY() + 0.5D, (double) blockpos.getZ() + 0.5D) <= (double) (i * i));
+ }
++
++ // CraftBukkit start
++ java.util.List<ItemStack> getContents();
++
++ void onOpen(CraftHumanEntity who);
++
++ void onClose(CraftHumanEntity who);
++
++ java.util.List<org.bukkit.entity.HumanEntity> getViewers();
++
++ org.bukkit.inventory.InventoryHolder getOwner();
++
++ void setMaxStackSize(int size);
++
++ org.bukkit.Location getLocation();
++
++ default RecipeHolder<?> getCurrentRecipe() {
++ return null;
++ }
++
++ default void setCurrentRecipe(RecipeHolder<?> recipe) {
++ }
++
++ int MAX_STACK = 64;
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/LockCode.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/LockCode.java.patch
new file mode 100644
index 0000000000..8787dc2f00
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/LockCode.java.patch
@@ -0,0 +1,37 @@
+--- a/net/minecraft/world/LockCode.java
++++ b/net/minecraft/world/LockCode.java
+@@ -4,6 +4,11 @@
+ import net.minecraft.nbt.CompoundTag;
+ import net.minecraft.world.item.ItemStack;
+
++// CraftBukkit start
++import org.bukkit.ChatColor;
++import org.bukkit.craftbukkit.util.CraftChatMessage;
++// CraftBukkit end
++
+ @Immutable
+ public class LockCode {
+
+@@ -15,8 +20,20 @@
+ this.key = s;
+ }
+
+- public boolean unlocksWith(ItemStack itemstack) {
+- return this.key.isEmpty() || !itemstack.isEmpty() && itemstack.hasCustomHoverName() && this.key.equals(itemstack.getHoverName().getString());
++ public boolean unlocksWith(ItemStack stack) {
++ // CraftBukkit start - SPIGOT-6307: Check for color codes if the lock contains color codes
++ if (this.key.isEmpty()) return true;
++ if (!stack.isEmpty() && stack.hasCustomHoverName()) {
++ if (this.key.indexOf(ChatColor.COLOR_CHAR) == -1) {
++ // The lock key contains no color codes, so let's ignore colors in the item display name (vanilla Minecraft behavior):
++ return this.key.equals(stack.getHoverName().getString());
++ } else {
++ // The lock key contains color codes, so let's take them into account:
++ return this.key.equals(CraftChatMessage.fromComponent(stack.getHoverName()));
++ }
++ }
++ return false;
++ // CraftBukkit end
+ }
+
+ public void addToTag(CompoundTag compoundtag) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/SimpleContainer.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/SimpleContainer.java.patch
new file mode 100644
index 0000000000..700beac642
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/SimpleContainer.java.patch
@@ -0,0 +1,76 @@
+--- a/net/minecraft/world/SimpleContainer.java
++++ b/net/minecraft/world/SimpleContainer.java
+@@ -14,6 +14,12 @@
+ import net.minecraft.world.item.Item;
+ import net.minecraft.world.item.ItemStack;
+
++// CraftBukkit start
++import org.bukkit.Location;
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++// CraftBukkit end
++
+ public class SimpleContainer implements Container, StackedContentsCompatible {
+
+ private final int size;
+@@ -21,7 +27,59 @@
+ @Nullable
+ private List<ContainerListener> listeners;
+
+- public SimpleContainer(int i) {
++ // CraftBukkit start - add fields and methods
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++ private int maxStack = MAX_STACK;
++ protected org.bukkit.inventory.InventoryHolder bukkitOwner;
++
++ public List<ItemStack> getContents() {
++ return this.items;
++ }
++
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ @Override
++ public int getMaxStackSize() {
++ return maxStack;
++ }
++
++ public void setMaxStackSize(int i) {
++ maxStack = i;
++ }
++
++ public org.bukkit.inventory.InventoryHolder getOwner() {
++ return bukkitOwner;
++ }
++
++ @Override
++ public Location getLocation() {
++ return null;
++ }
++
++ public SimpleContainer(SimpleContainer original) {
++ this(original.size);
++ for (int slot = 0; slot < original.size; slot++) {
++ this.items.set(slot, original.items.get(slot).copy());
++ }
++ }
++
++ public SimpleContainer(int size) {
++ this(size, null);
++ }
++
++ public SimpleContainer(int i, org.bukkit.inventory.InventoryHolder owner) {
++ this.bukkitOwner = owner;
++ // CraftBukkit end
+ this.size = i;
+ this.items = NonNullList.withSize(i, ItemStack.EMPTY);
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/damagesource/DamageSource.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/damagesource/DamageSource.java.patch
new file mode 100644
index 0000000000..94ba17f3b7
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/damagesource/DamageSource.java.patch
@@ -0,0 +1,43 @@
+--- a/net/minecraft/world/damagesource/DamageSource.java
++++ b/net/minecraft/world/damagesource/DamageSource.java
+@@ -20,8 +20,39 @@
+ private final Entity directEntity;
+ @Nullable
+ private final Vec3 damageSourcePosition;
++ // CraftBukkit start
++ private boolean sweep;
++ private boolean melting;
++ private boolean poison;
+
+- @Override
++ public boolean isSweep() {
++ return sweep;
++ }
++
++ public DamageSource sweep() {
++ this.sweep = true;
++ return this;
++ }
++
++ public boolean isMelting() {
++ return melting;
++ }
++
++ public DamageSource melting() {
++ this.melting = true;
++ return this;
++ }
++
++ public boolean isPoison() {
++ return poison;
++ }
++
++ public DamageSource poison() {
++ this.poison = true;
++ return this;
++ }
++ // CraftBukkit end
++
+ public String toString() {
+ return "DamageSource (" + this.type().msgId() + ")";
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/damagesource/DamageSources.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/damagesource/DamageSources.java.patch
new file mode 100644
index 0000000000..233f5cc911
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/damagesource/DamageSources.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/damagesource/DamageSources.java
++++ b/net/minecraft/world/damagesource/DamageSources.java
+@@ -41,9 +41,15 @@
+ private final DamageSource stalagmite;
+ private final DamageSource outsideBorder;
+ private final DamageSource genericKill;
++ // CraftBukkit start
++ public final DamageSource melting;
++ public final DamageSource poison;
+
+- public DamageSources(RegistryAccess registryaccess) {
+- this.damageTypes = registryaccess.registryOrThrow(Registries.DAMAGE_TYPE);
++ public DamageSources(RegistryAccess registry) {
++ this.damageTypes = registry.registryOrThrow(Registries.DAMAGE_TYPE);
++ this.melting = this.source(DamageTypes.ON_FIRE).melting();
++ this.poison = this.source(DamageTypes.MAGIC).poison();
++ // CraftBukkit end
+ this.inFire = this.source(DamageTypes.IN_FIRE);
+ this.lightningBolt = this.source(DamageTypes.LIGHTNING_BOLT);
+ this.onFire = this.source(DamageTypes.ON_FIRE);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/effect/HealOrHarmMobEffect.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/effect/HealOrHarmMobEffect.java.patch
new file mode 100644
index 0000000000..8433a065ed
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/effect/HealOrHarmMobEffect.java.patch
@@ -0,0 +1,31 @@
+--- a/net/minecraft/world/effect/HealOrHarmMobEffect.java
++++ b/net/minecraft/world/effect/HealOrHarmMobEffect.java
+@@ -14,11 +14,10 @@
+ }
+
+ @Override
+- @Override
+- public void applyEffectTick(LivingEntity livingentity, int i) {
+- super.applyEffectTick(livingentity, i);
+- if (this.isHarm == livingentity.isInvertedHealAndHarm()) {
+- livingentity.heal((float) Math.max(4 << i, 0));
++ public void applyEffectTick(LivingEntity livingEntity, int amplifier) {
++ super.applyEffectTick(livingEntity, amplifier);
++ if (this.isHarm == livingEntity.isInvertedHealAndHarm()) {
++ livingEntity.heal((float) Math.max(4 << amplifier, 0), org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC); // CraftBukkit
+ } else {
+ livingentity.hurt(livingentity.damageSources().magic(), (float) (6 << i));
+ }
+@@ -30,9 +28,9 @@
+ public void applyInstantenousEffect(@Nullable Entity entity, @Nullable Entity entity1, LivingEntity livingentity, int i, double d0) {
+ int j;
+
+- if (this.isHarm == livingentity.isInvertedHealAndHarm()) {
+- j = (int) (d0 * (double) (4 << i) + 0.5D);
+- livingentity.heal((float) j);
++ if (this.isHarm == livingEntity.isInvertedHealAndHarm()) {
++ j = (int) (health * (double) (4 << amplifier) + 0.5D);
++ livingEntity.heal((float) j, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC); // CraftBukkit
+ } else {
+ j = (int) (d0 * (double) (6 << i) + 0.5D);
+ if (entity == null) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/effect/HungerMobEffect.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/effect/HungerMobEffect.java.patch
new file mode 100644
index 0000000000..811fd56fa9
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/effect/HungerMobEffect.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/effect/HungerMobEffect.java
++++ b/net/minecraft/world/effect/HungerMobEffect.java
+@@ -16,7 +15,7 @@
+ if (livingentity instanceof Player) {
+ Player player = (Player) livingentity;
+
+- player.causeFoodExhaustion(0.005F * (float) (i + 1));
++ entityhuman.causeFoodExhaustion(0.005F * (float) (amplifier + 1), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.HUNGER_EFFECT); // CraftBukkit - EntityExhaustionEvent
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/effect/MobEffectUtil.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/effect/MobEffectUtil.java.patch
new file mode 100644
index 0000000000..c566068ab2
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/effect/MobEffectUtil.java.patch
@@ -0,0 +1,29 @@
+--- a/net/minecraft/world/effect/MobEffectUtil.java
++++ b/net/minecraft/world/effect/MobEffectUtil.java
+@@ -48,14 +48,20 @@
+ return livingentity.hasEffect(MobEffects.WATER_BREATHING) || livingentity.hasEffect(MobEffects.CONDUIT_POWER);
+ }
+
+- public static List<ServerPlayer> addEffectToPlayersAround(ServerLevel serverlevel, @Nullable Entity entity, Vec3 vec3, double d0, MobEffectInstance mobeffectinstance, int i) {
+- MobEffect mobeffect = mobeffectinstance.getEffect();
+- List<ServerPlayer> list = serverlevel.getPlayers((serverplayer) -> {
+- return serverplayer.gameMode.isSurvival() && (entity == null || !entity.isAlliedTo((Entity) serverplayer)) && vec3.closerThan(serverplayer.position(), d0) && (!serverplayer.hasEffect(mobeffect) || serverplayer.getEffect(mobeffect).getAmplifier() < mobeffectinstance.getAmplifier() || serverplayer.getEffect(mobeffect).endsWithin(i - 1));
++ public static List<ServerPlayer> addEffectToPlayersAround(ServerLevel level, @Nullable Entity source, Vec3 pos, double radius, MobEffectInstance mobeffect, int effect) {
++ // CraftBukkit start
++ return addEffectToPlayersAround(level, source, pos, radius, mobeffect, effect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN);
++ }
++
++ public static List<ServerPlayer> addEffectToPlayersAround(ServerLevel worldserver, @Nullable Entity entity, Vec3 vec3d, double d0, MobEffectInstance mobeffect, int i, org.bukkit.event.entity.EntityPotionEffectEvent.Cause cause) {
++ // CraftBukkit end
++ MobEffect mobeffectlist = mobeffect.getEffect();
++ List<ServerPlayer> list = worldserver.getPlayers((entityplayer) -> {
++ return entityplayer.gameMode.isSurvival() && (entity == null || !entity.isAlliedTo((Entity) entityplayer)) && vec3d.closerThan(entityplayer.position(), d0) && (!entityplayer.hasEffect(mobeffectlist) || entityplayer.getEffect(mobeffectlist).getAmplifier() < mobeffect.getAmplifier() || entityplayer.getEffect(mobeffectlist).endsWithin(i - 1));
+ });
+
+- list.forEach((serverplayer) -> {
+- serverplayer.addEffect(new MobEffectInstance(mobeffectinstance), entity);
++ list.forEach((entityplayer) -> {
++ entityplayer.addEffect(new MobEffectInstance(mobeffect), entity, cause); // CraftBukkit
+ });
+ return list;
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/effect/PoisonMobEffect.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/effect/PoisonMobEffect.java.patch
new file mode 100644
index 0000000000..6b728cb252
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/effect/PoisonMobEffect.java.patch
@@ -0,0 +1,18 @@
+--- a/net/minecraft/world/effect/PoisonMobEffect.java
++++ b/net/minecraft/world/effect/PoisonMobEffect.java
+@@ -9,11 +9,10 @@
+ }
+
+ @Override
+- @Override
+- public void applyEffectTick(LivingEntity livingentity, int i) {
+- super.applyEffectTick(livingentity, i);
+- if (livingentity.getHealth() > 1.0F) {
+- livingentity.hurt(livingentity.damageSources().magic(), 1.0F);
++ public void applyEffectTick(LivingEntity livingEntity, int amplifier) {
++ super.applyEffectTick(livingEntity, amplifier);
++ if (livingEntity.getHealth() > 1.0F) {
++ livingEntity.hurt(livingEntity.damageSources().poison, 1.0F); // CraftBukkit - DamageSource.MAGIC -> CraftEventFactory.POISON
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/effect/RegenerationMobEffect.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/effect/RegenerationMobEffect.java.patch
new file mode 100644
index 0000000000..e521009cff
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/effect/RegenerationMobEffect.java.patch
@@ -0,0 +1,18 @@
+--- a/net/minecraft/world/effect/RegenerationMobEffect.java
++++ b/net/minecraft/world/effect/RegenerationMobEffect.java
+@@ -9,11 +9,10 @@
+ }
+
+ @Override
+- @Override
+- public void applyEffectTick(LivingEntity livingentity, int i) {
+- super.applyEffectTick(livingentity, i);
+- if (livingentity.getHealth() < livingentity.getMaxHealth()) {
+- livingentity.heal(1.0F);
++ public void applyEffectTick(LivingEntity livingEntity, int amplifier) {
++ super.applyEffectTick(livingEntity, amplifier);
++ if (livingEntity.getHealth() < livingEntity.getMaxHealth()) {
++ livingEntity.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC_REGEN); // CraftBukkit
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/effect/SaturationMobEffect.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/effect/SaturationMobEffect.java.patch
new file mode 100644
index 0000000000..6c674df037
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/effect/SaturationMobEffect.java.patch
@@ -0,0 +1,30 @@
+--- a/net/minecraft/world/effect/SaturationMobEffect.java
++++ b/net/minecraft/world/effect/SaturationMobEffect.java
+@@ -2,6 +2,10 @@
+
+ import net.minecraft.world.entity.LivingEntity;
+ import net.minecraft.world.entity.player.Player;
++// CraftBukkit start
++import org.bukkit.craftbukkit.entity.CraftPlayer;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++// CraftBukkit end
+
+ class SaturationMobEffect extends InstantenousMobEffect {
+
+@@ -16,7 +19,15 @@
+ if (!livingentity.level().isClientSide && livingentity instanceof Player) {
+ Player player = (Player) livingentity;
+
+- player.getFoodData().eat(i + 1, 1.0F);
++ // CraftBukkit start
++ int oldFoodLevel = entityhuman.getFoodData().foodLevel;
++ org.bukkit.event.entity.FoodLevelChangeEvent event = CraftEventFactory.callFoodLevelChangeEvent(entityhuman, amplifier + 1 + oldFoodLevel);
++ if (!event.isCancelled()) {
++ entityhuman.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, 1.0F);
++ }
++
++ ((CraftPlayer) entityhuman.getBukkitEntity()).sendHealthUpdate();
++ // CraftBukkit end
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/AgeableMob.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/AgeableMob.java.patch
new file mode 100644
index 0000000000..a615763ef4
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/AgeableMob.java.patch
@@ -0,0 +1,49 @@
+--- a/net/minecraft/world/entity/AgeableMob.java
++++ b/net/minecraft/world/entity/AgeableMob.java
+@@ -20,6 +20,7 @@
+ protected int age;
+ protected int forcedAge;
+ protected int forcedAgeTimer;
++ public boolean ageLocked; // CraftBukkit
+
+ protected AgeableMob(EntityType<? extends AgeableMob> entitytype, Level level) {
+ super(entitytype, level);
+@@ -101,19 +100,19 @@
+ }
+
+ @Override
+- @Override
+- public void addAdditionalSaveData(CompoundTag compoundtag) {
+- super.addAdditionalSaveData(compoundtag);
+- compoundtag.putInt("Age", this.getAge());
+- compoundtag.putInt("ForcedAge", this.forcedAge);
++ public void addAdditionalSaveData(CompoundTag compound) {
++ super.addAdditionalSaveData(compound);
++ compound.putInt("Age", this.getAge());
++ compound.putInt("ForcedAge", this.forcedAge);
++ compound.putBoolean("AgeLocked", this.ageLocked); // CraftBukkit
+ }
+
+ @Override
+- @Override
+- public void readAdditionalSaveData(CompoundTag compoundtag) {
+- super.readAdditionalSaveData(compoundtag);
+- this.setAge(compoundtag.getInt("Age"));
+- this.forcedAge = compoundtag.getInt("ForcedAge");
++ public void readAdditionalSaveData(CompoundTag compound) {
++ super.readAdditionalSaveData(compound);
++ this.setAge(compound.getInt("Age"));
++ this.forcedAge = compound.getInt("ForcedAge");
++ this.ageLocked = compound.getBoolean("AgeLocked"); // CraftBukkit
+ }
+
+ @Override
+@@ -130,7 +127,7 @@
+ @Override
+ public void aiStep() {
+ super.aiStep();
+- if (this.level().isClientSide) {
++ if (this.level().isClientSide || ageLocked) { // CraftBukkit
+ if (this.forcedAgeTimer > 0) {
+ if (this.forcedAgeTimer % 4 == 0) {
+ this.level().addParticle(ParticleTypes.HAPPY_VILLAGER, this.getRandomX(1.0D), this.getRandomY() + 0.5D, this.getRandomZ(1.0D), 0.0D, 0.0D, 0.0D);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/AreaEffectCloud.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/AreaEffectCloud.java.patch
new file mode 100644
index 0000000000..183916f556
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/AreaEffectCloud.java.patch
@@ -0,0 +1,49 @@
+--- a/net/minecraft/world/entity/AreaEffectCloud.java
++++ b/net/minecraft/world/entity/AreaEffectCloud.java
+@@ -30,6 +30,9 @@
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.material.PushReaction;
+ import org.slf4j.Logger;
++import org.bukkit.craftbukkit.entity.CraftLivingEntity;
++import org.bukkit.entity.LivingEntity;
++// CraftBukkit end
+
+ public class AreaEffectCloud extends Entity implements TraceableEntity {
+
+@@ -267,6 +267,7 @@
+ if (!list1.isEmpty()) {
+ Iterator iterator1 = list1.iterator();
+
++ List<LivingEntity> entities = new java.util.ArrayList<LivingEntity>(); // CraftBukkit
+ while (iterator1.hasNext()) {
+ LivingEntity livingentity = (LivingEntity) iterator1.next();
+
+@@ -276,7 +277,18 @@
+ double d8 = d6 * d6 + d7 * d7;
+
+ if (d8 <= (double) (f * f)) {
+- this.victims.put(livingentity, this.tickCount + this.reapplicationDelay);
++ // CraftBukkit start
++ entities.add((LivingEntity) entityliving.getBukkitEntity());
++ }
++ }
++ }
++ org.bukkit.event.entity.AreaEffectCloudApplyEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callAreaEffectCloudApplyEvent(this, entities);
++ if (!event.isCancelled()) {
++ for (LivingEntity entity : event.getAffectedEntities()) {
++ if (entity instanceof CraftLivingEntity) {
++ net.minecraft.world.entity.LivingEntity entityliving = ((CraftLivingEntity) entity).getHandle();
++ // CraftBukkit end
++ this.victims.put(entityliving, this.tickCount + this.reapplicationDelay);
+ Iterator iterator2 = list.iterator();
+
+ while (iterator2.hasNext()) {
+@@ -285,7 +297,7 @@
+ if (mobeffectinstance1.getEffect().isInstantenous()) {
+ mobeffectinstance1.getEffect().applyInstantenousEffect(this, this.getOwner(), livingentity, mobeffectinstance1.getAmplifier(), 0.5D);
+ } else {
+- livingentity.addEffect(new MobEffectInstance(mobeffectinstance1), this);
++ entityliving.addEffect(new MobEffectInstance(mobeffect1), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.AREA_EFFECT_CLOUD); // CraftBukkit
+ }
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/Entity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/Entity.java.patch
new file mode 100644
index 0000000000..952a26da6e
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/Entity.java.patch
@@ -0,0 +1,1002 @@
+--- a/net/minecraft/world/entity/Entity.java
++++ b/net/minecraft/world/entity/Entity.java
+@@ -125,9 +126,63 @@
+ import net.minecraft.world.scores.Team;
+ import org.joml.Vector3f;
+ import org.slf4j.Logger;
++import org.bukkit.Bukkit;
++import org.bukkit.Location;
++import org.bukkit.Server;
++import org.bukkit.block.BlockFace;
++import org.bukkit.command.CommandSender;
++import org.bukkit.craftbukkit.event.CraftPortalEvent;
++import org.bukkit.entity.Hanging;
++import org.bukkit.entity.LivingEntity;
++import org.bukkit.entity.Vehicle;
++import org.bukkit.event.entity.EntityCombustByEntityEvent;
++import org.bukkit.event.hanging.HangingBreakByEntityEvent;
++import org.bukkit.event.vehicle.VehicleBlockCollisionEvent;
++import org.bukkit.event.vehicle.VehicleEnterEvent;
++import org.bukkit.event.vehicle.VehicleExitEvent;
++import org.bukkit.craftbukkit.CraftWorld;
++import org.bukkit.craftbukkit.entity.CraftEntity;
++import org.bukkit.craftbukkit.entity.CraftPlayer;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.craftbukkit.util.CraftLocation;
++import org.bukkit.entity.Pose;
++import org.bukkit.event.entity.EntityAirChangeEvent;
++import org.bukkit.event.entity.EntityCombustEvent;
++import org.bukkit.event.entity.EntityDropItemEvent;
++import org.bukkit.event.entity.EntityPortalEvent;
++import org.bukkit.event.entity.EntityPoseChangeEvent;
++import org.bukkit.event.player.PlayerTeleportEvent;
++import org.bukkit.plugin.PluginManager;
++// CraftBukkit end
+
+ public abstract class Entity implements Nameable, EntityAccess, CommandSource, ScoreHolder {
+
++ // CraftBukkit start
++ private static final int CURRENT_LEVEL = 2;
++ static boolean isLevelAtLeast(CompoundTag tag, int level) {
++ return tag.contains("Bukkit.updateLevel") && tag.getInt("Bukkit.updateLevel") >= level;
++ }
++
++ private CraftEntity bukkitEntity;
++
++ public CraftEntity getBukkitEntity() {
++ if (bukkitEntity == null) {
++ bukkitEntity = CraftEntity.getEntity(level.getCraftServer(), this);
++ }
++ return bukkitEntity;
++ }
++
++ @Override
++ public CommandSender getBukkitSender(CommandSourceStack wrapper) {
++ return getBukkitEntity();
++ }
++
++ // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
++ public int getDefaultMaxAirSupply() {
++ return TOTAL_AIR_SUPPLY;
++ }
++ // CraftBukkit end
++
+ private static final Logger LOGGER = LogUtils.getLogger();
+ public static final String ID_TAG = "id";
+ public static final String PASSENGERS_TAG = "Passengers";
+@@ -241,9 +296,32 @@
+ private int lastCrystalSoundPlayTick;
+ private boolean hasVisualFire;
+ @Nullable
+- private BlockState feetBlockState;
++ private IBlockData feetBlockState;
++ // CraftBukkit start
++ public boolean persist = true;
++ public boolean visibleByDefault = true;
++ public boolean valid;
++ public boolean inWorld = false;
++ public boolean generation;
++ public int maxAirTicks = getDefaultMaxAirSupply(); // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
++ public org.bukkit.projectiles.ProjectileSource projectileSource; // For projectiles only
++ public boolean lastDamageCancelled; // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Keep track if the event was canceled
++ public boolean persistentInvisibility = false;
++ public BlockPos lastLavaContact;
++ // Marks an entity, that it was removed by a plugin via Entity#remove
++ // Main use case currently is for SPIGOT-7487, preventing dropping of leash when leash is removed
++ public boolean pluginRemoved = false;
+
+- public Entity(EntityType<?> entitytype, Level level) {
++ public float getBukkitYaw() {
++ return this.yRot;
++ }
++
++ public boolean isChunkLoaded() {
++ return level.hasChunk((int) Math.floor(this.getX()) >> 4, (int) Math.floor(this.getZ()) >> 4);
++ }
++ // CraftBukkit end
++
++ public Entity(EntityType<?> entityType, Level level) {
+ this.id = Entity.ENTITY_COUNTER.incrementAndGet();
+ this.passengers = ImmutableList.of();
+ this.deltaMovement = Vec3.ZERO;
+@@ -377,7 +452,13 @@
+
+ public void onClientRemoval() {}
+
+- public void setPose(Pose pose) {
++ public void setPose(EntityPose pose) {
++ // CraftBukkit start
++ if (pose == this.getPose()) {
++ return;
++ }
++ this.level.getCraftServer().getPluginManager().callEvent(new EntityPoseChangeEvent(this.getBukkitEntity(), Pose.values()[pose.ordinal()]));
++ // CraftBukkit end
+ this.entityData.set(Entity.DATA_POSE, pose);
+ }
+
+@@ -401,9 +482,36 @@
+ return Mth.lengthSquared(d2, d4) < Mth.square(d0) && Mth.square(d3) < Mth.square(d1);
+ }
+
+- protected void setRot(float f, float f1) {
+- this.setYRot(f % 360.0F);
+- this.setXRot(f1 % 360.0F);
++ protected void setRot(float yRot, float xRot) {
++ // CraftBukkit start - yaw was sometimes set to NaN, so we need to set it back to 0
++ if (Float.isNaN(yRot)) {
++ yRot = 0;
++ }
++
++ if (yRot == Float.POSITIVE_INFINITY || yRot == Float.NEGATIVE_INFINITY) {
++ if (this instanceof ServerPlayer) {
++ this.level.getCraftServer().getLogger().warning(this.getScoreboardName() + " was caught trying to crash the server with an invalid yaw");
++ ((CraftPlayer) this.getBukkitEntity()).kickPlayer("Infinite yaw (Hacking?)");
++ }
++ yRot = 0;
++ }
++
++ // pitch was sometimes set to NaN, so we need to set it back to 0
++ if (Float.isNaN(xRot)) {
++ xRot = 0;
++ }
++
++ if (xRot == Float.POSITIVE_INFINITY || xRot == Float.NEGATIVE_INFINITY) {
++ if (this instanceof ServerPlayer) {
++ this.level.getCraftServer().getLogger().warning(this.getScoreboardName() + " was caught trying to crash the server with an invalid pitch");
++ ((CraftPlayer) this.getBukkitEntity()).kickPlayer("Infinite pitch (Hacking?)");
++ }
++ xRot = 0;
++ }
++ // CraftBukkit end
++
++ this.setYRot(yRot % 360.0F);
++ this.setXRot(xRot % 360.0F);
+ }
+
+ public final void setPos(Vec3 vec3) {
+@@ -443,6 +551,15 @@
+ this.baseTick();
+ }
+
++ // CraftBukkit start
++ public void postTick() {
++ // No clean way to break out of ticking once the entity has been copied to a new world, so instead we move the portalling later in the tick cycle
++ if (!(this instanceof ServerPlayer)) {
++ this.handleNetherPortal();
++ }
++ }
++ // CraftBukkit end
++
+ public void baseTick() {
+ this.level().getProfiler().push("entityBaseTick");
+ this.feetBlockState = null;
+@@ -457,7 +574,7 @@
+ this.walkDistO = this.walkDist;
+ this.xRotO = this.getXRot();
+ this.yRotO = this.getYRot();
+- this.handleNetherPortal();
++ if (this instanceof ServerPlayer) this.handleNetherPortal(); // CraftBukkit - // Moved up to postTick
+ if (this.canSpawnSprintParticle()) {
+ this.spawnSprintParticle();
+ }
+@@ -492,6 +609,10 @@
+ if (this.isInLava()) {
+ this.lavaHurt();
+ this.fallDistance *= 0.5F;
++ // CraftBukkit start
++ } else {
++ this.lastLavaContact = null;
++ // CraftBukkit end
+ }
+
+ this.checkBelowWorld();
+@@ -543,15 +664,48 @@
+
+ public void lavaHurt() {
+ if (!this.fireImmune()) {
+- this.setSecondsOnFire(15);
++ // CraftBukkit start - Fallen in lava TODO: this event spams!
++ if (this instanceof net.minecraft.world.entity.LivingEntity && remainingFireTicks <= 0) {
++ // not on fire yet
++ org.bukkit.block.Block damager = (lastLavaContact == null) ? null : org.bukkit.craftbukkit.block.CraftBlock.at(level, lastLavaContact);
++ org.bukkit.entity.Entity damagee = this.getBukkitEntity();
++ EntityCombustEvent combustEvent = new org.bukkit.event.entity.EntityCombustByBlockEvent(damager, damagee, 15);
++ this.level.getCraftServer().getPluginManager().callEvent(combustEvent);
++
++ if (!combustEvent.isCancelled()) {
++ this.setSecondsOnFire(combustEvent.getDuration(), false);
++ }
++ } else {
++ // This will be called every single tick the entity is in lava, so don't throw an event
++ this.setSecondsOnFire(15, false);
++ }
++ CraftEventFactory.blockDamage = (lastLavaContact) == null ? null : org.bukkit.craftbukkit.block.CraftBlock.at(level, lastLavaContact);
+ if (this.hurt(this.damageSources().lava(), 4.0F)) {
+ this.playSound(SoundEvents.GENERIC_BURN, 0.4F, 2.0F + this.random.nextFloat() * 0.4F);
+ }
++ CraftEventFactory.blockDamage = null;
++ // CraftBukkit end - we also don't throw an event unless the object in lava is living, to save on some event calls
+
+ }
+ }
+
+- public void setSecondsOnFire(int i) {
++ public void setSecondsOnFire(int seconds) {
++ // CraftBukkit start
++ this.setSecondsOnFire(seconds, true);
++ }
++
++ public void setSecondsOnFire(int i, boolean callEvent) {
++ if (callEvent) {
++ EntityCombustEvent event = new EntityCombustEvent(this.getBukkitEntity(), i);
++ this.level.getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return;
++ }
++
++ i = event.getDuration();
++ }
++ // CraftBukkit end
+ int j = i * 20;
+
+ if (this instanceof LivingEntity) {
+@@ -702,6 +856,28 @@
+ block.updateEntityAfterFallOn(this.level(), this);
+ }
+
++ // CraftBukkit start
++ if (horizontalCollision && getBukkitEntity() instanceof Vehicle) {
++ Vehicle vehicle = (Vehicle) this.getBukkitEntity();
++ org.bukkit.block.Block bl = this.level.getWorld().getBlockAt(Mth.floor(this.getX()), Mth.floor(this.getY()), Mth.floor(this.getZ()));
++
++ if (pos.x > vec3d1.x) {
++ bl = bl.getRelative(BlockFace.EAST);
++ } else if (pos.x < vec3d1.x) {
++ bl = bl.getRelative(BlockFace.WEST);
++ } else if (pos.z > vec3d1.z) {
++ bl = bl.getRelative(BlockFace.SOUTH);
++ } else if (pos.z < vec3d1.z) {
++ bl = bl.getRelative(BlockFace.NORTH);
++ }
++
++ if (!bl.getType().isAir()) {
++ VehicleBlockCollisionEvent event = new VehicleBlockCollisionEvent(vehicle, bl);
++ level.getCraftServer().getPluginManager().callEvent(event);
++ }
++ }
++ // CraftBukkit end
++
+ if (this.onGround()) {
+ block.stepOn(this.level(), blockpos, blockstate, this);
+ }
+@@ -1029,6 +1205,20 @@
+ return SoundEvents.GENERIC_SPLASH;
+ }
+
++ // CraftBukkit start - Add delegate methods
++ public SoundEvent getSwimSound0() {
++ return getSwimSound();
++ }
++
++ public SoundEvent getSwimSplashSound0() {
++ return getSwimSplashSound();
++ }
++
++ public SoundEvent getSwimHighSpeedSplashSound0() {
++ return getSwimHighSpeedSplashSound();
++ }
++ // CraftBukkit end
++
+ protected void checkInsideBlocks() {
+ AABB aabb = this.getBoundingBox();
+ BlockPos blockpos = BlockPos.containing(aabb.minX + 1.0E-7D, aabb.minY + 1.0E-7D, aabb.minZ + 1.0E-7D);
+@@ -1443,6 +1633,7 @@
+ this.yo = d1;
+ this.zo = d4;
+ this.setPos(d3, d1, d4);
++ if (valid) level.getChunk((int) Math.floor(this.getX()) >> 4, (int) Math.floor(this.getZ()) >> 4); // CraftBukkit
+ }
+
+ public void moveTo(Vec3 vec3) {
+@@ -1637,9 +1828,15 @@
+ return false;
+ }
+
+- public void awardKillScore(Entity entity, int i, DamageSource damagesource) {
+- if (entity instanceof ServerPlayer) {
+- CriteriaTriggers.ENTITY_KILLED_PLAYER.trigger((ServerPlayer) entity, this, damagesource);
++ // CraftBukkit start - collidable API
++ public boolean canCollideWithBukkit(Entity entity) {
++ return isPushable();
++ }
++ // CraftBukkit end
++
++ public void awardKillScore(Entity killed, int scoreValue, DamageSource source) {
++ if (killed instanceof ServerPlayer) {
++ CriteriaTriggers.ENTITY_KILLED_PLAYER.trigger((ServerPlayer) killed, this, source);
+ }
+
+ }
+@@ -1664,17 +1861,23 @@
+ return d0 < d1 * d1;
+ }
+
+- public boolean saveAsPassenger(CompoundTag compoundtag) {
++ public boolean saveAsPassenger(CompoundTag compound) {
++ // CraftBukkit start - allow excluding certain data when saving
++ return saveAsPassenger(compound, true);
++ }
++
++ public boolean saveAsPassenger(CompoundTag nbttagcompound, boolean includeAll) {
++ // CraftBukkit end
+ if (this.removalReason != null && !this.removalReason.shouldSave()) {
+ return false;
+ } else {
+ String s = this.getEncodeId();
+
+- if (s == null) {
++ if (!this.persist || s == null) { // CraftBukkit - persist flag
+ return false;
+ } else {
+- compoundtag.putString("id", s);
+- this.saveWithoutId(compoundtag);
++ nbttagcompound.putString("id", s);
++ this.saveWithoutId(nbttagcompound, includeAll); // CraftBukkit - pass on includeAll
+ return true;
+ }
+ }
+@@ -1684,13 +1887,23 @@
+ return this.isPassenger() ? false : this.saveAsPassenger(compoundtag);
+ }
+
+- public CompoundTag saveWithoutId(CompoundTag compoundtag) {
++ public CompoundTag saveWithoutId(CompoundTag compound) {
++ // CraftBukkit start - allow excluding certain data when saving
++ return saveWithoutId(compound, true);
++ }
++
++ public CompoundTag saveWithoutId(CompoundTag nbttagcompound, boolean includeAll) {
++ // CraftBukkit end
+ try {
+- if (this.vehicle != null) {
+- compoundtag.put("Pos", this.newDoubleList(this.vehicle.getX(), this.getY(), this.vehicle.getZ()));
+- } else {
+- compoundtag.put("Pos", this.newDoubleList(this.getX(), this.getY(), this.getZ()));
++ // CraftBukkit start - selectively save position
++ if (includeAll) {
++ if (this.vehicle != null) {
++ nbttagcompound.put("Pos", this.newDoubleList(this.vehicle.getX(), this.getY(), this.vehicle.getZ()));
++ } else {
++ nbttagcompound.put("Pos", this.newDoubleList(this.getX(), this.getY(), this.getZ()));
++ }
+ }
++ // CraftBukkit end
+
+ Vec3 vec3 = this.getDeltaMovement();
+
+@@ -1705,10 +1909,52 @@
+ compoundtag.putUUID("UUID", this.getUUID());
+ Component component = this.getCustomName();
+
+- if (component != null) {
+- compoundtag.putString("CustomName", Component.Serializer.toJson(component));
++ // CraftBukkit start - Checking for NaN pitch/yaw and resetting to zero
++ // TODO: make sure this is the best way to address this.
++ if (Float.isNaN(this.yRot)) {
++ this.yRot = 0;
+ }
+
++ if (Float.isNaN(this.xRot)) {
++ this.xRot = 0;
++ }
++ // CraftBukkit end
++
++ nbttagcompound.put("Rotation", this.newFloatList(this.getYRot(), this.getXRot()));
++ nbttagcompound.putFloat("FallDistance", this.fallDistance);
++ nbttagcompound.putShort("Fire", (short) this.remainingFireTicks);
++ nbttagcompound.putShort("Air", (short) this.getAirSupply());
++ nbttagcompound.putBoolean("OnGround", this.onGround());
++ nbttagcompound.putBoolean("Invulnerable", this.invulnerable);
++ nbttagcompound.putInt("PortalCooldown", this.portalCooldown);
++ // CraftBukkit start - selectively save uuid and world
++ if (includeAll) {
++ nbttagcompound.putUUID("UUID", this.getUUID());
++ // PAIL: Check above UUID reads 1.8 properly, ie: UUIDMost / UUIDLeast
++ nbttagcompound.putLong("WorldUUIDLeast", ((ServerLevel) this.level).getWorld().getUID().getLeastSignificantBits());
++ nbttagcompound.putLong("WorldUUIDMost", ((ServerLevel) this.level).getWorld().getUID().getMostSignificantBits());
++ }
++ nbttagcompound.putInt("Bukkit.updateLevel", CURRENT_LEVEL);
++ if (!this.persist) {
++ nbttagcompound.putBoolean("Bukkit.persist", this.persist);
++ }
++ if (!this.visibleByDefault) {
++ nbttagcompound.putBoolean("Bukkit.visibleByDefault", this.visibleByDefault);
++ }
++ if (this.persistentInvisibility) {
++ nbttagcompound.putBoolean("Bukkit.invisible", this.persistentInvisibility);
++ }
++ // SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
++ if (maxAirTicks != getDefaultMaxAirSupply()) {
++ nbttagcompound.putInt("Bukkit.MaxAirSupply", getMaxAirSupply());
++ }
++ // CraftBukkit end
++ Component ichatbasecomponent = this.getCustomName();
++
++ if (ichatbasecomponent != null) {
++ nbttagcompound.putString("CustomName", Component.Serializer.toJson(ichatbasecomponent));
++ }
++
+ if (this.isCustomNameVisible()) {
+ compoundtag.putBoolean("CustomNameVisible", this.isCustomNameVisible());
+ }
+@@ -1751,7 +1997,7 @@
+ compoundtag.put("Tags", listtag);
+ }
+
+- this.addAdditionalSaveData(compoundtag);
++ this.addAdditionalSaveData(nbttagcompound, includeAll); // CraftBukkit - pass on includeAll
+ if (this.isVehicle()) {
+ listtag = new ListTag();
+ iterator = this.getPassengers().iterator();
+@@ -1760,8 +2006,8 @@
+ Entity entity = (Entity) iterator.next();
+ CompoundTag compoundtag1 = new CompoundTag();
+
+- if (entity.saveAsPassenger(compoundtag1)) {
+- listtag.add(compoundtag1);
++ if (entity.saveAsPassenger(nbttagcompound1, includeAll)) { // CraftBukkit - pass on includeAll
++ nbttaglist.add(nbttagcompound1);
+ }
+ }
+
+@@ -1770,7 +2016,12 @@
+ }
+ }
+
+- return compoundtag;
++ // CraftBukkit start - stores eventually existing bukkit values
++ if (this.bukkitEntity != null) {
++ this.bukkitEntity.storeBukkitValues(nbttagcompound);
++ }
++ // CraftBukkit end
++ return nbttagcompound;
+ } catch (Throwable throwable) {
+ CrashReport crashreport = CrashReport.forThrowable(throwable, "Saving entity NBT");
+ CrashReportCategory crashreportcategory = crashreport.addCategory("Entity being saved");
+@@ -1853,6 +2104,45 @@
+ } else {
+ throw new IllegalStateException("Entity has invalid position");
+ }
++
++ // CraftBukkit start
++ this.persist = !compound.contains("Bukkit.persist") || compound.getBoolean("Bukkit.persist");
++ this.visibleByDefault = !compound.contains("Bukkit.visibleByDefault") || compound.getBoolean("Bukkit.visibleByDefault");
++ // SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
++ if (compound.contains("Bukkit.MaxAirSupply")) {
++ maxAirTicks = compound.getInt("Bukkit.MaxAirSupply");
++ }
++ // CraftBukkit end
++
++ // CraftBukkit start - Reset world
++ if (this instanceof ServerPlayer) {
++ Server server = Bukkit.getServer();
++ org.bukkit.World bworld = null;
++
++ // TODO: Remove World related checks, replaced with WorldUID
++ String worldName = compound.getString("world");
++
++ if (compound.contains("WorldUUIDMost") && compound.contains("WorldUUIDLeast")) {
++ UUID uid = new UUID(compound.getLong("WorldUUIDMost"), compound.getLong("WorldUUIDLeast"));
++ bworld = server.getWorld(uid);
++ } else {
++ bworld = server.getWorld(worldName);
++ }
++
++ if (bworld == null) {
++ bworld = ((org.bukkit.craftbukkit.CraftServer) server).getServer().getLevel(Level.OVERWORLD).getWorld();
++ }
++
++ ((ServerPlayer) this).setLevel(bworld == null ? null : ((CraftWorld) bworld).getHandle());
++ }
++ this.getBukkitEntity().readBukkitValues(compound);
++ if (compound.contains("Bukkit.invisible")) {
++ boolean bukkitInvisible = compound.getBoolean("Bukkit.invisible");
++ this.setInvisible(bukkitInvisible);
++ this.persistentInvisibility = bukkitInvisible;
++ }
++ // CraftBukkit end
++
+ } catch (Throwable throwable) {
+ CrashReport crashreport = CrashReport.forThrowable(throwable, "Loading entity NBT");
+ CrashReportCategory crashreportcategory = crashreport.addCategory("Entity being loaded");
+@@ -1874,6 +2164,12 @@
+ return entitytype.canSerialize() && resourcelocation != null ? resourcelocation.toString() : null;
+ }
+
++ // CraftBukkit start - allow excluding certain data when saving
++ protected void addAdditionalSaveData(CompoundTag nbttagcompound, boolean includeAll) {
++ addAdditionalSaveData(nbttagcompound);
++ }
++ // CraftBukkit end
++
+ protected abstract void readAdditionalSaveData(CompoundTag compound);
+
+ protected abstract void addAdditionalSaveData(CompoundTag compound);
+@@ -1928,11 +2224,24 @@
+ } else if (this.level().isClientSide) {
+ return null;
+ } else {
+- ItemEntity itementity = new ItemEntity(this.level(), this.getX(), this.getY() + (double) f, this.getZ(), itemstack);
++ // CraftBukkit start - Capture drops for death event
++ if (this instanceof net.minecraft.world.entity.LivingEntity && !((net.minecraft.world.entity.LivingEntity) this).forceDrops) {
++ ((net.minecraft.world.entity.LivingEntity) this).drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(stack));
++ return null;
++ }
++ // CraftBukkit end
++ ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY() + (double) offsetY, this.getZ(), stack);
+
+- itementity.setDefaultPickUpDelay();
+- this.level().addFreshEntity(itementity);
+- return itementity;
++ entityitem.setDefaultPickUpDelay();
++ // CraftBukkit start
++ EntityDropItemEvent event = new EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity());
++ Bukkit.getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ return null;
++ }
++ // CraftBukkit end
++ this.level().addFreshEntity(entityitem);
++ return entityitem;
+ }
+ }
+
+@@ -2028,6 +2337,18 @@
+ if (!flag && (!this.canRide(entity) || !entity.canAddPassenger(this))) {
+ return false;
+ } else {
++ // CraftBukkit start
++ if (vehicle.getBukkitEntity() instanceof Vehicle && this.getBukkitEntity() instanceof LivingEntity) {
++ VehicleEnterEvent event = new VehicleEnterEvent((Vehicle) vehicle.getBukkitEntity(), this.getBukkitEntity());
++ // Suppress during worldgen
++ if (this.valid) {
++ Bukkit.getPluginManager().callEvent(event);
++ }
++ if (event.isCancelled()) {
++ return false;
++ }
++ }
++ // CraftBukkit end
+ if (this.isPassenger()) {
+ this.stopRiding();
+ }
+@@ -2061,7 +2382,7 @@
+ Entity entity = this.vehicle;
+
+ this.vehicle = null;
+- entity.removePassenger(this);
++ if (!entity.removePassenger(this)) this.vehicle = entity; // CraftBukkit
+ }
+
+ }
+@@ -2092,10 +2413,29 @@
+ }
+ }
+
+- protected void removePassenger(Entity entity) {
++ protected boolean removePassenger(Entity entity) { // CraftBukkit
+ if (entity.getVehicle() == this) {
+ throw new IllegalStateException("Use x.stopRiding(y), not y.removePassenger(x)");
+ } else {
++ // CraftBukkit start
++ CraftEntity craft = (CraftEntity) entity.getBukkitEntity().getVehicle();
++ Entity orig = craft == null ? null : craft.getHandle();
++ if (getBukkitEntity() instanceof Vehicle && entity.getBukkitEntity() instanceof LivingEntity) {
++ VehicleExitEvent event = new VehicleExitEvent(
++ (Vehicle) getBukkitEntity(),
++ (LivingEntity) entity.getBukkitEntity()
++ );
++ // Suppress during worldgen
++ if (this.valid) {
++ Bukkit.getPluginManager().callEvent(event);
++ }
++ CraftEntity craftn = (CraftEntity) entity.getBukkitEntity().getVehicle();
++ Entity n = craftn == null ? null : craftn.getHandle();
++ if (event.isCancelled() || n != orig) {
++ return false;
++ }
++ }
++ // CraftBukkit end
+ if (this.passengers.size() == 1 && this.passengers.get(0) == entity) {
+ this.passengers = ImmutableList.of();
+ } else {
+@@ -2107,6 +2447,7 @@
+ entity.boardingCooldown = 60;
+ this.gameEvent(GameEvent.ENTITY_DISMOUNT, entity);
+ }
++ return true; // CraftBukkit
+ }
+
+ protected boolean canAddPassenger(Entity entity) {
+@@ -2192,15 +2533,21 @@
+ ServerLevel serverlevel = (ServerLevel) this.level();
+
+ if (this.isInsidePortal) {
+- MinecraftServer minecraftserver = serverlevel.getServer();
+- ResourceKey<Level> resourcekey = this.level().dimension() == Level.NETHER ? Level.OVERWORLD : Level.NETHER;
+- ServerLevel serverlevel1 = minecraftserver.getLevel(resourcekey);
++ MinecraftServer minecraftserver = worldserver.getServer();
++ ResourceKey<Level> resourcekey = this.level().getTypeKey() == LevelStem.NETHER ? Level.OVERWORLD : Level.NETHER; // CraftBukkit
++ ServerLevel worldserver1 = minecraftserver.getLevel(resourcekey);
+
+- if (serverlevel1 != null && minecraftserver.isNetherEnabled() && !this.isPassenger() && this.portalTime++ >= i) {
++ if (true && !this.isPassenger() && this.portalTime++ >= i) { // CraftBukkit
+ this.level().getProfiler().push("portal");
+ this.portalTime = i;
+ this.setPortalCooldown();
+- this.changeDimension(serverlevel1);
++ // CraftBukkit start
++ if (this instanceof ServerPlayer) {
++ ((ServerPlayer) this).changeDimension(worldserver1, PlayerTeleportEvent.TeleportCause.NETHER_PORTAL);
++ } else {
++ this.changeDimension(worldserver1);
++ }
++ // CraftBukkit end
+ this.level().getProfiler().pop();
+ }
+
+@@ -2323,8 +2670,15 @@
+ return this.isVisuallySwimming() && !this.isInWater();
+ }
+
+- public void setSwimming(boolean flag) {
+- this.setSharedFlag(4, flag);
++ public void setSwimming(boolean swimming) {
++ // CraftBukkit start
++ if (valid && this.isSwimming() != swimming && this instanceof net.minecraft.world.entity.LivingEntity) {
++ if (CraftEventFactory.callToggleSwimEvent((net.minecraft.world.entity.LivingEntity) this, swimming).isCancelled()) {
++ return;
++ }
++ }
++ // CraftBukkit end
++ this.setSharedFlag(4, swimming);
+ }
+
+ public final boolean hasGlowingTag() {
+@@ -2373,8 +2727,12 @@
+ return this.getTeam() != null ? this.getTeam().isAlliedTo(team) : false;
+ }
+
+- public void setInvisible(boolean flag) {
+- this.setSharedFlag(5, flag);
++ // CraftBukkit - start
++ public void setInvisible(boolean invisible) {
++ if (!this.persistentInvisibility) { // Prevent Minecraft from removing our invisibility flag
++ this.setSharedFlag(5, invisible);
++ }
++ // CraftBukkit - end
+ }
+
+ protected boolean getSharedFlag(int i) {
+@@ -2393,15 +2751,26 @@
+ }
+
+ public int getMaxAirSupply() {
+- return 300;
++ return maxAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
+ }
+
+ public int getAirSupply() {
+ return (Integer) this.entityData.get(Entity.DATA_AIR_SUPPLY_ID);
+ }
+
+- public void setAirSupply(int i) {
+- this.entityData.set(Entity.DATA_AIR_SUPPLY_ID, i);
++ public void setAirSupply(int air) {
++ // CraftBukkit start
++ EntityAirChangeEvent event = new EntityAirChangeEvent(this.getBukkitEntity(), air);
++ // Suppress during worldgen
++ if (this.valid) {
++ event.getEntity().getServer().getPluginManager().callEvent(event);
++ }
++ if (event.isCancelled() && this.getAirSupply() != air) {
++ this.entityData.markDirty(Entity.DATA_AIR_SUPPLY_ID);
++ return;
++ }
++ this.entityData.set(Entity.DATA_AIR_SUPPLY_ID, event.getAmount());
++ // CraftBukkit end
+ }
+
+ public int getTicksFrozen() {
+@@ -2428,11 +2797,41 @@
+
+ public void thunderHit(ServerLevel serverlevel, LightningBolt lightningbolt) {
+ this.setRemainingFireTicks(this.remainingFireTicks + 1);
++ // CraftBukkit start
++ final org.bukkit.entity.Entity thisBukkitEntity = this.getBukkitEntity();
++ final org.bukkit.entity.Entity stormBukkitEntity = lightning.getBukkitEntity();
++ final PluginManager pluginManager = Bukkit.getPluginManager();
++ // CraftBukkit end
++
+ if (this.remainingFireTicks == 0) {
+- this.setSecondsOnFire(8);
++ // CraftBukkit start - Call a combust event when lightning strikes
++ EntityCombustByEntityEvent entityCombustEvent = new EntityCombustByEntityEvent(stormBukkitEntity, thisBukkitEntity, 8);
++ pluginManager.callEvent(entityCombustEvent);
++ if (!entityCombustEvent.isCancelled()) {
++ this.setSecondsOnFire(entityCombustEvent.getDuration(), false);
++ }
++ // CraftBukkit end
+ }
+
+- this.hurt(this.damageSources().lightningBolt(), 5.0F);
++ // CraftBukkit start
++ if (thisBukkitEntity instanceof Hanging) {
++ HangingBreakByEntityEvent hangingEvent = new HangingBreakByEntityEvent((Hanging) thisBukkitEntity, stormBukkitEntity);
++ pluginManager.callEvent(hangingEvent);
++
++ if (hangingEvent.isCancelled()) {
++ return;
++ }
++ }
++
++ if (this.fireImmune()) {
++ return;
++ }
++ CraftEventFactory.entityDamage = lightning;
++ if (!this.hurt(this.damageSources().lightningBolt(), 5.0F)) {
++ CraftEventFactory.entityDamage = null;
++ return;
++ }
++ // CraftBukkit end
+ }
+
+ public void onAboveBubbleCol(boolean flag) {
+@@ -2598,27 +2995,62 @@
+ }
+
+ @Nullable
+- public Entity changeDimension(ServerLevel serverlevel) {
++ public Entity changeDimension(ServerLevel destination) {
++ // CraftBukkit start
++ return teleportTo(destination, null);
++ }
++
++ @Nullable
++ public Entity teleportTo(ServerLevel worldserver, Vec3 location) {
++ // CraftBukkit end
+ if (this.level() instanceof ServerLevel && !this.isRemoved()) {
+ this.level().getProfiler().push("changeDimension");
+- this.unRide();
++ // CraftBukkit start
++ // this.unRide();
++ if (worldserver == null) {
++ return null;
++ }
++ // CraftBukkit end
+ this.level().getProfiler().push("reposition");
+- PortalInfo portalinfo = this.findDimensionEntryPoint(serverlevel);
++ PortalInfo shapedetectorshape = (location == null) ? this.findDimensionEntryPoint(worldserver) : new PortalInfo(new Vec3(location.x(), location.y(), location.z()), Vec3.ZERO, this.yRot, this.xRot, worldserver, null); // CraftBukkit
+
+ if (portalinfo == null) {
+ return null;
+ } else {
++ // CraftBukkit start
++ worldserver = shapedetectorshape.world;
++ if (worldserver == level) {
++ // SPIGOT-6782: Just move the entity if a plugin changed the world to the one the entity is already in
++ moveTo(shapedetectorshape.pos.x, shapedetectorshape.pos.y, shapedetectorshape.pos.z, shapedetectorshape.yRot, shapedetectorshape.xRot);
++ setDeltaMovement(shapedetectorshape.speed);
++ return this;
++ }
++ this.unRide();
++ // CraftBukkit end
++
+ this.level().getProfiler().popPush("reloading");
+ Entity entity = this.getType().create(serverlevel);
+
+ if (entity != null) {
+ entity.restoreFrom(this);
+- entity.moveTo(portalinfo.pos.x, portalinfo.pos.y, portalinfo.pos.z, portalinfo.yRot, entity.getXRot());
+- entity.setDeltaMovement(portalinfo.speed);
+- serverlevel.addDuringTeleport(entity);
+- if (serverlevel.dimension() == Level.END) {
+- ServerLevel.makeObsidianPlatform(serverlevel);
++ entity.moveTo(shapedetectorshape.pos.x, shapedetectorshape.pos.y, shapedetectorshape.pos.z, shapedetectorshape.yRot, entity.getXRot());
++ entity.setDeltaMovement(shapedetectorshape.speed);
++ // CraftBukkit start - Don't spawn the new entity if the current entity isn't spawned
++ if (this.inWorld) {
++ worldserver.addDuringTeleport(entity);
++ if (worldserver.getTypeKey() == LevelStem.END) { // CraftBukkit
++ ServerLevel.makeObsidianPlatform(worldserver, this); // CraftBukkit
++ }
+ }
++ // CraftBukkit end
++ // CraftBukkit start - Forward the CraftEntity to the new entity
++ this.getBukkitEntity().setHandle(entity);
++ entity.bukkitEntity = this.getBukkitEntity();
++
++ if (this instanceof Mob) {
++ ((Mob) this).dropLeash(true, false); // Unleash to prevent duping of leads.
++ }
++ // CraftBukkit end
+ }
+
+ this.removeAfterChangingDimensions();
+@@ -2638,24 +3070,38 @@
+ }
+
+ @Nullable
+- protected PortalInfo findDimensionEntryPoint(ServerLevel serverlevel) {
+- boolean flag = this.level().dimension() == Level.END && serverlevel.dimension() == Level.OVERWORLD;
+- boolean flag1 = serverlevel.dimension() == Level.END;
++ protected PortalInfo findDimensionEntryPoint(ServerLevel destination) {
++ // CraftBukkit start
++ if (destination == null) {
++ return null;
++ }
++ boolean flag = this.level().getTypeKey() == LevelStem.END && destination.getTypeKey() == LevelStem.OVERWORLD; // fromEndToOverworld
++ boolean flag1 = destination.getTypeKey() == LevelStem.END; // targetIsEnd
++ // CraftBukkit end
+
+ if (!flag && !flag1) {
+- boolean flag2 = serverlevel.dimension() == Level.NETHER;
++ boolean flag2 = destination.getTypeKey() == LevelStem.NETHER; // CraftBukkit
+
+- if (this.level().dimension() != Level.NETHER && !flag2) {
++ if (this.level().getTypeKey() != LevelStem.NETHER && !flag2) { // CraftBukkit
+ return null;
+ } else {
+- WorldBorder worldborder = serverlevel.getWorldBorder();
+- double d0 = DimensionType.getTeleportationScale(this.level().dimensionType(), serverlevel.dimensionType());
+- BlockPos blockpos = worldborder.clampToBounds(this.getX() * d0, this.getY(), this.getZ() * d0);
++ WorldBorder worldborder = destination.getWorldBorder();
++ double d0 = DimensionType.getTeleportationScale(this.level().dimensionType(), destination.dimensionType());
++ BlockPos blockposition = worldborder.clampToBounds(this.getX() * d0, this.getY(), this.getZ() * d0);
++ // CraftBukkit start
++ CraftPortalEvent event = callPortalEvent(this, destination, new Vec3(blockposition.getX(), blockposition.getY(), blockposition.getZ()), PlayerTeleportEvent.TeleportCause.NETHER_PORTAL, flag2 ? 16 : 128, 16);
++ if (event == null) {
++ return null;
++ }
++ final ServerLevel worldserverFinal = destination = ((CraftWorld) event.getTo().getWorld()).getHandle();
++ worldborder = worldserverFinal.getWorldBorder();
++ blockposition = worldborder.clampToBounds(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ());
+
+- return (PortalInfo) this.getExitPortal(serverlevel, blockpos, flag2, worldborder).map((blockutil_foundrectangle) -> {
+- BlockState blockstate = this.level().getBlockState(this.portalEntrancePos);
+- Direction.Axis direction_axis;
+- Vec3 vec3;
++ return (PortalInfo) this.getExitPortal(destination, blockposition, flag2, worldborder, event.getSearchRadius(), event.getCanCreatePortal(), event.getCreationRadius()).map((blockutil_rectangle) -> {
++ // CraftBukkit end
++ IBlockData iblockdata = this.level().getBlockState(this.portalEntrancePos);
++ Direction.Axis enumdirection_enumaxis;
++ Vec3 vec3d;
+
+ if (blockstate.hasProperty(BlockStateProperties.HORIZONTAL_AXIS)) {
+ direction_axis = (Direction.Axis) blockstate.getValue(BlockStateProperties.HORIZONTAL_AXIS);
+@@ -2669,8 +3115,8 @@
+ vec3 = new Vec3(0.5D, 0.0D, 0.0D);
+ }
+
+- return PortalShape.createPortalInfo(serverlevel, blockutil_foundrectangle, direction_axis, vec3, this, this.getDeltaMovement(), this.getYRot(), this.getXRot());
+- }).orElse((Object) null);
++ return PortalShape.createPortalInfo(worldserverFinal, blockutil_rectangle, enumdirection_enumaxis, vec3d, this, this.getDeltaMovement(), this.getYRot(), this.getXRot(), event); // CraftBukkit
++ }).orElse(null); // CraftBukkit - decompile error
+ }
+ } else {
+ BlockPos blockpos1;
+@@ -2680,8 +3126,14 @@
+ } else {
+ blockpos1 = serverlevel.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, serverlevel.getSharedSpawnPos());
+ }
++ // CraftBukkit start
++ CraftPortalEvent event = callPortalEvent(this, destination, new Vec3(blockposition1.getX() + 0.5D, blockposition1.getY(), blockposition1.getZ() + 0.5D), PlayerTeleportEvent.TeleportCause.END_PORTAL, 0, 0);
++ if (event == null) {
++ return null;
++ }
+
+- return new PortalInfo(new Vec3((double) blockpos1.getX() + 0.5D, (double) blockpos1.getY(), (double) blockpos1.getZ() + 0.5D), this.getDeltaMovement(), this.getYRot(), this.getXRot());
++ return new PortalInfo(new Vec3(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ()), this.getDeltaMovement(), this.getYRot(), this.getXRot(), ((CraftWorld) event.getTo().getWorld()).getHandle(), event);
++ // CraftBukkit end
+ }
+ }
+
+@@ -2689,10 +3141,25 @@
+ return PortalShape.getRelativePosition(blockutil_foundrectangle, direction_axis, this.position(), this.getDimensions(this.getPose()));
+ }
+
+- protected Optional<BlockUtil.FoundRectangle> getExitPortal(ServerLevel serverlevel, BlockPos blockpos, boolean flag, WorldBorder worldborder) {
+- return serverlevel.getPortalForcer().findPortalAround(blockpos, flag, worldborder);
++ // CraftBukkit start
++ protected CraftPortalEvent callPortalEvent(Entity entity, ServerLevel exitWorldServer, Vec3 exitPosition, PlayerTeleportEvent.TeleportCause cause, int searchRadius, int creationRadius) {
++ org.bukkit.entity.Entity bukkitEntity = entity.getBukkitEntity();
++ Location enter = bukkitEntity.getLocation();
++ Location exit = CraftLocation.toBukkit(exitPosition, exitWorldServer.getWorld());
++
++ EntityPortalEvent event = new EntityPortalEvent(bukkitEntity, enter, exit, searchRadius);
++ event.getEntity().getServer().getPluginManager().callEvent(event);
++ if (event.isCancelled() || event.getTo() == null || event.getTo().getWorld() == null || !entity.isAlive()) {
++ return null;
++ }
++ return new CraftPortalEvent(event);
+ }
+
++ protected Optional<BlockUtil.FoundRectangle> getExitPortal(ServerLevel worldserver, BlockPos blockposition, boolean flag, WorldBorder worldborder, int searchRadius, boolean canCreatePortal, int createRadius) {
++ return worldserver.getPortalForcer().findPortalAround(blockposition, worldborder, searchRadius);
++ // CraftBukkit end
++ }
++
+ public boolean canChangeDimensions() {
+ return !this.isPassenger() && !this.isVehicle();
+ }
+@@ -2816,7 +3278,13 @@
+ }
+ }
+
+- public boolean teleportTo(ServerLevel serverlevel, double d0, double d1, double d2, Set<RelativeMovement> set, float f, float f1) {
++ // CraftBukkit start
++ public boolean teleportTo(ServerLevel worldserver, double d0, double d1, double d2, Set<RelativeMovement> set, float f, float f1, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) {
++ return this.teleportTo(worldserver, d0, d1, d2, set, f, f1);
++ }
++ // CraftBukkit end
++
++ public boolean teleportTo(ServerLevel level, double x, double d1, double y, Set<RelativeMovement> set, float z, float f1) {
+ float f2 = Mth.clamp(f1, -90.0F, 90.0F);
+
+ if (serverlevel == this.level()) {
+@@ -2835,7 +3303,11 @@
+ entity.moveTo(d0, d1, d2, f, f2);
+ entity.setYHeadRot(f);
+ this.setRemoved(Entity.RemovalReason.CHANGED_DIMENSION);
+- serverlevel.addDuringTeleport(entity);
++ // CraftBukkit start - Don't spawn the new entity if the current entity isn't spawned
++ if (inWorld) {
++ level.addDuringTeleport(entity);
++ }
++ // CraftBukkit end
+ }
+
+ return true;
+@@ -2941,8 +3412,27 @@
+ return this.getBoundingBox();
+ }
+
+- public final void setBoundingBox(AABB aabb) {
+- this.bb = aabb;
++ public final void setBoundingBox(AABB bb) {
++ // CraftBukkit start - block invalid bounding boxes
++ double minX = bb.minX,
++ minY = bb.minY,
++ minZ = bb.minZ,
++ maxX = bb.maxX,
++ maxY = bb.maxY,
++ maxZ = bb.maxZ;
++ double len = bb.maxX - bb.minX;
++ if (len < 0) maxX = minX;
++ if (len > 64) maxX = minX + 64.0;
++
++ len = bb.maxY - bb.minY;
++ if (len < 0) maxY = minY;
++ if (len > 64) maxY = minY + 64.0;
++
++ len = bb.maxZ - bb.minZ;
++ if (len < 0) maxZ = minZ;
++ if (len > 64) maxZ = minZ + 64.0;
++ this.bb = new AABB(minX, minY, minZ, maxX, maxY, maxZ);
++ // CraftBukkit end
+ }
+
+ protected float getEyeHeight(Pose pose, EntityDimensions entitydimensions) {
+@@ -3263,6 +3747,11 @@
+ vec3 = vec3.add(vec31);
+ ++k1;
+ }
++ // CraftBukkit start - store last lava contact location
++ if (fluidTag == FluidTags.LAVA) {
++ this.lastLavaContact = blockposition_mutableblockposition.immutable();
++ }
++ // CraftBukkit end
+ }
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/EntitySelector.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/EntitySelector.java.patch
new file mode 100644
index 0000000000..59a9208de6
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/EntitySelector.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/world/entity/EntitySelector.java
++++ b/net/minecraft/world/entity/EntitySelector.java
+@@ -43,8 +43,8 @@
+ PlayerTeam playerteam = entity.getTeam();
+ Team.CollisionRule team_collisionrule = playerteam == null ? Team.CollisionRule.ALWAYS : playerteam.getCollisionRule();
+
+- return (Predicate) (team_collisionrule == Team.CollisionRule.NEVER ? Predicates.alwaysFalse() : EntitySelector.NO_SPECTATORS.and((entity1) -> {
+- if (!entity1.isPushable()) {
++ return (Predicate) (scoreboardteambase_enumteampush == Team.CollisionRule.NEVER ? Predicates.alwaysFalse() : EntitySelector.NO_SPECTATORS.and((entity1) -> {
++ if (!entity1.canCollideWithBukkit(entity) || !entity.canCollideWithBukkit(entity1)) { // CraftBukkit - collidable API
+ return false;
+ } else if (entity.level().isClientSide && (!(entity1 instanceof Player) || !((Player) entity1).isLocalPlayer())) {
+ return false;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/EntityType.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/EntityType.java.patch
new file mode 100644
index 0000000000..6762ded21c
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/EntityType.java.patch
@@ -0,0 +1,87 @@
+--- a/net/minecraft/world/entity/EntityType.java
++++ b/net/minecraft/world/entity/EntityType.java
+@@ -170,7 +171,7 @@
+ private static final float MAGIC_HORSE_WIDTH = 1.3964844F;
+ private static final int DISPLAY_TRACKING_RANGE = 10;
+ public static final EntityType<Allay> ALLAY = register("allay", EntityType.Builder.of(Allay::new, MobCategory.CREATURE).sized(0.35F, 0.6F).clientTrackingRange(8).updateInterval(2));
+- public static final EntityType<AreaEffectCloud> AREA_EFFECT_CLOUD = register("area_effect_cloud", EntityType.Builder.of(AreaEffectCloud::new, MobCategory.MISC).fireImmune().sized(6.0F, 0.5F).clientTrackingRange(10).updateInterval(Integer.MAX_VALUE));
++ public static final EntityType<AreaEffectCloud> AREA_EFFECT_CLOUD = register("area_effect_cloud", EntityType.Builder.of(AreaEffectCloud::new, MobCategory.MISC).fireImmune().sized(6.0F, 0.5F).clientTrackingRange(10).updateInterval(10)); // CraftBukkit - SPIGOT-3729: track area effect clouds
+ public static final EntityType<ArmorStand> ARMOR_STAND = register("armor_stand", EntityType.Builder.of(ArmorStand::new, MobCategory.MISC).sized(0.5F, 1.975F).clientTrackingRange(10));
+ public static final EntityType<Arrow> ARROW = register("arrow", EntityType.Builder.of(Arrow::new, MobCategory.MISC).sized(0.5F, 0.5F).clientTrackingRange(4).updateInterval(20));
+ public static final EntityType<Axolotl> AXOLOTL = register("axolotl", EntityType.Builder.of(Axolotl::new, MobCategory.AXOLOTLS).sized(0.75F, 0.42F).clientTrackingRange(10));
+@@ -341,10 +342,17 @@
+ }
+
+ @Nullable
+- public T spawn(ServerLevel serverlevel, @Nullable ItemStack itemstack, @Nullable Player player, BlockPos blockpos, MobSpawnType mobspawntype, boolean flag, boolean flag1) {
+- CompoundTag compoundtag;
+- Consumer consumer;
++ public T spawn(ServerLevel serverLevel, @Nullable ItemStack stack, @Nullable Player player, BlockPos pos, EnumMobSpawn spawnType, boolean shouldOffsetY, boolean shouldOffsetYMore) {
++ // CraftBukkit start
++ return this.spawn(serverLevel, stack, player, pos, spawnType, shouldOffsetY, shouldOffsetYMore, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG);
++ }
+
++ @Nullable
++ public T spawn(ServerLevel worldserver, @Nullable ItemStack itemstack, @Nullable Player entityhuman, BlockPos blockposition, EnumMobSpawn enummobspawn, boolean flag, boolean flag1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) {
++ // CraftBukkit end
++ CompoundTag nbttagcompound;
++ Consumer<T> consumer; // CraftBukkit - decompile error
++
+ if (itemstack != null) {
+ compoundtag = itemstack.getTag();
+ consumer = createDefaultStackConfig(serverlevel, itemstack, player);
+@@ -354,7 +362,7 @@
+ compoundtag = null;
+ }
+
+- return this.spawn(serverlevel, compoundtag, consumer, blockpos, mobspawntype, flag, flag1);
++ return this.spawn(worldserver, nbttagcompound, consumer, blockposition, enummobspawn, flag, flag1, spawnReason); // CraftBukkit
+ }
+
+ public static <T extends Entity> Consumer<T> createDefaultStackConfig(ServerLevel serverlevel, ItemStack itemstack, @Nullable Player player) {
+@@ -375,22 +383,38 @@
+ public static <T extends Entity> Consumer<T> appendCustomEntityStackConfig(Consumer<T> consumer, ServerLevel serverlevel, ItemStack itemstack, @Nullable Player player) {
+ CompoundTag compoundtag = itemstack.getTag();
+
+- return compoundtag != null ? consumer.andThen((entity) -> {
+- updateCustomEntityTag(serverlevel, player, entity, compoundtag);
++ return nbttagcompound != null ? consumer.andThen((entity) -> {
++ try { updateCustomEntityTag(level, player, entity, nbttagcompound); } catch (Throwable t) { LOGGER.warn("Error loading spawn egg NBT", t); } // CraftBukkit - SPIGOT-5665
+ }) : consumer;
+ }
+
+ @Nullable
+- public T spawn(ServerLevel serverlevel, BlockPos blockpos, MobSpawnType mobspawntype) {
+- return this.spawn(serverlevel, (CompoundTag) null, (Consumer) null, blockpos, mobspawntype, false, false);
++ public T spawn(ServerLevel level, BlockPos pos, EnumMobSpawn spawnType) {
++ // CraftBukkit start
++ return this.spawn(level, pos, spawnType, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
+ }
+
+ @Nullable
+- public T spawn(ServerLevel serverlevel, @Nullable CompoundTag compoundtag, @Nullable Consumer<T> consumer, BlockPos blockpos, MobSpawnType mobspawntype, boolean flag, boolean flag1) {
+- T t0 = this.create(serverlevel, compoundtag, consumer, blockpos, mobspawntype, flag, flag1);
++ public T spawn(ServerLevel worldserver, BlockPos blockposition, EnumMobSpawn enummobspawn, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) {
++ return this.spawn(worldserver, (CompoundTag) null, null, blockposition, enummobspawn, false, false, spawnReason); // CraftBukkit - decompile error
++ // CraftBukkit end
++ }
+
++ @Nullable
++ public T spawn(ServerLevel level, @Nullable CompoundTag compound, @Nullable Consumer<T> consumer, BlockPos pos, EnumMobSpawn spawnType, boolean shouldOffsetY, boolean shouldOffsetYMore) {
++ // CraftBukkit start
++ return this.spawn(level, compound, consumer, pos, spawnType, shouldOffsetY, shouldOffsetYMore, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
++ }
++
++ @Nullable
++ public T spawn(ServerLevel worldserver, @Nullable CompoundTag nbttagcompound, @Nullable Consumer<T> consumer, BlockPos blockposition, EnumMobSpawn enummobspawn, boolean flag, boolean flag1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) {
++ // CraftBukkit end
++ T t0 = this.create(worldserver, nbttagcompound, consumer, blockposition, enummobspawn, flag, flag1);
++
+ if (t0 != null) {
+- serverlevel.addFreshEntityWithPassengers(t0);
++ worldserver.addFreshEntityWithPassengers(t0, spawnReason);
++ return !t0.isRemoved() ? t0 : null; // Don't return an entity when CreatureSpawnEvent is canceled
++ // CraftBukkit end
+ }
+
+ return t0;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ExperienceOrb.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ExperienceOrb.java.patch
new file mode 100644
index 0000000000..7d59276054
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ExperienceOrb.java.patch
@@ -0,0 +1,121 @@
+--- a/net/minecraft/world/entity/ExperienceOrb.java
++++ b/net/minecraft/world/entity/ExperienceOrb.java
+@@ -20,6 +20,12 @@
+ import net.minecraft.world.level.entity.EntityTypeTest;
+ import net.minecraft.world.phys.AABB;
+ import net.minecraft.world.phys.Vec3;
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityTargetLivingEntityEvent;
++import org.bukkit.event.entity.EntityTargetEvent;
++import org.bukkit.event.player.PlayerExpCooldownChangeEvent;
++// CraftBukkit end
+
+ public class ExperienceOrb extends Entity {
+
+@@ -62,6 +65,7 @@
+ @Override
+ public void tick() {
+ super.tick();
++ Player prevTarget = this.followingPlayer;// CraftBukkit - store old target
+ this.xo = this.getX();
+ this.yo = this.getY();
+ this.zo = this.getZ();
+@@ -87,10 +91,25 @@
+ this.followingPlayer = null;
+ }
+
+- if (this.followingPlayer != null) {
+- Vec3 vec3 = new Vec3(this.followingPlayer.getX() - this.getX(), this.followingPlayer.getY() + (double) this.followingPlayer.getEyeHeight() / 2.0D - this.getY(), this.followingPlayer.getZ() - this.getZ());
+- double d0 = vec3.lengthSqr();
++ // CraftBukkit start
++ boolean cancelled = false;
++ if (this.followingPlayer != prevTarget) {
++ EntityTargetLivingEntityEvent event = CraftEventFactory.callEntityTargetLivingEvent(this, followingPlayer, (followingPlayer != null) ? EntityTargetEvent.TargetReason.CLOSEST_PLAYER : EntityTargetEvent.TargetReason.FORGOT_TARGET);
++ LivingEntity target = (event.getTarget() == null) ? null : ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getTarget()).getHandle();
++ cancelled = event.isCancelled();
+
++ if (cancelled) {
++ followingPlayer = prevTarget;
++ } else {
++ followingPlayer = (target instanceof Player) ? (Player) target : null;
++ }
++ }
++
++ if (this.followingPlayer != null && !cancelled) {
++ // CraftBukkit end
++ Vec3 vec3d = new Vec3(this.followingPlayer.getX() - this.getX(), this.followingPlayer.getY() + (double) this.followingPlayer.getEyeHeight() / 2.0D - this.getY(), this.followingPlayer.getZ() - this.getZ());
++ double d0 = vec3d.lengthSqr();
++
+ if (d0 < 64.0D) {
+ double d1 = 1.0D - Math.sqrt(d0) / 8.0D;
+
+@@ -235,13 +248,13 @@
+ @Override
+ public void playerTouch(Player player) {
+ if (!this.level().isClientSide) {
+- if (player.takeXpDelay == 0) {
+- player.takeXpDelay = 2;
+- player.take(this, 1);
+- int i = this.repairPlayerItems(player, this.value);
++ if (entity.takeXpDelay == 0) {
++ entity.takeXpDelay = CraftEventFactory.callPlayerXpCooldownEvent(entity, 2, PlayerExpCooldownChangeEvent.ChangeReason.PICKUP_ORB).getNewCooldown(); // CraftBukkit - entityhuman.takeXpDelay = 2;
++ entity.take(this, 1);
++ int i = this.repairPlayerItems(entity, this.value);
+
+ if (i > 0) {
+- player.giveExperiencePoints(i);
++ entity.giveExperiencePoints(CraftEventFactory.callPlayerExpChangeEvent(entity, i).getAmount()); // CraftBukkit - this.value -> event.getAmount()
+ }
+
+ --this.count;
+@@ -258,10 +271,18 @@
+
+ if (entry != null) {
+ ItemStack itemstack = (ItemStack) entry.getValue();
+- int j = Math.min(this.xpToDurability(i), itemstack.getDamageValue());
++ int j = Math.min(this.xpToDurability(repairAmount), itemstack.getDamageValue());
++ // CraftBukkit start
++ org.bukkit.event.player.PlayerItemMendEvent event = CraftEventFactory.callPlayerItemMendEvent(player, this, itemstack, entry.getKey(), j);
++ j = event.getRepairAmount();
++ if (event.isCancelled()) {
++ return repairAmount;
++ }
++ // CraftBukkit end
+
+ itemstack.setDamageValue(itemstack.getDamageValue() - j);
+- int k = i - this.durabilityToXp(j);
++ int k = repairAmount - this.durabilityToXp(j);
++ this.value = k; // CraftBukkit - update exp value of orb for PlayerItemMendEvent calls
+
+ return k > 0 ? this.repairPlayerItems(player, k) : 0;
+ } else {
+@@ -285,8 +306,26 @@
+ return this.value >= 2477 ? 10 : (this.value >= 1237 ? 9 : (this.value >= 617 ? 8 : (this.value >= 307 ? 7 : (this.value >= 149 ? 6 : (this.value >= 73 ? 5 : (this.value >= 37 ? 4 : (this.value >= 17 ? 3 : (this.value >= 7 ? 2 : (this.value >= 3 ? 1 : 0)))))))));
+ }
+
+- public static int getExperienceValue(int i) {
+- return i >= 2477 ? 2477 : (i >= 1237 ? 1237 : (i >= 617 ? 617 : (i >= 307 ? 307 : (i >= 149 ? 149 : (i >= 73 ? 73 : (i >= 37 ? 37 : (i >= 17 ? 17 : (i >= 7 ? 7 : (i >= 3 ? 3 : 1)))))))));
++ public static int getExperienceValue(int expValue) {
++ // CraftBukkit start
++ if (expValue > 162670129) return expValue - 100000;
++ if (expValue > 81335063) return 81335063;
++ if (expValue > 40667527) return 40667527;
++ if (expValue > 20333759) return 20333759;
++ if (expValue > 10166857) return 10166857;
++ if (expValue > 5083423) return 5083423;
++ if (expValue > 2541701) return 2541701;
++ if (expValue > 1270849) return 1270849;
++ if (expValue > 635413) return 635413;
++ if (expValue > 317701) return 317701;
++ if (expValue > 158849) return 158849;
++ if (expValue > 79423) return 79423;
++ if (expValue > 39709) return 39709;
++ if (expValue > 19853) return 19853;
++ if (expValue > 9923) return 9923;
++ if (expValue > 4957) return 4957;
++ // CraftBukkit end
++ return expValue >= 2477 ? 2477 : (expValue >= 1237 ? 1237 : (expValue >= 617 ? 617 : (expValue >= 307 ? 307 : (expValue >= 149 ? 149 : (expValue >= 73 ? 73 : (expValue >= 37 ? 37 : (expValue >= 17 ? 17 : (expValue >= 7 ? 7 : (expValue >= 3 ? 3 : 1)))))))));
+ }
+
+ @Override
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/Interaction.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/Interaction.java.patch
new file mode 100644
index 0000000000..e78b361c1c
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/Interaction.java.patch
@@ -0,0 +1,39 @@
+--- a/net/minecraft/world/entity/Interaction.java
++++ b/net/minecraft/world/entity/Interaction.java
+@@ -24,6 +23,13 @@
+ import net.minecraft.world.phys.AABB;
+ import org.slf4j.Logger;
+
++// CraftBukkit start
++import net.minecraft.world.damagesource.DamageSource;
++import net.minecraft.world.entity.player.Player;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityDamageEvent;
++// CraftBukkit end
++
+ public class Interaction extends Entity implements Attackable, Targeting {
+
+ private static final Logger LOGGER = LogUtils.getLogger();
+@@ -151,13 +148,20 @@
+ @Override
+ public boolean skipAttackInteraction(Entity entity) {
+ if (entity instanceof Player) {
+- Player player = (Player) entity;
++ Player entityhuman = (Player) entity;
++ // CraftBukkit start
++ DamageSource source = entityhuman.damageSources().playerAttack(entityhuman);
++ EntityDamageEvent event = CraftEventFactory.callNonLivingEntityDamageEvent(this, source, 1.0F, false);
++ if (event.isCancelled()) {
++ return true;
++ }
++ // CraftBukkit end
+
+ this.attack = new Interaction.PlayerAction(player.getUUID(), this.level().getGameTime());
+ if (player instanceof ServerPlayer) {
+ ServerPlayer serverplayer = (ServerPlayer) player;
+
+- CriteriaTriggers.PLAYER_HURT_ENTITY.trigger(serverplayer, this, player.damageSources().generic(), 1.0F, 1.0F, false);
++ CriteriaTriggers.PLAYER_HURT_ENTITY.trigger(entityplayer, this, source, (float) event.getFinalDamage(), 1.0F, false); // CraftBukkit
+ }
+
+ return !this.getResponse();
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ItemBasedSteering.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ItemBasedSteering.java.patch
new file mode 100644
index 0000000000..60e69f4544
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ItemBasedSteering.java.patch
@@ -0,0 +1,18 @@
+--- a/net/minecraft/world/entity/ItemBasedSteering.java
++++ b/net/minecraft/world/entity/ItemBasedSteering.java
+@@ -53,9 +53,13 @@
+ return (Integer) this.entityData.get(this.boostTimeAccessor);
+ }
+
+- public void addAdditionalSaveData(CompoundTag compoundtag) {
+- compoundtag.putBoolean("Saddle", this.hasSaddle());
++ // CraftBukkit add setBoostTicks(int)
++ public void setBoostTicks(int ticks) {
++ this.boosting = true;
++ this.boostTime = 0;
++ this.entityData.set(this.boostTimeAccessor, ticks);
+ }
++ // CraftBukkit end
+
+ public void readAdditionalSaveData(CompoundTag compoundtag) {
+ this.setSaddle(compoundtag.getBoolean("Saddle"));
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/LightningBolt.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/LightningBolt.java.patch
new file mode 100644
index 0000000000..fc67c814a7
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/LightningBolt.java.patch
@@ -0,0 +1,55 @@
+--- a/net/minecraft/world/entity/LightningBolt.java
++++ b/net/minecraft/world/entity/LightningBolt.java
+@@ -28,6 +28,9 @@
+ import net.minecraft.world.level.gameevent.GameEvent;
+ import net.minecraft.world.phys.AABB;
+ import net.minecraft.world.phys.Vec3;
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++// CraftBukkit end
+
+ public class LightningBolt extends Entity {
+
+@@ -131,7 +132,7 @@
+ }
+ }
+
+- if (this.life >= 0) {
++ if (this.life >= 0 && !this.visualOnly) { // CraftBukkit - add !this.visualOnly
+ if (!(this.level() instanceof ServerLevel)) {
+ this.level().setSkyFlashTime(2);
+ } else if (!this.visualOnly) {
+@@ -164,18 +165,26 @@
+ BlockPos blockpos = this.blockPosition();
+ BlockState blockstate = BaseFireBlock.getState(this.level(), blockpos);
+
+- if (this.level().getBlockState(blockpos).isAir() && blockstate.canSurvive(this.level(), blockpos)) {
+- this.level().setBlockAndUpdate(blockpos, blockstate);
+- ++this.blocksSetOnFire;
++ if (this.level().getBlockState(blockposition).isAir() && iblockdata.canSurvive(this.level(), blockposition)) {
++ // CraftBukkit start - add "!visualOnly"
++ if (!visualOnly && !CraftEventFactory.callBlockIgniteEvent(this.level(), blockposition, this).isCancelled()) {
++ this.level().setBlockAndUpdate(blockposition, iblockdata);
++ ++this.blocksSetOnFire;
++ }
++ // CraftBukkit end
+ }
+
+ for (int j = 0; j < i; ++j) {
+ BlockPos blockpos1 = blockpos.offset(this.random.nextInt(3) - 1, this.random.nextInt(3) - 1, this.random.nextInt(3) - 1);
+
+- blockstate = BaseFireBlock.getState(this.level(), blockpos1);
+- if (this.level().getBlockState(blockpos1).isAir() && blockstate.canSurvive(this.level(), blockpos1)) {
+- this.level().setBlockAndUpdate(blockpos1, blockstate);
+- ++this.blocksSetOnFire;
++ iblockdata = BaseFireBlock.getState(this.level(), blockposition1);
++ if (this.level().getBlockState(blockposition1).isAir() && iblockdata.canSurvive(this.level(), blockposition1)) {
++ // CraftBukkit start - add "!visualOnly"
++ if (!visualOnly && !CraftEventFactory.callBlockIgniteEvent(this.level(), blockposition1, this).isCancelled()) {
++ this.level().setBlockAndUpdate(blockposition1, iblockdata);
++ ++this.blocksSetOnFire;
++ }
++ // CraftBukkit end
+ }
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/LivingEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/LivingEntity.java.patch
new file mode 100644
index 0000000000..55799925f7
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/LivingEntity.java.patch
@@ -0,0 +1,921 @@
+--- a/net/minecraft/world/entity/LivingEntity.java
++++ b/net/minecraft/world/entity/LivingEntity.java
+@@ -119,6 +120,26 @@
+ import net.minecraft.world.scores.PlayerTeam;
+ import org.slf4j.Logger;
+
++// CraftBukkit start
++import java.util.ArrayList;
++import java.util.HashSet;
++import java.util.Set;
++import com.google.common.base.Function;
++import org.bukkit.Location;
++import org.bukkit.craftbukkit.attribute.CraftAttributeMap;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.entity.Player;
++import org.bukkit.event.entity.ArrowBodyCountChangeEvent;
++import org.bukkit.event.entity.EntityDamageEvent;
++import org.bukkit.event.entity.EntityDamageEvent.DamageModifier;
++import org.bukkit.event.entity.EntityPotionEffectEvent;
++import org.bukkit.event.entity.EntityRegainHealthEvent;
++import org.bukkit.event.entity.EntityResurrectEvent;
++import org.bukkit.event.entity.EntityTeleportEvent;
++import org.bukkit.event.player.PlayerItemConsumeEvent;
++// CraftBukkit end
++
+ public abstract class LivingEntity extends Entity implements Attackable {
+
+ private static final Logger LOGGER = LogUtils.getLogger();
+@@ -226,9 +247,23 @@
+ private float swimAmountO;
+ protected Brain<?> brain;
+ private boolean skipDropExperience;
++ // CraftBukkit start
++ public int expToDrop;
++ public boolean forceDrops;
++ public ArrayList<org.bukkit.inventory.ItemStack> drops = new ArrayList<org.bukkit.inventory.ItemStack>();
++ public final org.bukkit.craftbukkit.attribute.CraftAttributeMap craftAttributes;
++ public boolean collides = true;
++ public Set<UUID> collidableExemptions = new HashSet<>();
++ public boolean bukkitPickUpLoot;
+
+- protected LivingEntity(EntityType<? extends LivingEntity> entitytype, Level level) {
+- super(entitytype, level);
++ @Override
++ public float getBukkitYaw() {
++ return getYHeadRot();
++ }
++ // CraftBukkit end
++
++ protected LivingEntity(EntityType<? extends LivingEntity> entityType, Level level) {
++ super(entityType, level);
+ this.lastHandItemStacks = NonNullList.withSize(2, ItemStack.EMPTY);
+ this.lastArmorItemStacks = NonNullList.withSize(4, ItemStack.EMPTY);
+ this.discardFriction = false;
+@@ -237,8 +272,10 @@
+ this.effectsDirty = true;
+ this.useItem = ItemStack.EMPTY;
+ this.lastClimbablePos = Optional.empty();
+- this.attributes = new AttributeMap(DefaultAttributes.getSupplier(entitytype));
+- this.setHealth(this.getMaxHealth());
++ this.attributes = new AttributeMap(DefaultAttributes.getSupplier(entityType));
++ this.craftAttributes = new CraftAttributeMap(attributes); // CraftBukkit
++ // CraftBukkit - setHealth(getMaxHealth()) inlined and simplified to skip the instanceof check for EntityPlayer, as getBukkitEntity() is not initialized in constructor
++ this.entityData.set(LivingEntity.DATA_HEALTH_ID, (float) this.getAttribute(Attributes.MAX_HEALTH).getValue());
+ this.blocksBuilding = true;
+ this.rotA = (float) ((Math.random() + 1.0D) * 0.009999999776482582D);
+ this.reapplyPosition();
+@@ -320,7 +354,13 @@
+ double d7 = Math.min((double) (0.2F + f / 15.0F), 2.5D);
+ int i = (int) (150.0D * d7);
+
+- ((ServerLevel) this.level()).sendParticles(new BlockParticleOption(ParticleTypes.BLOCK, blockstate), d1, d2, d3, i, 0.0D, 0.0D, 0.0D, 0.15000000596046448D);
++ // CraftBukkit start - visiblity api
++ if (this instanceof ServerPlayer) {
++ ((ServerLevel) this.level()).sendParticles((ServerPlayer) this, new BlockParticleOption(ParticleTypes.BLOCK, onGround), this.getX(), this.getY(), this.getZ(), i, 0.0D, 0.0D, 0.0D, 0.15000000596046448D, false);
++ } else {
++ ((ServerLevel) this.level()).sendParticles(new BlockParticleOption(ParticleTypes.BLOCK, onGround), d1, d2, d3, i, 0.0D, 0.0D, 0.0D, 0.15000000596046448D);
++ }
++ // CraftBukkit end
+ }
+
+ super.checkFallDamage(d0, flag, blockstate, blockpos);
+@@ -677,15 +714,21 @@
+ return true;
+ }
+
+- public void onEquipItem(EquipmentSlot equipmentslot, ItemStack itemstack, ItemStack itemstack1) {
++ public void onEquipItem(EquipmentSlot slot, ItemStack oldItem, ItemStack newItem) {
++ // CraftBukkit start
++ onEquipItem(slot, oldItem, newItem, false);
++ }
++
++ public void onEquipItem(EquipmentSlot enumitemslot, ItemStack itemstack, ItemStack itemstack1, boolean silent) {
++ // CraftBukkit end
+ boolean flag = itemstack1.isEmpty() && itemstack.isEmpty();
+
+ if (!flag && !ItemStack.isSameItemSameTags(itemstack, itemstack1) && !this.firstTick) {
+ Equipable equipable = Equipable.get(itemstack1);
+
+ if (!this.level().isClientSide() && !this.isSpectator()) {
+- if (!this.isSilent() && equipable != null && equipable.getEquipmentSlot() == equipmentslot) {
+- this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), equipable.getEquipSound(), this.getSoundSource(), 1.0F, 1.0F);
++ if (!this.isSilent() && equipable != null && equipable.getEquipmentSlot() == enumitemslot && !silent) { // CraftBukkit
++ this.level().playSound((net.minecraft.world.entity.player.Player) null, this.getX(), this.getY(), this.getZ(), equipable.getEquipSound(), this.getSoundSource(), 1.0F, 1.0F);
+ }
+
+ if (this.doesEmitEquipEvent(equipmentslot)) {
+@@ -761,9 +801,16 @@
+ }
+ }
+
+- if (compoundtag.contains("Health", 99)) {
+- this.setHealth(compoundtag.getFloat("Health"));
++ // CraftBukkit start
++ if (compound.contains("Bukkit.MaxHealth")) {
++ Tag nbtbase = compound.get("Bukkit.MaxHealth");
++ if (nbtbase.getId() == 5) {
++ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(((FloatTag) nbtbase).getAsDouble());
++ } else if (nbtbase.getId() == 3) {
++ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(((IntTag) nbtbase).getAsDouble());
++ }
+ }
++ // CraftBukkit end
+
+ this.hurtTime = compoundtag.getShort("HurtTime");
+ this.deathTime = compoundtag.getShort("DeathTime");
+@@ -798,9 +849,32 @@
+
+ }
+
++ // CraftBukkit start
++ private boolean isTickingEffects = false;
++ private List<ProcessableEffect> effectsToProcess = Lists.newArrayList();
++
++ private static class ProcessableEffect {
++
++ private MobEffect type;
++ private MobEffectInstance effect;
++ private final EntityPotionEffectEvent.Cause cause;
++
++ private ProcessableEffect(MobEffectInstance effect, EntityPotionEffectEvent.Cause cause) {
++ this.effect = effect;
++ this.cause = cause;
++ }
++
++ private ProcessableEffect(MobEffect type, EntityPotionEffectEvent.Cause cause) {
++ this.type = type;
++ this.cause = cause;
++ }
++ }
++ // CraftBukkit end
++
+ protected void tickEffects() {
+ Iterator iterator = this.activeEffects.keySet().iterator();
+
++ isTickingEffects = true; // CraftBukkit
+ try {
+ while (iterator.hasNext()) {
+ MobEffect mobeffect = (MobEffect) iterator.next();
+@@ -810,6 +884,12 @@
+ this.onEffectUpdated(mobeffectinstance, true, (Entity) null);
+ })) {
+ if (!this.level().isClientSide) {
++ // CraftBukkit start
++ EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, mobeffect, null, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.EXPIRATION);
++ if (event.isCancelled()) {
++ continue;
++ }
++ // CraftBukkit end
+ iterator.remove();
+ this.onEffectRemoved(mobeffectinstance);
+ }
+@@ -820,6 +900,17 @@
+ } catch (ConcurrentModificationException concurrentmodificationexception) {
+ ;
+ }
++ // CraftBukkit start
++ isTickingEffects = false;
++ for (ProcessableEffect e : effectsToProcess) {
++ if (e.effect != null) {
++ addEffect(e.effect, e.cause);
++ } else {
++ removeEffect(e.type, e.cause);
++ }
++ }
++ effectsToProcess.clear();
++ // CraftBukkit end
+
+ if (this.effectsDirty) {
+ if (!this.level().isClientSide) {
+@@ -946,7 +1037,13 @@
+ this.entityData.set(LivingEntity.DATA_EFFECT_COLOR_ID, 0);
+ }
+
++ // CraftBukkit start
+ public boolean removeAllEffects() {
++ return removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN);
++ }
++
++ public boolean removeAllEffects(EntityPotionEffectEvent.Cause cause) {
++ // CraftBukkit end
+ if (this.level().isClientSide) {
+ return false;
+ } else {
+@@ -955,7 +1052,14 @@
+ boolean flag;
+
+ for (flag = false; iterator.hasNext(); flag = true) {
+- this.onEffectRemoved((MobEffectInstance) iterator.next());
++ // CraftBukkit start
++ MobEffectInstance effect = (MobEffectInstance) iterator.next();
++ EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, effect, null, cause, EntityPotionEffectEvent.Action.CLEARED);
++ if (event.isCancelled()) {
++ continue;
++ }
++ this.onEffectRemoved(effect);
++ // CraftBukkit end
+ iterator.remove();
+ }
+
+@@ -984,19 +1088,49 @@
+ return this.addEffect(mobeffectinstance, (Entity) null);
+ }
+
+- public boolean addEffect(MobEffectInstance mobeffectinstance, @Nullable Entity entity) {
+- if (!this.canBeAffected(mobeffectinstance)) {
++ // CraftBukkit start
++ public boolean addEffect(MobEffectInstance mobeffect, EntityPotionEffectEvent.Cause cause) {
++ return this.addEffect(mobeffect, (Entity) null, cause);
++ }
++
++ public boolean addEffect(MobEffectInstance effectInstance, @Nullable Entity entity) {
++ return this.addEffect(effectInstance, entity, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN);
++ }
++
++ public boolean addEffect(MobEffectInstance mobeffect, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause) {
++ if (isTickingEffects) {
++ effectsToProcess.add(new ProcessableEffect(mobeffect, cause));
++ return true;
++ }
++ // CraftBukkit end
++
++ if (!this.canBeAffected(mobeffect)) {
+ return false;
+ } else {
+ MobEffectInstance mobeffectinstance1 = (MobEffectInstance) this.activeEffects.get(mobeffectinstance.getEffect());
+ boolean flag = false;
+
+- if (mobeffectinstance1 == null) {
+- this.activeEffects.put(mobeffectinstance.getEffect(), mobeffectinstance);
+- this.onEffectAdded(mobeffectinstance, entity);
++ // CraftBukkit start
++ boolean override = false;
++ if (mobeffect1 != null) {
++ override = new MobEffectInstance(mobeffect1).update(mobeffect);
++ }
++
++ EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, mobeffect1, mobeffect, cause, override);
++ if (event.isCancelled()) {
++ return false;
++ }
++ // CraftBukkit end
++
++ if (mobeffect1 == null) {
++ this.activeEffects.put(mobeffect.getEffect(), mobeffect);
++ this.onEffectAdded(mobeffect, entity);
+ flag = true;
+- } else if (mobeffectinstance1.update(mobeffectinstance)) {
+- this.onEffectUpdated(mobeffectinstance1, true, entity);
++ // CraftBukkit start
++ } else if (event.isOverride()) {
++ mobeffect1.update(mobeffect);
++ this.onEffectUpdated(mobeffect1, true, entity);
++ // CraftBukkit end
+ flag = true;
+ }
+
+@@ -1034,6 +1168,7 @@
+ return this.getMobType() == MobType.UNDEAD;
+ }
+
++ // CraftBukkit start
+ @Nullable
+ public MobEffectInstance removeEffectNoUpdate(@Nullable MobEffect mobeffect) {
+ return (MobEffectInstance) this.activeEffects.remove(mobeffect);
+@@ -1042,8 +1181,29 @@
+ public boolean removeEffect(MobEffect mobeffect) {
+ MobEffectInstance mobeffectinstance = this.removeEffectNoUpdate(mobeffect);
+
+- if (mobeffectinstance != null) {
+- this.onEffectRemoved(mobeffectinstance);
++ MobEffectInstance effect = this.activeEffects.get(mobeffectlist);
++ if (effect == null) {
++ return null;
++ }
++
++ EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, effect, null, cause);
++ if (event.isCancelled()) {
++ return null;
++ }
++
++ return (MobEffectInstance) this.activeEffects.remove(mobeffectlist);
++ }
++
++ public boolean removeEffect(MobEffect effect) {
++ return removeEffect(effect, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.UNKNOWN);
++ }
++
++ public boolean removeEffect(MobEffect mobeffectlist, EntityPotionEffectEvent.Cause cause) {
++ MobEffectInstance mobeffect = this.c(mobeffectlist, cause);
++ // CraftBukkit end
++
++ if (mobeffect != null) {
++ this.onEffectRemoved(mobeffect);
+ return true;
+ } else {
+ return false;
+@@ -1138,21 +1298,56 @@
+
+ }
+
+- public void heal(float f) {
++ // CraftBukkit start - Delegate so we can handle providing a reason for health being regained
++ public void heal(float healAmount) {
++ heal(healAmount, EntityRegainHealthEvent.RegainReason.CUSTOM);
++ }
++
++ public void heal(float f, EntityRegainHealthEvent.RegainReason regainReason) {
+ float f1 = this.getHealth();
+
+ if (f1 > 0.0F) {
+- this.setHealth(f1 + f);
++ EntityRegainHealthEvent event = new EntityRegainHealthEvent(this.getBukkitEntity(), f, regainReason);
++ // Suppress during worldgen
++ if (this.valid) {
++ this.level().getCraftServer().getPluginManager().callEvent(event);
++ }
++
++ if (!event.isCancelled()) {
++ this.setHealth((float) (this.getHealth() + event.getAmount()));
++ }
++ // CraftBukkit end
+ }
+
+ }
+
+ public float getHealth() {
++ // CraftBukkit start - Use unscaled health
++ if (this instanceof ServerPlayer) {
++ return (float) ((ServerPlayer) this).getBukkitEntity().getHealth();
++ }
++ // CraftBukkit end
+ return (Float) this.entityData.get(LivingEntity.DATA_HEALTH_ID);
+ }
+
+- public void setHealth(float f) {
+- this.entityData.set(LivingEntity.DATA_HEALTH_ID, Mth.clamp(f, 0.0F, this.getMaxHealth()));
++ public void setHealth(float health) {
++ // CraftBukkit start - Handle scaled health
++ if (this instanceof ServerPlayer) {
++ org.bukkit.craftbukkit.entity.CraftPlayer player = ((ServerPlayer) this).getBukkitEntity();
++ // Squeeze
++ if (health < 0.0F) {
++ player.setRealHealth(0.0D);
++ } else if (health > player.getMaxHealth()) {
++ player.setRealHealth(player.getMaxHealth());
++ } else {
++ player.setRealHealth(health);
++ }
++
++ player.updateScaledHealth(false);
++ return;
++ }
++ // CraftBukkit end
++ this.entityData.set(LivingEntity.DATA_HEALTH_ID, Mth.clamp(health, 0.0F, this.getMaxHealth()));
+ }
+
+ public boolean isDeadOrDying() {
+@@ -1166,7 +1360,7 @@
+ return false;
+ } else if (this.level().isClientSide) {
+ return false;
+- } else if (this.isDeadOrDying()) {
++ } else if (this.isRemoved() || this.dead || this.getHealth() <= 0.0F) { // CraftBukkit - Don't allow entities that got set to dead/killed elsewhere to get damaged and die
+ return false;
+ } else if (damagesource.is(DamageTypeTags.IS_FIRE) && this.hasEffect(MobEffects.FIRE_RESISTANCE)) {
+ return false;
+@@ -1180,12 +1374,13 @@
+ boolean flag = false;
+ float f2 = 0.0F;
+
+- if (f > 0.0F && this.isDamageSourceBlocked(damagesource)) {
+- this.hurtCurrentlyUsedShield(f);
+- f2 = f;
+- f = 0.0F;
+- if (!damagesource.is(DamageTypeTags.IS_PROJECTILE)) {
+- Entity entity = damagesource.getDirectEntity();
++ // CraftBukkit - Moved into damageEntity0(DamageSource, float)
++ if (false && amount > 0.0F && this.isDamageSourceBlocked(source)) {
++ this.hurtCurrentlyUsedShield(amount);
++ f2 = amount;
++ amount = 0.0F;
++ if (!source.is(DamageTypeTags.IS_PROJECTILE)) {
++ Entity entity = source.getDirectEntity();
+
+ if (entity instanceof LivingEntity) {
+ LivingEntity livingentity = (LivingEntity) entity;
+@@ -1204,25 +1399,35 @@
+ this.walkAnimation.setSpeed(1.5F);
+ boolean flag1 = true;
+
+- if ((float) this.invulnerableTime > 10.0F && !damagesource.is(DamageTypeTags.BYPASSES_COOLDOWN)) {
+- if (f <= this.lastHurt) {
++ if ((float) this.invulnerableTime > (float) this.invulnerableDuration / 2.0F && !source.is(DamageTypeTags.BYPASSES_COOLDOWN)) { // CraftBukkit - restore use of maxNoDamageTicks
++ if (amount <= this.lastHurt) {
+ return false;
+ }
+
+- this.actuallyHurt(damagesource, f - this.lastHurt);
+- this.lastHurt = f;
++ // CraftBukkit start
++ if (!this.damageEntity0(source, amount - this.lastHurt)) {
++ return false;
++ }
++ // CraftBukkit end
++ this.lastHurt = amount;
+ flag1 = false;
+ } else {
+- this.lastHurt = f;
+- this.invulnerableTime = 20;
+- this.actuallyHurt(damagesource, f);
++ // CraftBukkit start
++ if (!this.damageEntity0(source, amount)) {
++ return false;
++ }
++ this.lastHurt = amount;
++ this.invulnerableTime = this.invulnerableDuration; // CraftBukkit - restore use of maxNoDamageTicks
++ // this.damageEntity0(damagesource, f);
++ // CraftBukkit end
+ this.hurtDuration = 10;
+ this.hurtTime = this.hurtDuration;
+ }
+
+- if (damagesource.is(DamageTypeTags.DAMAGES_HELMET) && !this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
+- this.hurtHelmet(damagesource, f);
+- f *= 0.75F;
++ // CraftBukkit - Moved into damageEntity0(DamageSource, float)
++ if (false && source.is(DamageTypeTags.DAMAGES_HELMET) && !this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
++ this.hurtHelmet(source, amount);
++ amount *= 0.75F;
+ }
+
+ Entity entity1 = damagesource.getEntity();
+@@ -1338,13 +1543,17 @@
+ InteractionHand[] ainteractionhand = InteractionHand.values();
+ int i = ainteractionhand.length;
+
++ // CraftBukkit start
++ EnumHand hand = null;
++ ItemStack itemstack1 = ItemStack.EMPTY;
+ for (int j = 0; j < i; ++j) {
+ InteractionHand interactionhand = ainteractionhand[j];
+ ItemStack itemstack1 = this.getItemInHand(interactionhand);
+
+ if (itemstack1.is(Items.TOTEM_OF_UNDYING)) {
++ hand = enumhand; // CraftBukkit
+ itemstack = itemstack1.copy();
+- itemstack1.shrink(1);
++ // itemstack1.subtract(1); // CraftBukkit
+ break;
+ }
+ }
+@@ -1353,16 +1563,26 @@
+ if (this instanceof ServerPlayer) {
+ ServerPlayer serverplayer = (ServerPlayer) this;
+
+- serverplayer.awardStat(Stats.ITEM_USED.get(Items.TOTEM_OF_UNDYING));
+- CriteriaTriggers.USED_TOTEM.trigger(serverplayer, itemstack);
++ if (!event.isCancelled()) {
++ if (!itemstack1.isEmpty()) {
++ itemstack1.shrink(1);
++ }
++ if (itemstack != null && this instanceof ServerPlayer) {
++ // CraftBukkit end
++ ServerPlayer entityplayer = (ServerPlayer) this;
++
++ entityplayer.awardStat(Stats.ITEM_USED.get(Items.TOTEM_OF_UNDYING));
++ CriteriaTriggers.USED_TOTEM.trigger(entityplayer, itemstack);
+ this.gameEvent(GameEvent.ITEM_INTERACT_FINISH);
+ }
+
+ this.setHealth(1.0F);
+- this.removeAllEffects();
+- this.addEffect(new MobEffectInstance(MobEffects.REGENERATION, 900, 1));
+- this.addEffect(new MobEffectInstance(MobEffects.ABSORPTION, 100, 1));
+- this.addEffect(new MobEffectInstance(MobEffects.FIRE_RESISTANCE, 800, 0));
++ // CraftBukkit start
++ this.removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.TOTEM);
++ this.addEffect(new MobEffectInstance(MobEffects.REGENERATION, 900, 1), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.TOTEM);
++ this.addEffect(new MobEffectInstance(MobEffects.ABSORPTION, 100, 1), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.TOTEM);
++ this.addEffect(new MobEffectInstance(MobEffects.FIRE_RESISTANCE, 800, 0), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.TOTEM);
++ // CraftBukkit end
+ this.level().broadcastEntityEvent(this, (byte) 35);
+ }
+
+@@ -1472,16 +1692,24 @@
+ BlockPos blockpos = this.blockPosition();
+ BlockState blockstate = Blocks.WITHER_ROSE.defaultBlockState();
+
+- if (this.level().getBlockState(blockpos).isAir() && blockstate.canSurvive(this.level(), blockpos)) {
+- this.level().setBlock(blockpos, blockstate, 3);
+- flag = true;
++ if (this.level().getBlockState(blockposition).isAir() && iblockdata.canSurvive(this.level(), blockposition)) {
++ // CraftBukkit start - call EntityBlockFormEvent for Wither Rose
++ flag = org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this.level(), blockposition, iblockdata, 3, this);
++ // CraftBukkit end
+ }
+ }
+
+ if (!flag) {
+ ItemEntity itementity = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), new ItemStack(Items.WITHER_ROSE));
+
+- this.level().addFreshEntity(itementity);
++ // CraftBukkit start
++ org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity());
++ CraftEventFactory.callEvent(event);
++ if (event.isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
++ this.level().addFreshEntity(entityitem);
+ }
+ }
+
+@@ -1500,22 +1728,38 @@
+
+ boolean flag = this.lastHurtByPlayerTime > 0;
+
++ this.dropEquipment(); // CraftBukkit - from below
+ if (this.shouldDropLoot() && this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
+ this.dropFromLootTable(damagesource, flag);
+ this.dropCustomDeathLoot(damagesource, i, flag);
+ }
++ // CraftBukkit start - Call death event
++ CraftEventFactory.callEntityDeathEvent(this, this.drops);
++ this.drops = new ArrayList<>();
++ // CraftBukkit end
+
+- this.dropEquipment();
++ // this.dropInventory();// CraftBukkit - moved up
+ this.dropExperience();
+ }
+
+ protected void dropEquipment() {}
+
+- protected void dropExperience() {
++ // CraftBukkit start
++ public int getExpReward() {
+ if (this.level() instanceof ServerLevel && !this.wasExperienceConsumed() && (this.isAlwaysExperienceDropper() || this.lastHurtByPlayerTime > 0 && this.shouldDropExperience() && this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT))) {
+ ExperienceOrb.award((ServerLevel) this.level(), this.position(), this.getExperienceReward());
+ }
++ }
++ // CraftBukkit end
+
++ protected void dropExperience() {
++ // CraftBukkit start - Update getExpReward() above if the removed if() changes!
++ if (true && !(this instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon)) { // CraftBukkit - SPIGOT-2420: Special case ender dragon will drop the xp over time
++ ExperienceOrb.award((ServerLevel) this.level(), this.position(), this.expToDrop);
++ this.expToDrop = 0;
++ }
++ // CraftBukkit end
++
+ }
+
+ protected void dropCustomDeathLoot(DamageSource damagesource, int i, boolean flag) {}
+@@ -1606,6 +1853,28 @@
+ return itemstack.getEatingSound();
+ }
+
++ // CraftBukkit start - Add delegate methods
++ public SoundEvent getHurtSound0(DamageSource damagesource) {
++ return getHurtSound(damagesource);
++ }
++
++ public SoundEvent getDeathSound0() {
++ return getDeathSound();
++ }
++
++ public SoundEvent getFallDamageSound0(int fallHeight) {
++ return getFallDamageSound(fallHeight);
++ }
++
++ public SoundEvent getDrinkingSound0(ItemStack itemstack) {
++ return getDrinkingSound(itemstack);
++ }
++
++ public SoundEvent getEatingSound0(ItemStack itemstack) {
++ return getEatingSound(itemstack);
++ }
++ // CraftBukkit end
++
+ public Optional<BlockPos> getLastClimbablePos() {
+ return this.lastClimbablePos;
+ }
+@@ -1654,9 +1921,14 @@
+ int i = this.calculateFallDamage(f, f1);
+
+ if (i > 0) {
++ // CraftBukkit start
++ if (!this.hurt(source, (float) i)) {
++ return true;
++ }
++ // CraftBukkit end
+ this.playSound(this.getFallDamageSound(i), 1.0F, 1.0F);
+ this.playBlockFallSound();
+- this.hurt(damagesource, (float) i);
++ // this.damageEntity(damagesource, (float) i); // CraftBukkit - moved up
+ return true;
+ } else {
+ return flag;
+@@ -1707,10 +1978,10 @@
+
+ protected void hurtCurrentlyUsedShield(float f) {}
+
+- protected float getDamageAfterArmorAbsorb(DamageSource damagesource, float f) {
+- if (!damagesource.is(DamageTypeTags.BYPASSES_ARMOR)) {
+- this.hurtArmor(damagesource, f);
+- f = CombatRules.getDamageAfterAbsorb(f, (float) this.getArmorValue(), (float) this.getAttributeValue(Attributes.ARMOR_TOUGHNESS));
++ protected float getDamageAfterArmorAbsorb(DamageSource damageSource, float damageAmount) {
++ if (!damageSource.is(DamageTypeTags.BYPASSES_ARMOR)) {
++ // this.hurtArmor(damagesource, f); // CraftBukkit - Moved into damageEntity0(DamageSource, float)
++ damageAmount = CombatRules.getDamageAfterAbsorb(damageAmount, (float) this.getArmorValue(), (float) this.getAttributeValue(Attributes.ARMOR_TOUGHNESS));
+ }
+
+ return f;
+@@ -1722,7 +1993,8 @@
+ } else {
+ int i;
+
+- if (this.hasEffect(MobEffects.DAMAGE_RESISTANCE) && !damagesource.is(DamageTypeTags.BYPASSES_RESISTANCE)) {
++ // CraftBukkit - Moved to damageEntity0(DamageSource, float)
++ if (false && this.hasEffect(MobEffects.DAMAGE_RESISTANCE) && !damageSource.is(DamageTypeTags.BYPASSES_RESISTANCE)) {
+ i = (this.getEffect(MobEffects.DAMAGE_RESISTANCE).getAmplifier() + 1) * 5;
+ int j = 25 - i;
+ float f1 = f * (float) j;
+@@ -1755,11 +2027,16 @@
+ }
+ }
+
+- protected void actuallyHurt(DamageSource damagesource, float f) {
+- if (!this.isInvulnerableTo(damagesource)) {
+- f = this.getDamageAfterArmorAbsorb(damagesource, f);
+- f = this.getDamageAfterMagicAbsorb(damagesource, f);
+- float f1 = f;
++ // CraftBukkit start
++ protected boolean damageEntity0(final DamageSource damagesource, float f) { // void -> boolean, add final
++ if (!this.isInvulnerableTo(damagesource)) {
++ final boolean human = this instanceof net.minecraft.world.entity.player.Player;
++ float originalDamage = f;
++ Function<Double, Double> hardHat = new Function<Double, Double>() {
++ @Override
++ public Double apply(Double f) {
++ if (damagesource.is(DamageTypeTags.DAMAGES_HELMET) && !LivingEntity.this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
++ return -(f - (f * 0.75F));
+
+ f = Math.max(f - this.getAbsorptionAmount(), 0.0F);
+ this.setAbsorptionAmount(this.getAbsorptionAmount() - (f1 - f));
+@@ -1775,13 +2156,47 @@
+ }
+ }
+
+- if (f != 0.0F) {
++ if (f > 0 || !human) {
++ if (human) {
++ // PAIL: Be sure to drag all this code from the EntityHuman subclass each update.
++ ((net.minecraft.world.entity.player.Player) this).causeFoodExhaustion(damagesource.getFoodExhaustion(), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.DAMAGED); // CraftBukkit - EntityExhaustionEvent
++ if (f < 3.4028235E37F) {
++ ((net.minecraft.world.entity.player.Player) this).awardStat(Stats.DAMAGE_TAKEN, Math.round(f * 10.0F));
++ }
++ }
++ // CraftBukkit end
+ this.getCombatTracker().recordDamage(damagesource, f);
+ this.setHealth(this.getHealth() - f);
+- this.setAbsorptionAmount(this.getAbsorptionAmount() - f);
++ // CraftBukkit start
++ if (!human) {
++ this.setAbsorptionAmount(this.getAbsorptionAmount() - f);
++ }
+ this.gameEvent(GameEvent.ENTITY_DAMAGE);
++
++ return true;
++ } else {
++ // Duplicate triggers if blocking
++ if (event.getDamage(DamageModifier.BLOCKING) < 0) {
++ if (this instanceof ServerPlayer) {
++ CriteriaTriggers.ENTITY_HURT_PLAYER.trigger((ServerPlayer) this, damagesource, f, originalDamage, true);
++ f2 = (float) -event.getDamage(DamageModifier.BLOCKING);
++ if (f2 > 0.0F && f2 < 3.4028235E37F) {
++ ((ServerPlayer) this).awardStat(Stats.DAMAGE_BLOCKED_BY_SHIELD, Math.round(originalDamage * 10.0F));
++ }
++ }
++
++ if (damagesource.getEntity() instanceof ServerPlayer) {
++ CriteriaTriggers.PLAYER_HURT_ENTITY.trigger((ServerPlayer) damagesource.getEntity(), this, damagesource, f, originalDamage, true);
++ }
++
++ return false;
++ } else {
++ return originalDamage > 0;
++ }
++ // CraftBukkit end
+ }
+ }
++ return false; // CraftBukkit
+ }
+
+ public CombatTracker getCombatTracker() {
+@@ -1805,10 +2220,20 @@
+ return (Integer) this.entityData.get(LivingEntity.DATA_ARROW_COUNT_ID);
+ }
+
+- public final void setArrowCount(int i) {
+- this.entityData.set(LivingEntity.DATA_ARROW_COUNT_ID, i);
++ public final void setArrowCount(int count) {
++ // CraftBukkit start
++ setArrowCount(count, false);
+ }
+
++ public final void setArrowCount(int i, boolean flag) {
++ ArrowBodyCountChangeEvent event = CraftEventFactory.callArrowBodyCountChangeEvent(this, getArrowCount(), i, flag);
++ if (event.isCancelled()) {
++ return;
++ }
++ this.entityData.set(LivingEntity.DATA_ARROW_COUNT_ID, event.getNewAmount());
++ }
++ // CraftBukkit end
++
+ public final int getStingerCount() {
+ return (Integer) this.entityData.get(LivingEntity.DATA_STINGER_COUNT_ID);
+ }
+@@ -2053,6 +2474,12 @@
+
+ public abstract ItemStack getItemBySlot(EquipmentSlot slot);
+
++ // CraftBukkit start
++ public void setItemSlot(EquipmentSlot enumitemslot, ItemStack itemstack, boolean silent) {
++ this.setItemSlot(enumitemslot, itemstack);
++ }
++ // CraftBukkit end
++
+ @Override
+ @Override
+ public abstract void setItemSlot(EquipmentSlot slot, ItemStack stack);
+@@ -2291,6 +2714,7 @@
+ }
+
+ if (this.onGround() && !this.level().isClientSide) {
++ if (getSharedFlag(7) && !CraftEventFactory.callToggleGlideEvent(this, false).isCancelled()) // CraftBukkit
+ this.setSharedFlag(7, false);
+ }
+ } else {
+@@ -2462,7 +2885,7 @@
+ }
+ }
+
+- this.detectEquipmentUpdates();
++ this.detectEquipmentUpdatesPublic(); // CraftBukkit
+ if (this.tickCount % 20 == 0) {
+ this.getCombatTracker().recheckStatus();
+ }
+@@ -2559,7 +2982,7 @@
+ this.refreshDirtyAttributes();
+ }
+
+- private void detectEquipmentUpdates() {
++ public void detectEquipmentUpdatesPublic() { // CraftBukkit
+ Map<EquipmentSlot, ItemStack> map = this.collectEquipmentChanges();
+
+ if (map != null) {
+@@ -2861,6 +3284,7 @@
+ }
+
+ if (!this.level().isClientSide) {
++ if (flag != this.getSharedFlag(7) && !CraftEventFactory.callToggleGlideEvent(this, flag).isCancelled()) // CraftBukkit
+ this.setSharedFlag(7, flag);
+ }
+
+@@ -3062,16 +3475,22 @@
+ @Override
+ @Override
+ public boolean isPickable() {
+- return !this.isRemoved();
++ return !this.isRemoved() && this.collides; // CraftBukkit
+ }
+
+ @Override
+ @Override
+ public boolean isPushable() {
+- return this.isAlive() && !this.isSpectator() && !this.onClimbable();
++ return this.isAlive() && !this.isSpectator() && !this.onClimbable() && this.collides; // CraftBukkit
+ }
+
++ // CraftBukkit start - collidable API
+ @Override
++ public boolean canCollideWithBukkit(Entity entity) {
++ return isPushable() && this.collides != this.collidableExemptions.contains(entity.getUUID());
++ }
++ // CraftBukkit end
++
+ @Override
+ public float getYHeadRot() {
+ return this.yHeadRot;
+@@ -3271,8 +3684,27 @@
+ } else {
+ if (!this.useItem.isEmpty() && this.isUsingItem()) {
+ this.triggerItemUseEffects(this.useItem, 16);
+- ItemStack itemstack = this.useItem.finishUsingItem(this.level(), this);
++ // CraftBukkit start - fire PlayerItemConsumeEvent
++ ItemStack itemstack;
++ if (this instanceof ServerPlayer) {
++ org.bukkit.inventory.ItemStack craftItem = CraftItemStack.asBukkitCopy(this.useItem);
++ org.bukkit.inventory.EquipmentSlot hand = org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(enumhand);
++ PlayerItemConsumeEvent event = new PlayerItemConsumeEvent((Player) this.getBukkitEntity(), craftItem, hand);
++ this.level().getCraftServer().getPluginManager().callEvent(event);
+
++ if (event.isCancelled()) {
++ // Update client
++ ((ServerPlayer) this).getBukkitEntity().updateInventory();
++ ((ServerPlayer) this).getBukkitEntity().updateScaledHealth();
++ return;
++ }
++
++ itemstack = (craftItem.equals(event.getItem())) ? this.useItem.finishUsingItem(this.level(), this) : CraftItemStack.asNMSCopy(event.getItem()).finishUsingItem(this.level(), this);
++ } else {
++ itemstack = this.useItem.finishUsingItem(this.level(), this);
++ }
++ // CraftBukkit end
++
+ if (itemstack != this.useItem) {
+ this.setItemInHand(interactionhand, itemstack);
+ }
+@@ -3349,7 +3780,13 @@
+ return this.fallFlyTicks;
+ }
+
+- public boolean randomTeleport(double d0, double d1, double d2, boolean flag) {
++ public boolean randomTeleport(double x, double d1, double y, boolean flag) {
++ // CraftBukkit start
++ return randomTeleport(x, d1, y, flag, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.UNKNOWN).orElse(false);
++ }
++
++ public Optional<Boolean> randomTeleport(double d0, double d1, double d2, boolean flag, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) {
++ // CraftBukkit end
+ double d3 = this.getX();
+ double d4 = this.getY();
+ double d5 = this.getZ();
+@@ -3374,16 +3811,41 @@
+ }
+
+ if (flag2) {
+- this.teleportTo(d0, d6, d2);
+- if (level.noCollision((Entity) this) && !level.containsAnyLiquid(this.getBoundingBox())) {
++ // CraftBukkit start - Teleport event
++ // this.teleportTo(d0, d6, d2);
++
++ // first set position, to check if the place to teleport is valid
++ this.setPos(d0, d6, d2);
++ if (world.noCollision((Entity) this) && !world.containsAnyLiquid(this.getBoundingBox())) {
+ flag1 = true;
+ }
++ // now revert and call event if the teleport place is valid
++ this.setPos(d3, d4, d5);
++
++ if (flag1) {
++ if (!(this instanceof ServerPlayer)) {
++ EntityTeleportEvent teleport = new EntityTeleportEvent(this.getBukkitEntity(), new Location(this.level().getWorld(), d3, d4, d5), new Location(this.level().getWorld(), d0, d6, d2));
++ this.level().getCraftServer().getPluginManager().callEvent(teleport);
++ if (!teleport.isCancelled()) {
++ Location to = teleport.getTo();
++ this.teleportTo(to.getX(), to.getY(), to.getZ());
++ } else {
++ return Optional.empty();
++ }
++ } else {
++ // player teleport event is called in the underlining code
++ if (((ServerPlayer) this).connection.teleport(d0, d6, d2, this.getYRot(), this.getXRot(), java.util.Collections.emptySet(), cause)) {
++ return Optional.empty();
++ }
++ }
++ }
++ // CraftBukkit end
+ }
+ }
+
+ if (!flag1) {
+- this.teleportTo(d3, d4, d5);
+- return false;
++ // this.enderTeleportTo(d3, d4, d5); // CraftBukkit - already set the location back
++ return Optional.of(false); // CraftBukkit
+ } else {
+ if (flag) {
+ level.broadcastEntityEvent(this, (byte) 46);
+@@ -3395,7 +3857,7 @@
+ pathfindermob.getNavigation().stop();
+ }
+
+- return true;
++ return Optional.of(true); // CraftBukkit
+ }
+ }
+
+@@ -3570,7 +4028,7 @@
+ Pair<MobEffectInstance, Float> pair = (Pair) iterator.next();
+
+ if (!level.isClientSide && pair.getFirst() != null && level.random.nextFloat() < (Float) pair.getSecond()) {
+- livingentity.addEffect(new MobEffectInstance((MobEffectInstance) pair.getFirst()));
++ livingEntity.addEffect(new MobEffectInstance((MobEffectInstance) pair.getFirst()), EntityPotionEffectEvent.Cause.FOOD); // CraftBukkit
+ }
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/Mob.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/Mob.java.patch
new file mode 100644
index 0000000000..e998b34007
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/Mob.java.patch
@@ -0,0 +1,349 @@
+--- a/net/minecraft/world/entity/Mob.java
++++ b/net/minecraft/world/entity/Mob.java
+@@ -75,6 +76,16 @@
+ import net.minecraft.world.level.material.Fluid;
+ import net.minecraft.world.level.pathfinder.BlockPathTypes;
+ import net.minecraft.world.phys.AABB;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.craftbukkit.entity.CraftLivingEntity;
++import org.bukkit.event.entity.CreatureSpawnEvent;
++import org.bukkit.event.entity.EntityCombustByEntityEvent;
++import org.bukkit.event.entity.EntityTargetLivingEntityEvent;
++import org.bukkit.event.entity.EntityTargetEvent;
++import org.bukkit.event.entity.EntityTransformEvent;
++import org.bukkit.event.entity.EntityUnleashEvent;
++import org.bukkit.event.entity.EntityUnleashEvent.UnleashReason;
++// CraftBukkit end
+
+ public abstract class Mob extends LivingEntity implements Targeting {
+
+@@ -123,8 +134,10 @@
+ private BlockPos restrictCenter;
+ private float restrictRadius;
+
+- protected Mob(EntityType<? extends Mob> entitytype, Level level) {
+- super(entitytype, level);
++ public boolean aware = true; // CraftBukkit
++
++ protected Mob(EntityType<? extends Mob> entityType, Level level) {
++ super(entityType, level);
+ this.handItems = NonNullList.withSize(2, ItemStack.EMPTY);
+ this.handDropChances = new float[2];
+ this.armorItems = NonNullList.withSize(4, ItemStack.EMPTY);
+@@ -148,6 +161,12 @@
+
+ }
+
++ // CraftBukkit start
++ public void setPersistenceRequired(boolean persistenceRequired) {
++ this.persistenceRequired = persistenceRequired;
++ }
++ // CraftBukkit end
++
+ protected void registerGoals() {}
+
+ public static AttributeSupplier.Builder createMobAttributes() {
+@@ -260,10 +277,41 @@
+ return this.target;
+ }
+
+- public void setTarget(@Nullable LivingEntity livingentity) {
+- this.target = livingentity;
++ public void setTarget(@Nullable LivingEntity target) {
++ // CraftBukkit start - fire event
++ setTarget(target, EntityTargetEvent.TargetReason.UNKNOWN, true);
+ }
+
++ public boolean setTarget(LivingEntity entityliving, EntityTargetEvent.TargetReason reason, boolean fireEvent) {
++ if (getTarget() == entityliving) return false;
++ if (fireEvent) {
++ if (reason == EntityTargetEvent.TargetReason.UNKNOWN && getTarget() != null && entityliving == null) {
++ reason = getTarget().isAlive() ? EntityTargetEvent.TargetReason.FORGOT_TARGET : EntityTargetEvent.TargetReason.TARGET_DIED;
++ }
++ if (reason == EntityTargetEvent.TargetReason.UNKNOWN) {
++ this.level().getCraftServer().getLogger().log(java.util.logging.Level.WARNING, "Unknown target reason, please report on the issue tracker", new Exception());
++ }
++ CraftLivingEntity ctarget = null;
++ if (entityliving != null) {
++ ctarget = (CraftLivingEntity) entityliving.getBukkitEntity();
++ }
++ EntityTargetLivingEntityEvent event = new EntityTargetLivingEntityEvent(this.getBukkitEntity(), ctarget, reason);
++ this.level().getCraftServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ return false;
++ }
++
++ if (event.getTarget() != null) {
++ entityliving = ((CraftLivingEntity) event.getTarget()).getHandle();
++ } else {
++ entityliving = null;
++ }
++ }
++ this.target = entityliving;
++ return true;
++ // CraftBukkit end
++ }
++
+ @Override
+ @Override
+ public boolean canAttackType(EntityType<?> entitytype) {
+@@ -409,6 +449,12 @@
+ return null;
+ }
+
++ // CraftBukkit start - Add delegate method
++ public SoundEvent getAmbientSound0() {
++ return getAmbientSound();
++ }
++ // CraftBukkit end
++
+ @Override
+ @Override
+ public void addAdditionalSaveData(CompoundTag compoundtag) {
+@@ -467,9 +512,9 @@
+ listtag3.add(FloatTag.valueOf(f1));
+ }
+
+- compoundtag.put("HandDropChances", listtag3);
+- if (this.leashHolder != null) {
+- compoundtag2 = new CompoundTag();
++ compound.put("HandDropChances", nbttaglist3);
++ if (this.leashHolder != null && !this.leashHolder.pluginRemoved) { // CraftBukkit - SPIGOT-7487: Don't save (and possible drop) leash, when the holder was removed by a plugin
++ nbttagcompound2 = new CompoundTag();
+ if (this.leashHolder instanceof LivingEntity) {
+ UUID uuid = this.leashHolder.getUUID();
+
+@@ -499,18 +544,27 @@
+ compoundtag.putBoolean("NoAI", this.isNoAi());
+ }
+
++ compound.putBoolean("Bukkit.Aware", this.aware); // CraftBukkit
+ }
+
+ @Override
+- @Override
+- public void readAdditionalSaveData(CompoundTag compoundtag) {
+- super.readAdditionalSaveData(compoundtag);
+- if (compoundtag.contains("CanPickUpLoot", 1)) {
+- this.setCanPickUpLoot(compoundtag.getBoolean("CanPickUpLoot"));
++ public void readAdditionalSaveData(CompoundTag compound) {
++ super.readAdditionalSaveData(compound);
++
++ // CraftBukkit start - If looting or persistence is false only use it if it was set after we started using it
++ if (compound.contains("CanPickUpLoot", 1)) {
++ boolean data = compound.getBoolean("CanPickUpLoot");
++ if (isLevelAtLeast(compound, 1) || data) {
++ this.setCanPickUpLoot(data);
++ }
+ }
+
+- this.persistenceRequired = compoundtag.getBoolean("PersistenceRequired");
+- ListTag listtag;
++ boolean data = compound.getBoolean("PersistenceRequired");
++ if (isLevelAtLeast(compound, 1) || data) {
++ this.persistenceRequired = data;
++ }
++ // CraftBukkit end
++ ListTag nbttaglist;
+ int i;
+
+ if (compoundtag.contains("ArmorItems", 9)) {
+@@ -555,7 +609,12 @@
+ this.lootTableSeed = compoundtag.getLong("DeathLootTableSeed");
+ }
+
+- this.setNoAi(compoundtag.getBoolean("NoAI"));
++ this.setNoAi(compound.getBoolean("NoAI"));
++ // CraftBukkit start
++ if (compound.contains("Bukkit.Aware")) {
++ this.aware = compound.getBoolean("Bukkit.Aware");
++ }
++ // CraftBukkit end
+ }
+
+ @Override
+@@ -626,9 +680,9 @@
+ return Mob.ITEM_PICKUP_REACH;
+ }
+
+- protected void pickUpItem(ItemEntity itementity) {
+- ItemStack itemstack = itementity.getItem();
+- ItemStack itemstack1 = this.equipItemIfPossible(itemstack.copy());
++ protected void pickUpItem(ItemEntity itemEntity) {
++ ItemStack itemstack = itemEntity.getItem();
++ ItemStack itemstack1 = this.equipItemIfPossible(itemstack.copy(), itemEntity); // CraftBukkit - add item
+
+ if (!itemstack1.isEmpty()) {
+ this.onItemPickup(itementity);
+@@ -641,9 +695,15 @@
+
+ }
+
+- public ItemStack equipItemIfPossible(ItemStack itemstack) {
+- EquipmentSlot equipmentslot = getEquipmentSlotForItem(itemstack);
+- ItemStack itemstack1 = this.getItemBySlot(equipmentslot);
++ public ItemStack equipItemIfPossible(ItemStack stack) {
++ // CraftBukkit start - add item
++ return this.equipItemIfPossible(stack, null);
++ }
++
++ public ItemStack equipItemIfPossible(ItemStack itemstack, ItemEntity entityitem) {
++ // CraftBukkit end
++ EquipmentSlot enumitemslot = getEquipmentSlotForItem(itemstack);
++ ItemStack itemstack1 = this.getItemBySlot(enumitemslot);
+ boolean flag = this.canReplaceCurrentItem(itemstack, itemstack1);
+
+ if (equipmentslot.isArmor() && !flag) {
+@@ -652,11 +712,19 @@
+ flag = itemstack1.isEmpty();
+ }
+
+- if (flag && this.canHoldItem(itemstack)) {
+- double d0 = (double) this.getEquipmentDropChance(equipmentslot);
++ // CraftBukkit start
++ boolean canPickup = flag && this.canHoldItem(itemstack);
++ if (entityitem != null) {
++ canPickup = !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(this, entityitem, 0, !canPickup).isCancelled();
++ }
++ if (canPickup) {
++ // CraftBukkit end
++ double d0 = (double) this.getEquipmentDropChance(enumitemslot);
+
+ if (!itemstack1.isEmpty() && (double) Math.max(this.random.nextFloat() - 0.1F, 0.0F) < d0) {
++ this.forceDrops = true; // CraftBukkit
+ this.spawnAtLocation(itemstack1);
++ this.forceDrops = false; // CraftBukkit
+ }
+
+ if (equipmentslot.isArmor() && itemstack.getCount() > 1) {
+@@ -810,6 +876,7 @@
+ @Override
+ protected final void serverAiStep() {
+ ++this.noActionTime;
++ if (!this.aware) return; // CraftBukkit
+ this.level().getProfiler().push("sensing");
+ this.sensing.tick();
+ this.level().getProfiler().pop();
+@@ -1211,6 +1270,12 @@
+ if (!this.isAlive()) {
+ return InteractionResult.PASS;
+ } else if (this.getLeashHolder() == player) {
++ // CraftBukkit start - fire PlayerUnleashEntityEvent
++ if (CraftEventFactory.callPlayerUnleashEntityEvent(this, player, hand).isCancelled()) {
++ ((ServerPlayer) player).connection.send(new ClientboundSetEntityLinkPacket(this, this.getLeashHolder()));
++ return InteractionResult.PASS;
++ }
++ // CraftBukkit end
+ this.dropLeash(true, !player.getAbilities().instabuild);
+ this.gameEvent(GameEvent.ENTITY_INTERACT, player);
+ return InteractionResult.sidedSuccess(this.level().isClientSide);
+@@ -1236,6 +1301,12 @@
+ ItemStack itemstack = player.getItemInHand(interactionhand);
+
+ if (itemstack.is(Items.LEAD) && this.canBeLeashed(player)) {
++ // CraftBukkit start - fire PlayerLeashEntityEvent
++ if (CraftEventFactory.callPlayerLeashEntityEvent(this, player, player, hand).isCancelled()) {
++ ((ServerPlayer) player).connection.send(new ClientboundSetEntityLinkPacket(this, this.getLeashHolder()));
++ return InteractionResult.PASS;
++ }
++ // CraftBukkit end
+ this.setLeashedTo(player, true);
+ itemstack.shrink(1);
+ return InteractionResult.sidedSuccess(this.level().isClientSide);
+@@ -1301,8 +1372,15 @@
+ return this.restrictRadius != -1.0F;
+ }
+
++ // CraftBukkit start
+ @Nullable
+- public <T extends Mob> T convertTo(EntityType<T> entitytype, boolean flag) {
++ public <T extends Mob> T convertTo(EntityType<T> entityType, boolean transferInventory) {
++ return this.convertTo(entityType, transferInventory, EntityTransformEvent.TransformReason.UNKNOWN, CreatureSpawnEvent.SpawnReason.DEFAULT);
++ }
++
++ @Nullable
++ public <T extends Mob> T convertTo(EntityType<T> entitytypes, boolean flag, EntityTransformEvent.TransformReason transformReason, CreatureSpawnEvent.SpawnReason spawnReason) {
++ // CraftBukkit end
+ if (this.isRemoved()) {
+ return null;
+ } else {
+@@ -1340,7 +1418,12 @@
+ }
+ }
+
+- this.level().addFreshEntity(t0);
++ // CraftBukkit start
++ if (CraftEventFactory.callEntityTransformEvent(this, t0, transformReason).isCancelled()) {
++ return null;
++ }
++ this.level().addFreshEntity(t0, spawnReason);
++ // CraftBukkit end
+ if (this.isPassenger()) {
+ Entity entity = this.getVehicle();
+
+@@ -1361,7 +1444,8 @@
+
+ if (this.leashHolder != null) {
+ if (!this.isAlive() || !this.leashHolder.isAlive()) {
+- this.dropLeash(true, true);
++ this.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), (!this.isAlive()) ? UnleashReason.PLAYER_UNLEASH : UnleashReason.HOLDER_GONE)); // CraftBukkit
++ this.dropLeash(true, !this.leashHolder.pluginRemoved);// CraftBukkit - SPIGOT-7487: Don't drop leash, when the holder was removed by a plugin
+ }
+
+ }
+@@ -1371,8 +1455,10 @@
+ if (this.leashHolder != null) {
+ this.leashHolder = null;
+ this.leashInfoTag = null;
+- if (!this.level().isClientSide && flag1) {
+- this.spawnAtLocation((ItemLike) Items.LEAD);
++ if (!this.level().isClientSide && dropLeash) {
++ this.forceDrops = true; // CraftBukkit
++ this.spawnAtLocation((IMaterial) Items.LEAD);
++ this.forceDrops = false; // CraftBukkit
+ }
+
+ if (!this.level().isClientSide && flag && this.level() instanceof ServerLevel) {
+@@ -1423,6 +1508,7 @@
+ boolean flag1 = super.startRiding(entity, flag);
+
+ if (flag1 && this.isLeashed()) {
++ this.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN)); // CraftBukkit
+ this.dropLeash(true, true);
+ }
+
+@@ -1447,7 +1533,9 @@
+ }
+
+ if (this.tickCount > 100) {
+- this.spawnAtLocation((ItemLike) Items.LEAD);
++ this.forceDrops = true; // CraftBukkit
++ this.spawnAtLocation((IMaterial) Items.LEAD);
++ this.forceDrops = false; // CraftBukkit
+ this.leashInfoTag = null;
+ }
+ }
+@@ -1532,7 +1617,14 @@
+ int i = EnchantmentHelper.getFireAspect(this);
+
+ if (i > 0) {
+- entity.setSecondsOnFire(i * 4);
++ // CraftBukkit start - Call a combust event when somebody hits with a fire enchanted item
++ EntityCombustByEntityEvent combustEvent = new EntityCombustByEntityEvent(this.getBukkitEntity(), entity.getBukkitEntity(), i * 4);
++ org.bukkit.Bukkit.getPluginManager().callEvent(combustEvent);
++
++ if (!combustEvent.isCancelled()) {
++ entity.setSecondsOnFire(combustEvent.getDuration(), false);
++ }
++ // CraftBukkit end
+ }
+
+ boolean flag = entity.hurt(this.damageSources().mobAttack(this), f);
+@@ -1609,6 +1699,7 @@
+ @Override
+ protected void removeAfterChangingDimensions() {
+ super.removeAfterChangingDimensions();
++ this.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN)); // CraftBukkit
+ this.dropLeash(true, false);
+ this.getAllSlots().forEach((itemstack) -> {
+ if (!itemstack.isEmpty()) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/NeutralMob.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/NeutralMob.java.patch
new file mode 100644
index 0000000000..d997e5441b
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/NeutralMob.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/entity/NeutralMob.java
++++ b/net/minecraft/world/entity/NeutralMob.java
+@@ -108,7 +108,7 @@
+ default void stopBeingAngry() {
+ this.setLastHurtByMob((LivingEntity) null);
+ this.setPersistentAngerTarget((UUID) null);
+- this.setTarget((LivingEntity) null);
++ this.setTarget((LivingEntity) null, org.bukkit.event.entity.EntityTargetEvent.TargetReason.FORGOT_TARGET, true); // CraftBukkit
+ this.setRemainingPersistentAngerTime(0);
+ }
+
+@@ -121,6 +121,8 @@
+
+ void setTarget(@Nullable LivingEntity livingEntity);
+
++ boolean setTarget(@Nullable LivingEntity entityliving, org.bukkit.event.entity.EntityTargetEvent.TargetReason reason, boolean fireEvent); // CraftBukkit
++
+ boolean canAttack(LivingEntity entity);
+
+ @Nullable
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/PathfinderMob.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/PathfinderMob.java.patch
new file mode 100644
index 0000000000..1b7ee9be1f
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/PathfinderMob.java.patch
@@ -0,0 +1,28 @@
+--- a/net/minecraft/world/entity/PathfinderMob.java
++++ b/net/minecraft/world/entity/PathfinderMob.java
+@@ -8,6 +8,9 @@
+ import net.minecraft.world.level.LevelAccessor;
+ import net.minecraft.world.level.LevelReader;
+ import net.minecraft.world.phys.Vec3;
++// CraftBukkit start
++import org.bukkit.event.entity.EntityUnleashEvent;
++// CraftBukkit end
+
+ public abstract class PathfinderMob extends Mob {
+
+@@ -53,6 +54,7 @@
+
+ if (this instanceof TamableAnimal && ((TamableAnimal) this).isInSittingPose()) {
+ if (f > 10.0F) {
++ this.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE)); // CraftBukkit
+ this.dropLeash(true, true);
+ }
+
+@@ -61,6 +63,7 @@
+
+ this.onLeashDistance(f);
+ if (f > 10.0F) {
++ this.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.DISTANCE)); // CraftBukkit
+ this.dropLeash(true, true);
+ this.goalSelector.disableControlFlag(Goal.Flag.MOVE);
+ } else if (f > 6.0F) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/attributes/Attributes.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/attributes/Attributes.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/attributes/Attributes.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java.patch
new file mode 100644
index 0000000000..293e20b291
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java.patch
@@ -0,0 +1,33 @@
+--- a/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java
++++ b/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java
+@@ -9,6 +9,12 @@
+ import net.minecraft.world.entity.npc.Villager;
+ import net.minecraft.world.entity.npc.VillagerProfession;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.entity.CraftVillager;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.VillagerCareerChangeEvent;
++// CraftBukkit end
++
+ public class AssignProfessionFromJobSite {
+
+ public AssignProfessionFromJobSite() {}
+@@ -37,8 +43,15 @@
+ return villagerprofession.heldJobSite().test(holder);
+ }).findFirst();
+ }).ifPresent((villagerprofession) -> {
+- villager.setVillagerData(villager.getVillagerData().setProfession(villagerprofession));
+- villager.refreshBrain(serverlevel);
++ // CraftBukkit start - Fire VillagerCareerChangeEvent where Villager gets employed
++ VillagerCareerChangeEvent event = CraftEventFactory.callVillagerCareerChangeEvent(entityvillager, CraftVillager.CraftProfession.minecraftToBukkit(villagerprofession), VillagerCareerChangeEvent.ChangeReason.EMPLOYED);
++ if (event.isCancelled()) {
++ return;
++ }
++
++ entityvillager.setVillagerData(entityvillager.getVillagerData().setProfession(CraftVillager.CraftProfession.bukkitToMinecraft(event.getProfession())));
++ // CraftBukkit end
++ entityvillager.refreshBrain(worldserver);
+ });
+ return true;
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java.patch
new file mode 100644
index 0000000000..d036d0e4d1
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java.patch
@@ -0,0 +1,40 @@
+--- a/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java
++++ b/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java
+@@ -7,6 +7,12 @@
+ import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder;
+ import net.minecraft.world.entity.ai.memory.MemoryModuleType;
+ import net.minecraft.world.entity.ai.memory.WalkTarget;
++// CraftBukkit start
++import org.bukkit.craftbukkit.entity.CraftLivingEntity;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityTargetEvent;
++import org.bukkit.event.entity.EntityTargetLivingEntityEvent;
++// CraftBukkit end
+
+ public class BabyFollowAdult {
+
+@@ -25,10 +31,21 @@
+ if (!ageablemob.isBaby()) {
+ return false;
+ } else {
+- AgeableMob ageablemob1 = (AgeableMob) behaviorbuilder_instance.get(memoryaccessor);
++ LivingEntity entityageable1 = (AgeableMob) behaviorbuilder_b.get(memoryaccessor); // CraftBukkit - type
+
+- if (ageablemob.closerThan(ageablemob1, (double) (uniformint.getMaxValue() + 1)) && !ageablemob.closerThan(ageablemob1, (double) uniformint.getMinValue())) {
+- WalkTarget walktarget = new WalkTarget(new EntityTracker(ageablemob1, false), (Float) function.apply(ageablemob), uniformint.getMinValue() - 1);
++ if (entityageable.closerThan(entityageable1, (double) (followRange.getMaxValue() + 1)) && !entityageable.closerThan(entityageable1, (double) followRange.getMinValue())) {
++ // CraftBukkit start
++ EntityTargetLivingEntityEvent event = CraftEventFactory.callEntityTargetLivingEvent(entityageable, entityageable1, EntityTargetEvent.TargetReason.FOLLOW_LEADER);
++ if (event.isCancelled()) {
++ return false;
++ }
++ if (event.getTarget() == null) {
++ memoryaccessor.erase();
++ return true;
++ }
++ entityageable1 = ((CraftLivingEntity) event.getTarget()).getHandle();
++ // CraftBukkit end
++ WalkTarget memorytarget = new WalkTarget(new EntityTracker(entityageable1, false), (Float) speedModifier.apply(entityageable), followRange.getMinValue() - 1);
+
+ memoryaccessor1.set(new EntityTracker(ageablemob1, true));
+ memoryaccessor2.set(walktarget);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java.patch
new file mode 100644
index 0000000000..4e842e3a81
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java.patch
@@ -0,0 +1,35 @@
+--- a/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java
++++ b/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java
+@@ -91,17 +91,25 @@
+ throwItem(livingentity, itemstack, vec3, vec31, 0.3F);
+ }
+
+- public static void throwItem(LivingEntity livingentity, ItemStack itemstack, Vec3 vec3, Vec3 vec31, float f) {
+- double d0 = livingentity.getEyeY() - (double) f;
+- ItemEntity itementity = new ItemEntity(livingentity.level(), livingentity.getX(), d0, livingentity.getZ(), itemstack);
++ public static void throwItem(LivingEntity entity, ItemStack stack, Vec3 offset, Vec3 speedMultiplier, float yOffset) {
++ if (stack.isEmpty()) return; // CraftBukkit - SPIGOT-4940: no empty loot
++ double d0 = entity.getEyeY() - (double) yOffset;
++ ItemEntity entityitem = new ItemEntity(entity.level(), entity.getX(), d0, entity.getZ(), stack);
+
+ itementity.setThrower(livingentity);
+ Vec3 vec32 = vec3.subtract(livingentity.position());
+
+- vec32 = vec32.normalize().multiply(vec31.x, vec31.y, vec31.z);
+- itementity.setDeltaMovement(vec32);
+- itementity.setDefaultPickUpDelay();
+- livingentity.level().addFreshEntity(itementity);
++ vec3d2 = vec3d2.normalize().multiply(speedMultiplier.x, speedMultiplier.y, speedMultiplier.z);
++ entityitem.setDeltaMovement(vec3d2);
++ entityitem.setDefaultPickUpDelay();
++ // CraftBukkit start
++ org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(entity.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity());
++ entityitem.level().getCraftServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
++ entity.level().addFreshEntity(entityitem);
+ }
+
+ public static SectionPos findSectionClosestToVillage(ServerLevel serverlevel, SectionPos sectionpos, int i) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java.patch
new file mode 100644
index 0000000000..916a7f852c
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java.patch
@@ -0,0 +1,32 @@
+--- a/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java
++++ b/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java
+@@ -27,11 +27,25 @@
+ return (serverlevel, livingentity, j) -> {
+ ItemEntity itementity = (ItemEntity) behaviorbuilder_instance.get(memoryaccessor2);
+
+- if (behaviorbuilder_instance.tryGet(memoryaccessor3).isEmpty() && predicate.test(livingentity) && itementity.closerThan(livingentity, (double) i) && livingentity.level().getWorldBorder().isWithinBounds(itementity.blockPosition())) {
+- WalkTarget walktarget = new WalkTarget(new EntityTracker(itementity, false), f, 0);
++ if (behaviorbuilder_b.tryGet(memoryaccessor3).isEmpty() && canWalkToItem.test(entityliving) && entityitem.closerThan(entityliving, (double) maxDistToWalk) && entityliving.level().getWorldBorder().isWithinBounds(entityitem.blockPosition())) {
++ // CraftBukkit start
++ if (entityliving instanceof net.minecraft.world.entity.animal.allay.Allay) {
++ org.bukkit.event.entity.EntityTargetEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTargetEvent(entityliving, entityitem, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_ENTITY);
+
+- memoryaccessor.set(new EntityTracker(itementity, true));
+- memoryaccessor1.set(walktarget);
++ if (event.isCancelled()) {
++ return false;
++ }
++ if (!(event.getTarget() instanceof ItemEntity)) {
++ memoryaccessor2.erase();
++ }
++
++ entityitem = (ItemEntity) ((org.bukkit.craftbukkit.entity.CraftEntity) event.getTarget()).getHandle();
++ }
++ // CraftBukkit end
++ WalkTarget memorytarget = new WalkTarget(new EntityTracker(entityitem, false), speedModifier, 0);
++
++ memoryaccessor.set(new EntityTracker(entityitem, true));
++ memoryaccessor1.set(memorytarget);
+ return true;
+ } else {
+ return false;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java.patch
new file mode 100644
index 0000000000..9a8b798fe4
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java.patch
@@ -0,0 +1,47 @@
+--- a/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java
++++ b/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java
+@@ -21,10 +21,15 @@
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.level.GameRules;
+ import net.minecraft.world.level.block.Block;
++import net.minecraft.world.level.block.state.IBlockData;
++import net.minecraft.world.level.gameevent.GameEvent;
++
++// CraftBukkit start
++import net.minecraft.world.level.block.Blocks;
+ import net.minecraft.world.level.block.CropBlock;
+ import net.minecraft.world.level.block.FarmBlock;
+-import net.minecraft.world.level.block.state.BlockState;
+-import net.minecraft.world.level.gameevent.GameEvent;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++// CraftBukkit end
+
+ public class HarvestFarmland extends Behavior<Villager> {
+
+@@ -105,8 +106,10 @@
+ Block block = blockstate.getBlock();
+ Block block1 = serverlevel.getBlockState(this.aboveFarmlandPos.below()).getBlock();
+
+- if (block instanceof CropBlock && ((CropBlock) block).isMaxAge(blockstate)) {
+- serverlevel.destroyBlock(this.aboveFarmlandPos, true, villager);
++ if (block instanceof CropBlock && ((CropBlock) block).isMaxAge(iblockdata)) {
++ if (CraftEventFactory.callEntityChangeBlockEvent(owner, this.aboveFarmlandPos, Blocks.AIR.defaultBlockState())) { // CraftBukkit
++ level.destroyBlock(this.aboveFarmlandPos, true, owner);
++ } // CraftBukkit
+ }
+
+ if (blockstate.isAir() && block1 instanceof FarmBlock && villager.hasFarmSeeds()) {
+@@ -123,9 +126,11 @@
+ BlockItem blockitem = (BlockItem) item;
+ BlockState blockstate1 = blockitem.getBlock().defaultBlockState();
+
+- serverlevel.setBlockAndUpdate(this.aboveFarmlandPos, blockstate1);
+- serverlevel.gameEvent(GameEvent.BLOCK_PLACE, this.aboveFarmlandPos, GameEvent.Context.of(villager, blockstate1));
++ if (CraftEventFactory.callEntityChangeBlockEvent(owner, this.aboveFarmlandPos, iblockdata1)) { // CraftBukkit
++ level.setBlockAndUpdate(this.aboveFarmlandPos, iblockdata1);
++ level.gameEvent(GameEvent.BLOCK_PLACE, this.aboveFarmlandPos, GameEvent.Context.of(owner, iblockdata1));
+ flag = true;
++ } // CraftBukkit
+ }
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java.patch
new file mode 100644
index 0000000000..5929a6db5d
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java.patch
@@ -0,0 +1,40 @@
+--- a/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java
++++ b/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java
+@@ -60,8 +60,15 @@
+ })) {
+ DoorBlock doorblock = (DoorBlock) blockstate.getBlock();
+
+- if (!doorblock.isOpen(blockstate)) {
+- doorblock.setOpen(livingentity, serverlevel, blockstate, blockpos, true);
++ if (!blockdoor.isOpen(iblockdata)) {
++ // CraftBukkit start - entities opening doors
++ org.bukkit.event.entity.EntityInteractEvent event = new org.bukkit.event.entity.EntityInteractEvent(entityliving.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(entityliving.level(), blockposition));
++ entityliving.level().getCraftServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ return false;
++ }
++ // CraftBukkit end
++ blockdoor.setOpen(entityliving, worldserver, iblockdata, blockposition, true);
+ }
+
+ optional = rememberDoorToClose(memoryaccessor1, optional, serverlevel, blockpos);
+@@ -75,9 +82,16 @@
+ })) {
+ DoorBlock doorblock1 = (DoorBlock) blockstate1.getBlock();
+
+- if (!doorblock1.isOpen(blockstate1)) {
+- doorblock1.setOpen(livingentity, serverlevel, blockstate1, blockpos1, true);
+- optional = rememberDoorToClose(memoryaccessor1, optional, serverlevel, blockpos1);
++ if (!blockdoor1.isOpen(iblockdata1)) {
++ // CraftBukkit start - entities opening doors
++ org.bukkit.event.entity.EntityInteractEvent event = new org.bukkit.event.entity.EntityInteractEvent(entityliving.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(entityliving.level(), blockposition1));
++ entityliving.level().getCraftServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ return false;
++ }
++ // CraftBukkit end
++ blockdoor1.setOpen(entityliving, worldserver, iblockdata1, blockposition1, true);
++ optional = rememberDoorToClose(memoryaccessor1, optional, worldserver, blockposition1);
+ }
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java.patch
new file mode 100644
index 0000000000..84e29804b4
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java.patch
@@ -0,0 +1,31 @@
+--- a/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java
++++ b/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java
+@@ -29,6 +30,10 @@
+ import net.minecraft.world.level.pathfinder.Path;
+ import net.minecraft.world.level.pathfinder.WalkNodeEvaluator;
+ import net.minecraft.world.phys.Vec3;
++import org.bukkit.craftbukkit.entity.CraftLivingEntity;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityTargetEvent;
++// CraftBukkit end
+
+ public class PrepareRamNearestTarget<E extends PathfinderMob> extends Behavior<E> {
+
+@@ -62,8 +66,15 @@
+ return nearestvisiblelivingentities.findClosest((livingentity) -> {
+ return this.ramTargeting.test(pathfindermob, livingentity);
+ });
+- }).ifPresent((livingentity) -> {
+- this.chooseRamPosition(pathfindermob, livingentity);
++ }).ifPresent((entityliving) -> {
++ // CraftBukkit start
++ EntityTargetEvent event = CraftEventFactory.callEntityTargetLivingEvent(entity, entityliving, (entityliving instanceof ServerPlayer) ? EntityTargetEvent.TargetReason.CLOSEST_PLAYER : EntityTargetEvent.TargetReason.CLOSEST_ENTITY);
++ if (event.isCancelled() || event.getTarget() == null) {
++ return;
++ }
++ entityliving = ((CraftLivingEntity) event.getTarget()).getHandle();
++ // CraftBukkit end
++ this.chooseRamPosition(entity, entityliving);
+ });
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/ResetProfession.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/ResetProfession.java.patch
new file mode 100644
index 0000000000..1bc8e58c47
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/ResetProfession.java.patch
@@ -0,0 +1,35 @@
+--- a/net/minecraft/world/entity/ai/behavior/ResetProfession.java
++++ b/net/minecraft/world/entity/ai/behavior/ResetProfession.java
+@@ -6,6 +6,12 @@
+ import net.minecraft.world.entity.npc.VillagerData;
+ import net.minecraft.world.entity.npc.VillagerProfession;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.entity.CraftVillager;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.VillagerCareerChangeEvent;
++// CraftBukkit end
++
+ public class ResetProfession {
+
+ public ResetProfession() {}
+@@ -16,9 +22,16 @@
+ return (serverlevel, villager, i) -> {
+ VillagerData villagerdata = villager.getVillagerData();
+
+- if (villagerdata.getProfession() != VillagerProfession.NONE && villagerdata.getProfession() != VillagerProfession.NITWIT && villager.getVillagerXp() == 0 && villagerdata.getLevel() <= 1) {
+- villager.setVillagerData(villager.getVillagerData().setProfession(VillagerProfession.NONE));
+- villager.refreshBrain(serverlevel);
++ if (villagerdata.getProfession() != VillagerProfession.NONE && villagerdata.getProfession() != VillagerProfession.NITWIT && entityvillager.getVillagerXp() == 0 && villagerdata.getLevel() <= 1) {
++ // CraftBukkit start
++ VillagerCareerChangeEvent event = CraftEventFactory.callVillagerCareerChangeEvent(entityvillager, CraftVillager.CraftProfession.minecraftToBukkit(VillagerProfession.NONE), VillagerCareerChangeEvent.ChangeReason.LOSING_JOB);
++ if (event.isCancelled()) {
++ return false;
++ }
++
++ entityvillager.setVillagerData(entityvillager.getVillagerData().setProfession(CraftVillager.CraftProfession.bukkitToMinecraft(event.getProfession())));
++ // CraftBukkit end
++ entityvillager.refreshBrain(worldserver);
+ return true;
+ } else {
+ return false;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/StartAttacking.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/StartAttacking.java.patch
new file mode 100644
index 0000000000..d06873d925
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/StartAttacking.java.patch
@@ -0,0 +1,33 @@
+--- a/net/minecraft/world/entity/ai/behavior/StartAttacking.java
++++ b/net/minecraft/world/entity/ai/behavior/StartAttacking.java
+@@ -7,6 +8,10 @@
+ import net.minecraft.world.entity.Mob;
+ import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder;
+ import net.minecraft.world.entity.ai.memory.MemoryModuleType;
++import org.bukkit.craftbukkit.entity.CraftLivingEntity;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityTargetEvent;
++// CraftBukkit end
+
+ public class StartAttacking {
+
+@@ -35,7 +40,18 @@
+ if (!mob.canAttack(livingentity)) {
+ return false;
+ } else {
+- memoryaccessor.set(livingentity);
++ // CraftBukkit start
++ EntityTargetEvent event = CraftEventFactory.callEntityTargetLivingEvent(entityinsentient, entityliving, (entityliving instanceof ServerPlayer) ? EntityTargetEvent.TargetReason.CLOSEST_PLAYER : EntityTargetEvent.TargetReason.CLOSEST_ENTITY);
++ if (event.isCancelled()) {
++ return false;
++ }
++ if (event.getTarget() == null) {
++ memoryaccessor.erase();
++ return true;
++ }
++ entityliving = ((CraftLivingEntity) event.getTarget()).getHandle();
++ // CraftBukkit end
++ memoryaccessor.set(entityliving);
+ memoryaccessor1.erase();
+ return true;
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java.patch
new file mode 100644
index 0000000000..5d5b0c7ab5
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java.patch
@@ -0,0 +1,36 @@
+--- a/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java
++++ b/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java
+@@ -8,6 +8,12 @@
+ import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder;
+ import net.minecraft.world.entity.ai.memory.MemoryModuleType;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.entity.CraftLivingEntity;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityTargetEvent;
++// CraftBukkit end
++
+ public class StopAttackingIfTargetInvalid {
+
+ private static final int TIMEOUT_TO_GET_WITHIN_ATTACK_RANGE = 200;
+@@ -41,7 +47,19 @@
+ if (mob.canAttack(livingentity) && (!flag || !isTiredOfTryingToReachTarget(mob, behaviorbuilder_instance.tryGet(memoryaccessor1))) && livingentity.isAlive() && livingentity.level() == mob.level() && !predicate.test(livingentity)) {
+ return true;
+ } else {
+- biconsumer.accept(mob, livingentity);
++ // CraftBukkit start
++ LivingEntity old = entityinsentient.getBrain().getMemory(MemoryModuleType.ATTACK_TARGET).orElse(null);
++ EntityTargetEvent event = CraftEventFactory.callEntityTargetLivingEvent(entityinsentient, null, (old != null && !old.isAlive()) ? EntityTargetEvent.TargetReason.TARGET_DIED : EntityTargetEvent.TargetReason.FORGOT_TARGET);
++ if (event.isCancelled()) {
++ return false;
++ }
++ if (event.getTarget() == null) {
++ memoryaccessor.erase();
++ return true;
++ }
++ entityliving = ((CraftLivingEntity) event.getTarget()).getHandle();
++ // CraftBukkit end
++ onStopAttacking.accept(entityinsentient, entityliving);
+ memoryaccessor.erase();
+ return true;
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java.patch
new file mode 100644
index 0000000000..020f4f13a9
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java.patch
@@ -0,0 +1,40 @@
+--- a/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java
++++ b/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java
+@@ -17,6 +17,10 @@
+ import net.minecraft.world.entity.ai.village.poi.PoiTypes;
+ import net.minecraft.world.entity.npc.Villager;
+ import net.minecraft.world.level.pathfinder.Path;
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.CreatureSpawnEvent;
++// CraftBukkit end
+
+ public class VillagerMakeLove extends Behavior<Villager> {
+
+@@ -121,13 +120,19 @@
+ if (villager2 == null) {
+ return Optional.empty();
+ } else {
+- villager.setAge(6000);
+- villager1.setAge(6000);
+- villager2.setAge(-24000);
+- villager2.moveTo(villager.getX(), villager.getY(), villager.getZ(), 0.0F, 0.0F);
+- serverlevel.addFreshEntityWithPassengers(villager2);
+- serverlevel.broadcastEntityEvent(villager2, (byte) 12);
+- return Optional.of(villager2);
++ entityvillager2.setAge(-24000);
++ entityvillager2.moveTo(parent.getX(), parent.getY(), parent.getZ(), 0.0F, 0.0F);
++ // CraftBukkit start - call EntityBreedEvent
++ if (CraftEventFactory.callEntityBreedEvent(entityvillager2, parent, partner, null, null, 0).isCancelled()) {
++ return Optional.empty();
++ }
++ // Move age setting down
++ parent.setAge(6000);
++ partner.setAge(6000);
++ level.addFreshEntityWithPassengers(entityvillager2, CreatureSpawnEvent.SpawnReason.BREEDING);
++ // CraftBukkit end
++ level.broadcastEntityEvent(entityvillager2, (byte) 12);
++ return Optional.of(entityvillager2);
+ }
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java.patch
new file mode 100644
index 0000000000..a4cc73ca81
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java.patch
@@ -0,0 +1,15 @@
+--- a/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java
++++ b/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java
+@@ -76,6 +71,12 @@
+ }
+
+ if (this.breakTime == this.getDoorBreakTime() && this.isValidDifficulty(this.mob.level().getDifficulty())) {
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreakDoorEvent(this.mob, this.doorPos).isCancelled()) {
++ this.start();
++ return;
++ }
++ // CraftBukkit end
+ this.mob.level().removeBlock(this.doorPos, false);
+ this.mob.level().levelEvent(1021, this.doorPos, 0);
+ this.mob.level().levelEvent(2001, this.doorPos, Block.getId(this.mob.level().getBlockState(this.doorPos)));
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/goal/EatBlockGoal.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/goal/EatBlockGoal.java.patch
new file mode 100644
index 0000000000..03ac06a116
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/goal/EatBlockGoal.java.patch
@@ -0,0 +1,40 @@
+--- a/net/minecraft/world/entity/ai/goal/EatBlockGoal.java
++++ b/net/minecraft/world/entity/ai/goal/EatBlockGoal.java
+@@ -11,6 +11,10 @@
+ import net.minecraft.world.level.block.state.BlockState;
+ import net.minecraft.world.level.block.state.predicate.BlockStatePredicate;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++// CraftBukkit end
++
+ public class EatBlockGoal extends Goal {
+
+ private static final int EAT_ANIMATION_TICKS = 40;
+@@ -68,19 +67,19 @@
+ if (this.eatAnimationTick == this.adjustedTickDelay(4)) {
+ BlockPos blockpos = this.mob.blockPosition();
+
+- if (EatBlockGoal.IS_TALL_GRASS.test(this.level.getBlockState(blockpos))) {
+- if (this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+- this.level.destroyBlock(blockpos, false);
++ if (EatBlockGoal.IS_TALL_GRASS.test(this.level.getBlockState(blockposition))) {
++ if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition, Blocks.AIR.defaultBlockState(), !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit
++ this.level.destroyBlock(blockposition, false);
+ }
+
+ this.mob.ate();
+ } else {
+ BlockPos blockpos1 = blockpos.below();
+
+- if (this.level.getBlockState(blockpos1).is(Blocks.GRASS_BLOCK)) {
+- if (this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+- this.level.levelEvent(2001, blockpos1, Block.getId(Blocks.GRASS_BLOCK.defaultBlockState()));
+- this.level.setBlock(blockpos1, Blocks.DIRT.defaultBlockState(), 2);
++ if (this.level.getBlockState(blockposition1).is(Blocks.GRASS_BLOCK)) {
++ if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.AIR.defaultBlockState(), !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit
++ this.level.levelEvent(2001, blockposition1, Block.getId(Blocks.GRASS_BLOCK.defaultBlockState()));
++ this.level.setBlock(blockposition1, Blocks.DIRT.defaultBlockState(), 2);
+ }
+
+ this.mob.ate();
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java.patch
new file mode 100644
index 0000000000..d686adb2a5
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java.patch
@@ -0,0 +1,30 @@
+--- a/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java
++++ b/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java
+@@ -13,6 +13,11 @@
+ import net.minecraft.world.level.block.state.BlockState;
+ import net.minecraft.world.level.pathfinder.BlockPathTypes;
+ import net.minecraft.world.level.pathfinder.WalkNodeEvaluator;
++// CraftBukkit start
++import org.bukkit.Location;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityTeleportEvent;
++// CraftBukkit end
+
+ public class FollowOwnerGoal extends Goal {
+
+@@ -127,7 +127,14 @@
+ } else if (!this.canTeleportTo(new BlockPos(i, j, k))) {
+ return false;
+ } else {
+- this.tamable.moveTo((double) i + 0.5D, (double) j, (double) k + 0.5D, this.tamable.getYRot(), this.tamable.getXRot());
++ // CraftBukkit start
++ EntityTeleportEvent event = CraftEventFactory.callEntityTeleportEvent(this.tamable, (double) x + 0.5D, (double) y, (double) z + 0.5D);
++ if (event.isCancelled()) {
++ return false;
++ }
++ Location to = event.getTo();
++ this.tamable.moveTo(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch());
++ // CraftBukkit end
+ this.navigation.stop();
+ return true;
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java.patch
new file mode 100644
index 0000000000..93948419de
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java.patch
@@ -0,0 +1,29 @@
+--- a/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java
++++ b/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java
+@@ -20,6 +20,10 @@
+ import net.minecraft.world.level.chunk.ChunkAccess;
+ import net.minecraft.world.level.chunk.ChunkStatus;
+ import net.minecraft.world.phys.Vec3;
++// CraftBukkit start
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++// CraftBukkit end
+
+ public class RemoveBlockGoal extends MoveToBlockGoal {
+
+@@ -100,8 +100,13 @@
+ }
+
+ if (this.ticksSinceReachedGoal > 60) {
+- level.removeBlock(blockpos1, false);
+- if (!level.isClientSide) {
++ // CraftBukkit start - Step on eggs
++ if (!CraftEventFactory.callEntityInteractEvent(this.removerMob, CraftBlock.at(world, blockposition1))) {
++ return;
++ }
++ // CraftBukkit end
++ world.removeBlock(blockposition1, false);
++ if (!world.isClientSide) {
+ for (int i = 0; i < 20; ++i) {
+ d0 = randomsource.nextGaussian() * 0.02D;
+ double d1 = randomsource.nextGaussian() * 0.02D;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java.patch
new file mode 100644
index 0000000000..ec6c6ee0ba
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java.patch
@@ -0,0 +1,24 @@
+--- a/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java
++++ b/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java
+@@ -6,6 +6,10 @@
+ import net.minecraft.world.entity.animal.horse.AbstractHorse;
+ import net.minecraft.world.entity.player.Player;
+ import net.minecraft.world.phys.Vec3;
++// CraftBukkit start
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++// CraftBukkit end
+
+ public class RunAroundLikeCrazyGoal extends Goal {
+
+@@ -67,8 +67,8 @@
+ int i = this.horse.getTemper();
+ int j = this.horse.getMaxTemper();
+
+- if (j > 0 && this.horse.getRandom().nextInt(j) < i) {
+- this.horse.tameWithName(player);
++ if (j > 0 && this.horse.getRandom().nextInt(j) < i && !CraftEventFactory.callEntityTameEvent(this.horse, ((CraftHumanEntity) this.horse.getBukkitEntity().getPassenger()).getHandle()).isCancelled()) { // CraftBukkit - fire EntityTameEvent
++ this.horse.tameWithName(entityhuman);
+ return;
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/goal/SitWhenOrderedToGoal.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/goal/SitWhenOrderedToGoal.java.patch
new file mode 100644
index 0000000000..c9bfae21ad
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/goal/SitWhenOrderedToGoal.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/ai/goal/SitWhenOrderedToGoal.java
++++ b/net/minecraft/world/entity/ai/goal/SitWhenOrderedToGoal.java
+@@ -24,7 +22,7 @@
+ @Override
+ public boolean canUse() {
+ if (!this.mob.isTame()) {
+- return false;
++ return this.mob.isOrderedToSit() && this.mob.getTarget() == null; // CraftBukkit - Allow sitting for wild animals
+ } else if (this.mob.isInWaterOrBubble()) {
+ return false;
+ } else if (!this.mob.onGround()) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/goal/TemptGoal.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/goal/TemptGoal.java.patch
new file mode 100644
index 0000000000..7fd5e8a4ff
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/goal/TemptGoal.java.patch
@@ -0,0 +1,40 @@
+--- a/net/minecraft/world/entity/ai/goal/TemptGoal.java
++++ b/net/minecraft/world/entity/ai/goal/TemptGoal.java
+@@ -8,6 +7,12 @@
+ import net.minecraft.world.entity.ai.targeting.TargetingConditions;
+ import net.minecraft.world.entity.player.Player;
+ import net.minecraft.world.item.crafting.Ingredient;
++// CraftBukkit start
++import org.bukkit.craftbukkit.entity.CraftLivingEntity;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityTargetEvent;
++import org.bukkit.event.entity.EntityTargetLivingEntityEvent;
++// CraftBukkit end
+
+ public class TemptGoal extends Goal {
+
+@@ -21,7 +26,7 @@
+ private double pRotX;
+ private double pRotY;
+ @Nullable
+- protected Player player;
++ protected LivingEntity player; // CraftBukkit
+ private int calmDown;
+ private boolean isRunning;
+ private final Ingredient items;
+@@ -44,6 +48,15 @@
+ return false;
+ } else {
+ this.player = this.mob.level().getNearestPlayer(this.targetingConditions, this.mob);
++ // CraftBukkit start
++ if (this.player != null) {
++ EntityTargetLivingEntityEvent event = CraftEventFactory.callEntityTargetLivingEvent(this.mob, this.player, EntityTargetEvent.TargetReason.TEMPT);
++ if (event.isCancelled()) {
++ return false;
++ }
++ this.player = (event.getTarget() == null) ? null : ((CraftLivingEntity) event.getTarget()).getHandle();
++ }
++ // CraftBukkit end
+ return this.player != null;
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/sensing/TemptingSensor.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/sensing/TemptingSensor.java.patch
new file mode 100644
index 0000000000..26a9813353
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/sensing/TemptingSensor.java.patch
@@ -0,0 +1,35 @@
+--- a/net/minecraft/world/entity/ai/sensing/TemptingSensor.java
++++ b/net/minecraft/world/entity/ai/sensing/TemptingSensor.java
+@@ -17,6 +17,13 @@
+ import net.minecraft.world.entity.player.Player;
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.item.crafting.Ingredient;
++// CraftBukkit start
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.entity.HumanEntity;
++import org.bukkit.event.entity.EntityTargetEvent;
++import org.bukkit.event.entity.EntityTargetLivingEntityEvent;
++// CraftBukkit end
+
+ public class TemptingSensor extends Sensor<PathfinderMob> {
+
+@@ -45,7 +51,17 @@
+ if (!list.isEmpty()) {
+ Player player = (Player) list.get(0);
+
+- brain.setMemory(MemoryModuleType.TEMPTING_PLAYER, (Object) player);
++ // CraftBukkit start
++ EntityTargetLivingEntityEvent event = CraftEventFactory.callEntityTargetLivingEvent(entity, entityhuman, EntityTargetEvent.TargetReason.TEMPT);
++ if (event.isCancelled()) {
++ return;
++ }
++ if (event.getTarget() instanceof HumanEntity) {
++ behaviorcontroller.setMemory(MemoryModuleType.TEMPTING_PLAYER, ((CraftHumanEntity) event.getTarget()).getHandle());
++ } else {
++ behaviorcontroller.eraseMemory(MemoryModuleType.TEMPTING_PLAYER);
++ }
++ // CraftBukkit end
+ } else {
+ brain.eraseMemory(MemoryModuleType.TEMPTING_PLAYER);
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/village/VillageSiege.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/village/VillageSiege.java.patch
new file mode 100644
index 0000000000..95afc35501
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ai/village/VillageSiege.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/world/entity/ai/village/VillageSiege.java
++++ b/net/minecraft/world/entity/ai/village/VillageSiege.java
+@@ -122,8 +121,8 @@
+ return;
+ }
+
+- zombie.moveTo(vec3.x, vec3.y, vec3.z, serverlevel.random.nextFloat() * 360.0F, 0.0F);
+- serverlevel.addFreshEntityWithPassengers(zombie);
++ entityzombie.moveTo(vec3d.x, vec3d.y, vec3d.z, level.random.nextFloat() * 360.0F, 0.0F);
++ level.addFreshEntityWithPassengers(entityzombie, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.VILLAGE_INVASION); // CraftBukkit
+ }
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ambient/Bat.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ambient/Bat.java.patch
new file mode 100644
index 0000000000..7419cd3d85
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/ambient/Bat.java.patch
@@ -0,0 +1,46 @@
+--- a/net/minecraft/world/entity/ambient/Bat.java
++++ b/net/minecraft/world/entity/ambient/Bat.java
+@@ -28,6 +28,9 @@
+ import net.minecraft.world.level.LevelAccessor;
+ import net.minecraft.world.level.block.state.BlockState;
+ import net.minecraft.world.phys.Vec3;
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++// CraftBukkit end
+
+ public class Bat extends AmbientCreature {
+
+@@ -155,13 +146,13 @@
+ this.yHeadRot = (float) this.random.nextInt(360);
+ }
+
+- if (this.level().getNearestPlayer(Bat.BAT_RESTING_TARGETING, this) != null) {
++ if (this.level().getNearestPlayer(Bat.BAT_RESTING_TARGETING, this) != null && CraftEventFactory.handleBatToggleSleepEvent(this, true)) { // CraftBukkit - Call BatToggleSleepEvent
+ this.setResting(false);
+ if (!flag) {
+ this.level().levelEvent((Player) null, 1025, blockpos, 0);
+ }
+ }
+- } else {
++ } else if (CraftEventFactory.handleBatToggleSleepEvent(this, true)) { // CraftBukkit - Call BatToggleSleepEvent
+ this.setResting(false);
+ if (!flag) {
+ this.level().levelEvent((Player) null, 1025, blockpos, 0);
+@@ -188,7 +179,7 @@
+
+ this.zza = 0.5F;
+ this.setYRot(this.getYRot() + f1);
+- if (this.random.nextInt(100) == 0 && this.level().getBlockState(blockpos1).isRedstoneConductor(this.level(), blockpos1)) {
++ if (this.random.nextInt(100) == 0 && this.level().getBlockState(blockposition1).isRedstoneConductor(this.level(), blockposition1) && CraftEventFactory.handleBatToggleSleepEvent(this, false)) { // CraftBukkit - Call BatToggleSleepEvent
+ this.setResting(true);
+ }
+ }
+@@ -217,7 +204,7 @@
+ if (this.isInvulnerableTo(damagesource)) {
+ return false;
+ } else {
+- if (!this.level().isClientSide && this.isResting()) {
++ if (!this.level().isClientSide && this.isResting() && CraftEventFactory.handleBatToggleSleepEvent(this, true)) { // CraftBukkit - Call BatToggleSleepEvent
+ this.setResting(false);
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Animal.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Animal.java.patch
new file mode 100644
index 0000000000..d7c2f54549
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Animal.java.patch
@@ -0,0 +1,115 @@
+--- a/net/minecraft/world/entity/animal/Animal.java
++++ b/net/minecraft/world/entity/animal/Animal.java
+@@ -29,13 +29,19 @@
+ import net.minecraft.world.level.LevelReader;
+ import net.minecraft.world.level.block.Blocks;
+ import net.minecraft.world.level.pathfinder.BlockPathTypes;
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityBreedEvent;
++import org.bukkit.event.entity.EntityEnterLoveModeEvent;
++// CraftBukkit end
+
+ public abstract class Animal extends AgeableMob {
+
+ protected static final int PARENT_AGE_AFTER_BREEDING = 6000;
+ private int inLove;
+ @Nullable
+- private UUID loveCause;
++ public UUID loveCause;
++ public ItemStack breedItem; // CraftBukkit - Add breedItem variable
+
+ protected Animal(EntityType<? extends Animal> entitytype, Level level) {
+ super(entitytype, level);
+@@ -80,8 +83,13 @@
+ if (this.isInvulnerableTo(damagesource)) {
+ return false;
+ } else {
++ // CraftBukkit start
++ boolean result = super.hurt(source, amount);
++ if (result) {
+ this.inLove = 0;
+- return super.hurt(damagesource, f);
++ }
++ return result;
++ // CraftBukkit end
+ }
+ }
+
+@@ -182,10 +183,17 @@
+ }
+
+ public void setInLove(@Nullable Player player) {
+- this.inLove = 600;
++ // CraftBukkit start
++ EntityEnterLoveModeEvent entityEnterLoveModeEvent = CraftEventFactory.callEntityEnterLoveModeEvent(player, this, 600);
++ if (entityEnterLoveModeEvent.isCancelled()) {
++ return;
++ }
++ this.inLove = entityEnterLoveModeEvent.getTicksInLove();
++ // CraftBukkit end
+ if (player != null) {
+ this.loveCause = player.getUUID();
+ }
++ this.breedItem = player.getInventory().getSelected(); // CraftBukkit
+
+ this.level().broadcastEntityEvent(this, (byte) 18);
+ }
+@@ -224,15 +232,32 @@
+ public void spawnChildFromBreeding(ServerLevel serverlevel, Animal animal) {
+ AgeableMob ageablemob = this.getBreedOffspring(serverlevel, animal);
+
+- if (ageablemob != null) {
+- ageablemob.setBaby(true);
+- ageablemob.moveTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F);
+- this.finalizeSpawnChildFromBreeding(serverlevel, animal, ageablemob);
+- serverlevel.addFreshEntityWithPassengers(ageablemob);
++ if (entityageable != null) {
++ entityageable.setBaby(true);
++ entityageable.moveTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F);
++ // CraftBukkit start - call EntityBreedEvent
++ ServerPlayer breeder = Optional.ofNullable(this.getLoveCause()).or(() -> {
++ return Optional.ofNullable(mate.getLoveCause());
++ }).orElse(null);
++ int experience = this.getRandom().nextInt(7) + 1;
++ EntityBreedEvent entityBreedEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreedEvent(entityageable, this, mate, breeder, this.breedItem, experience);
++ if (entityBreedEvent.isCancelled()) {
++ return;
++ }
++ experience = entityBreedEvent.getExperience();
++ this.finalizeSpawnChildFromBreeding(level, mate, entityageable, experience);
++ level.addFreshEntityWithPassengers(entityageable, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING);
++ // CraftBukkit end
+ }
+ }
+
+- public void finalizeSpawnChildFromBreeding(ServerLevel serverlevel, Animal animal, @Nullable AgeableMob ageablemob) {
++ public void finalizeSpawnChildFromBreeding(ServerLevel level, Animal animal, @Nullable AgeableMob baby) {
++ // CraftBukkit start
++ this.finalizeSpawnChildFromBreeding(level, animal, baby, this.getRandom().nextInt(7) + 1);
++ }
++
++ public void finalizeSpawnChildFromBreeding(ServerLevel worldserver, Animal entityanimal, @Nullable AgeableMob entityageable, int experience) {
++ // CraftBukkit end
+ Optional.ofNullable(this.getLoveCause()).or(() -> {
+ return Optional.ofNullable(animal.getLoveCause());
+ }).ifPresent((serverplayer) -> {
+@@ -242,10 +267,14 @@
+ this.setAge(6000);
+ animal.setAge(6000);
+ this.resetLove();
+- animal.resetLove();
+- serverlevel.broadcastEntityEvent(this, (byte) 18);
+- if (serverlevel.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
+- serverlevel.addFreshEntity(new ExperienceOrb(serverlevel, this.getX(), this.getY(), this.getZ(), this.getRandom().nextInt(7) + 1));
++ entityanimal.resetLove();
++ worldserver.broadcastEntityEvent(this, (byte) 18);
++ if (worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
++ // CraftBukkit start - use event experience
++ if (experience > 0) {
++ worldserver.addFreshEntity(new ExperienceOrb(worldserver, this.getX(), this.getY(), this.getZ(), experience));
++ }
++ // CraftBukkit end
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Bee.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Bee.java.patch
new file mode 100644
index 0000000000..3b5f28a11a
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Bee.java.patch
@@ -0,0 +1,115 @@
+--- a/net/minecraft/world/entity/animal/Bee.java
++++ b/net/minecraft/world/entity/animal/Bee.java
+@@ -89,6 +89,11 @@
+ import net.minecraft.world.level.pathfinder.BlockPathTypes;
+ import net.minecraft.world.level.pathfinder.Path;
+ import net.minecraft.world.phys.Vec3;
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityPotionEffectEvent;
++import org.bukkit.event.entity.EntityTargetEvent;
++// CraftBukkit end
+
+ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
+
+@@ -189,15 +191,21 @@
+ }
+
+ @Override
++ public void addAdditionalSaveData(CompoundTag compound) {
++ // CraftBukkit start - selectively save data
++ addAdditionalSaveData(compound, true);
++ }
++
+ @Override
+- public void addAdditionalSaveData(CompoundTag compoundtag) {
+- super.addAdditionalSaveData(compoundtag);
+- if (this.hasHive()) {
+- compoundtag.put("HivePos", NbtUtils.writeBlockPos(this.getHivePos()));
++ public void addAdditionalSaveData(CompoundTag nbttagcompound, boolean includeAll) {
++ // CraftBukkit end
++ super.addAdditionalSaveData(nbttagcompound);
++ if (includeAll && this.hasHive()) { // CraftBukkit - selectively save hive
++ nbttagcompound.put("HivePos", NbtUtils.writeBlockPos(this.getHivePos()));
+ }
+
+- if (this.hasSavedFlowerPos()) {
+- compoundtag.put("FlowerPos", NbtUtils.writeBlockPos(this.getSavedFlowerPos()));
++ if (includeAll && this.hasSavedFlowerPos()) { // CraftBukkit - selectively save flower
++ nbttagcompound.put("FlowerPos", NbtUtils.writeBlockPos(this.getSavedFlowerPos()));
+ }
+
+ compoundtag.putBoolean("HasNectar", this.hasNectar());
+@@ -248,7 +254,7 @@
+ }
+
+ if (b0 > 0) {
+- ((LivingEntity) entity).addEffect(new MobEffectInstance(MobEffects.POISON, b0 * 20, 0), this);
++ ((LivingEntity) entity).addEffect(new MobEffectInstance(MobEffects.POISON, b0 * 20, 0), this, EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+ }
+ }
+
+@@ -670,11 +652,14 @@
+ if (this.isInvulnerableTo(damagesource)) {
+ return false;
+ } else {
+- if (!this.level().isClientSide) {
++ // CraftBukkit start - Only stop pollinating if entity was damaged
++ boolean result = super.hurt(source, amount);
++ if (result && !this.level().isClientSide) {
++ // CraftBukkit end
+ this.beePollinateGoal.stopPollinating();
+ }
+
+- return super.hurt(damagesource, f);
++ return result; // CraftBukkit
+ }
+ }
+
+@@ -1048,7 +1014,7 @@
+
+ BeeGoToHiveGoal() {
+ super();
+- this.travellingTicks = Bee.this.level().random.nextInt(10);
++ this.travellingTicks = Bee.this.random.nextInt(10); // CraftBukkit - SPIGOT-7495: Give Bees another chance and let them use their own random, avoid concurrency issues
+ this.blacklistedTargets = Lists.newArrayList();
+ this.setFlags(EnumSet.of(Goal.Flag.MOVE));
+ }
+@@ -1170,8 +1131,8 @@
+
+ BeeGoToKnownFlowerGoal() {
+ super();
+- this.travellingTicks = Bee.this.level().random.nextInt(10);
+- this.setFlags(EnumSet.of(Goal.Flag.MOVE));
++ this.travellingTicks = Bee.this.random.nextInt(10); // CraftBukkit - SPIGOT-7495: Give Bees another chance and let them use their own random, avoid concurrency issues
++ this.setFlags(EnumSet.of(Goal.Type.MOVE));
+ }
+
+ @Override
+@@ -1278,9 +1231,9 @@
+ }
+ }
+
+- if (blockstate1 != null) {
+- Bee.this.level().levelEvent(2005, blockpos, 0);
+- Bee.this.level().setBlockAndUpdate(blockpos, blockstate1);
++ if (iblockdata1 != null && CraftEventFactory.callEntityChangeBlockEvent(Bee.this, blockposition, iblockdata1)) { // CraftBukkit
++ Bee.this.level().levelEvent(2005, blockposition, 0);
++ Bee.this.level().setBlockAndUpdate(blockposition, iblockdata1);
+ Bee.this.incrementNumCropsGrownSincePollination();
+ }
+ }
+@@ -1353,10 +1302,9 @@
+ }
+
+ @Override
+- @Override
+- protected void alertOther(Mob mob, LivingEntity livingentity) {
+- if (mob instanceof Bee && this.mob.hasLineOfSight(livingentity)) {
+- mob.setTarget(livingentity);
++ protected void alertOther(Mob mob, LivingEntity target) {
++ if (mob instanceof Bee && this.mob.hasLineOfSight(target)) {
++ mob.setTarget(target, EntityTargetEvent.TargetReason.TARGET_ATTACKED_ENTITY, true); // CraftBukkit - reason
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Bucketable.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Bucketable.java.patch
new file mode 100644
index 0000000000..7a07f25b49
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Bucketable.java.patch
@@ -0,0 +1,41 @@
+--- a/net/minecraft/world/entity/animal/Bucketable.java
++++ b/net/minecraft/world/entity/animal/Bucketable.java
+@@ -14,6 +15,10 @@
+ import net.minecraft.world.item.ItemUtils;
+ import net.minecraft.world.item.Items;
+ import net.minecraft.world.level.Level;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.player.PlayerBucketEntityEvent;
++// CraftBukkit end
+
+ public interface Bucketable {
+
+@@ -93,11 +98,23 @@
+ static <T extends LivingEntity & Bucketable> Optional<InteractionResult> bucketMobPickup(Player player, InteractionHand interactionhand, T t0) {
+ ItemStack itemstack = player.getItemInHand(interactionhand);
+
+- if (itemstack.getItem() == Items.WATER_BUCKET && t0.isAlive()) {
+- t0.playSound(((Bucketable) t0).getPickupSound(), 1.0F, 1.0F);
+- ItemStack itemstack1 = ((Bucketable) t0).getBucketItemStack();
++ if (itemstack.getItem() == Items.WATER_BUCKET && entity.isAlive()) {
++ // CraftBukkit start
++ // t0.playSound(((Bucketable) t0).getPickupSound(), 1.0F, 1.0F); // CraftBukkit - moved down
++ ItemStack itemstack1 = ((Bucketable) entity).getBucketItemStack();
+
+- ((Bucketable) t0).saveToBucketTag(itemstack1);
++ ((Bucketable) entity).saveToBucketTag(itemstack1);
++
++ PlayerBucketEntityEvent playerBucketFishEvent = CraftEventFactory.callPlayerFishBucketEvent(entity, player, itemstack, itemstack1, hand);
++ itemstack1 = CraftItemStack.asNMSCopy(playerBucketFishEvent.getEntityBucket());
++ if (playerBucketFishEvent.isCancelled()) {
++ ((ServerPlayer) player).containerMenu.sendAllDataToRemote(); // We need to update inventory to resync client's bucket
++ ((ServerPlayer) player).connection.send(new ClientboundAddEntityPacket(entity)); // We need to play out these packets as the client assumes the fish is gone
++ entity.getEntityData().refresh((ServerPlayer) player); // Need to send data such as the display name to client
++ return Optional.of(InteractionResult.FAIL);
++ }
++ entity.playSound(((Bucketable) entity).getPickupSound(), 1.0F, 1.0F);
++ // CraftBukkit end
+ ItemStack itemstack2 = ItemUtils.createFilledResult(itemstack, player, itemstack1, false);
+
+ player.setItemInHand(interactionhand, itemstack2);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Cat.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Cat.java.patch
new file mode 100644
index 0000000000..b0c0ac3937
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Cat.java.patch
@@ -0,0 +1,39 @@
+--- a/net/minecraft/world/entity/animal/Cat.java
++++ b/net/minecraft/world/entity/animal/Cat.java
+@@ -430,8 +412,8 @@
+ }
+ }
+ } else if (this.isFood(itemstack)) {
+- this.usePlayerItem(player, interactionhand, itemstack);
+- if (this.random.nextInt(3) == 0) {
++ this.usePlayerItem(player, hand, itemstack);
++ if (this.random.nextInt(3) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit
+ this.tame(player);
+ this.setOrderedToSit(true);
+ this.level().broadcastEntityEvent(this, (byte) 7);
+@@ -499,7 +475,7 @@
+ private static class CatTemptGoal extends TemptGoal {
+
+ @Nullable
+- private Player selectedPlayer;
++ private LivingEntity selectedPlayer; // CraftBukkit
+ private final Cat cat;
+
+ public CatTemptGoal(Cat cat, double d0, Ingredient ingredient, boolean flag) {
+@@ -647,7 +616,15 @@
+ while (iterator.hasNext()) {
+ ItemStack itemstack = (ItemStack) iterator.next();
+
+- this.cat.level().addFreshEntity(new ItemEntity(this.cat.level(), (double) blockpos_mutableblockpos.getX() - (double) Mth.sin(this.cat.yBodyRot * 0.017453292F), (double) blockpos_mutableblockpos.getY(), (double) blockpos_mutableblockpos.getZ() + (double) Mth.cos(this.cat.yBodyRot * 0.017453292F), itemstack));
++ // CraftBukkit start
++ ItemEntity entityitem = new ItemEntity(this.cat.level(), (double) blockposition_mutableblockposition.getX() - (double) Mth.sin(this.cat.yBodyRot * 0.017453292F), (double) blockposition_mutableblockposition.getY(), (double) blockposition_mutableblockposition.getZ() + (double) Mth.cos(this.cat.yBodyRot * 0.017453292F), itemstack);
++ org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(this.cat.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity());
++ entityitem.level().getCraftServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ continue;
++ }
++ this.cat.level().addFreshEntity(entityitem);
++ // CraftBukkit end
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Chicken.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Chicken.java.patch
new file mode 100644
index 0000000000..fbe787042f
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Chicken.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/world/entity/animal/Chicken.java
++++ b/net/minecraft/world/entity/animal/Chicken.java
+@@ -100,7 +97,9 @@
+ this.flap += this.flapping * 2.0F;
+ if (!this.level().isClientSide && this.isAlive() && !this.isBaby() && !this.isChickenJockey() && --this.eggTime <= 0) {
+ this.playSound(SoundEvents.CHICKEN_EGG, 1.0F, (this.random.nextFloat() - this.random.nextFloat()) * 0.2F + 1.0F);
+- this.spawnAtLocation((ItemLike) Items.EGG);
++ this.forceDrops = true; // CraftBukkit
++ this.spawnAtLocation((IMaterial) Items.EGG);
++ this.forceDrops = false; // CraftBukkit
+ this.gameEvent(GameEvent.ENTITY_PLACE);
+ this.eggTime = this.random.nextInt(6000) + 6000;
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Cow.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Cow.java.patch
new file mode 100644
index 0000000000..135f6878b5
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Cow.java.patch
@@ -0,0 +1,33 @@
+--- a/net/minecraft/world/entity/animal/Cow.java
++++ b/net/minecraft/world/entity/animal/Cow.java
+@@ -33,6 +33,12 @@
+ import net.minecraft.world.level.block.state.BlockState;
+ import org.joml.Vector3f;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.player.PlayerBucketFillEvent;
++// CraftBukkit end
++
+ public class Cow extends Animal {
+
+ public Cow(EntityType<? extends Cow> entitytype, Level level) {
+@@ -92,8 +91,16 @@
+ ItemStack itemstack = player.getItemInHand(interactionhand);
+
+ if (itemstack.is(Items.BUCKET) && !this.isBaby()) {
++ // CraftBukkit start - Got milk?
++ PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) player.level(), player, this.blockPosition(), this.blockPosition(), null, itemstack, Items.MILK_BUCKET, hand);
++
++ if (event.isCancelled()) {
++ return InteractionResult.PASS;
++ }
++ // CraftBukkit end
++
+ player.playSound(SoundEvents.COW_MILK, 1.0F, 1.0F);
+- ItemStack itemstack1 = ItemUtils.createFilledResult(itemstack, player, Items.MILK_BUCKET.getDefaultInstance());
++ ItemStack itemstack1 = ItemUtils.createFilledResult(itemstack, player, CraftItemStack.asNMSCopy(event.getItemStack())); // CraftBukkit
+
+ player.setItemInHand(interactionhand, itemstack1);
+ return InteractionResult.sidedSuccess(this.level().isClientSide);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Dolphin.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Dolphin.java.patch
new file mode 100644
index 0000000000..c2a7096092
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Dolphin.java.patch
@@ -0,0 +1,64 @@
+--- a/net/minecraft/world/entity/animal/Dolphin.java
++++ b/net/minecraft/world/entity/animal/Dolphin.java
+@@ -60,9 +60,19 @@
+ import net.minecraft.world.level.ServerLevelAccessor;
+ import net.minecraft.world.level.pathfinder.PathComputationType;
+ import net.minecraft.world.phys.Vec3;
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityPotionEffectEvent;
++// CraftBukkit end
+
+ public class Dolphin extends WaterAnimal {
+
++ // CraftBukkit start - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
++ @Override
++ public int getDefaultMaxAirSupply() {
++ return TOTAL_AIR_SUPPLY;
++ }
++ // CraftBukkit end
+ private static final EntityDataAccessor<BlockPos> TREASURE_POS = SynchedEntityData.defineId(Dolphin.class, EntityDataSerializers.BLOCK_POS);
+ private static final EntityDataAccessor<Boolean> GOT_FISH = SynchedEntityData.defineId(Dolphin.class, EntityDataSerializers.BOOLEAN);
+ private static final EntityDataAccessor<Integer> MOISTNESS_LEVEL = SynchedEntityData.defineId(Dolphin.class, EntityDataSerializers.INT);
+@@ -194,7 +195,7 @@
+ @Override
+ @Override
+ public int getMaxAirSupply() {
+- return 4800;
++ return maxAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
+ }
+
+ @Override
+@@ -242,7 +236,13 @@
+ ItemStack itemstack = itementity.getItem();
+
+ if (this.canHoldItem(itemstack)) {
+- this.onItemPickup(itementity);
++ // CraftBukkit start - call EntityPickupItemEvent
++ if (CraftEventFactory.callEntityPickupItemEvent(this, itemEntity, 0, false).isCancelled()) {
++ return;
++ }
++ itemstack = itemEntity.getItem(); // CraftBukkit- update ItemStack from event
++ // CraftBukkit start
++ this.onItemPickup(itemEntity);
+ this.setItemSlot(EquipmentSlot.MAINHAND, itemstack);
+ this.setGuaranteedDrop(EquipmentSlot.MAINHAND);
+ this.take(itementity, itemstack.getCount());
+@@ -519,7 +500,7 @@
+ @Override
+ @Override
+ public void start() {
+- this.player.addEffect(new MobEffectInstance(MobEffects.DOLPHINS_GRACE, 100), this.dolphin);
++ this.player.addEffect(new MobEffectInstance(MobEffects.DOLPHINS_GRACE, 100), this.dolphin, EntityPotionEffectEvent.Cause.DOLPHIN); // CraftBukkit
+ }
+
+ @Override
+@@ -540,7 +519,7 @@
+ }
+
+ if (this.player.isSwimming() && this.player.level().random.nextInt(6) == 0) {
+- this.player.addEffect(new MobEffectInstance(MobEffects.DOLPHINS_GRACE, 100), this.dolphin);
++ this.player.addEffect(new MobEffectInstance(MobEffects.DOLPHINS_GRACE, 100), this.dolphin, EntityPotionEffectEvent.Cause.DOLPHIN); // CraftBukkit
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Fox.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Fox.java.patch
new file mode 100644
index 0000000000..5d38bfb8b9
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Fox.java.patch
@@ -0,0 +1,62 @@
+--- a/net/minecraft/world/entity/animal/Fox.java
++++ b/net/minecraft/world/entity/animal/Fox.java
+@@ -542,7 +523,8 @@
+ protected void pickUpItem(ItemEntity itementity) {
+ ItemStack itemstack = itementity.getItem();
+
+- if (this.canHoldItem(itemstack)) {
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(this, itemEntity, itemstack.getCount() - 1, !this.canHoldItem(itemstack)).isCancelled()) { // CraftBukkit - call EntityPickupItemEvent
++ itemstack = itemEntity.getItem(); // CraftBukkit - update ItemStack from event
+ int i = itemstack.getCount();
+
+ if (i > 1) {
+@@ -928,6 +884,16 @@
+ if (serverplayer1 != null && serverplayer != serverplayer1) {
+ fox.addTrustedUUID(serverplayer1.getUUID());
+ }
++ // CraftBukkit start - call EntityBreedEvent
++ entityfox.setAge(-24000);
++ entityfox.moveTo(this.animal.getX(), this.animal.getY(), this.animal.getZ(), 0.0F, 0.0F);
++ int experience = this.animal.getRandom().nextInt(7) + 1;
++ org.bukkit.event.entity.EntityBreedEvent entityBreedEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreedEvent(entityfox, animal, partner, entityplayer, this.animal.breedItem, experience);
++ if (entityBreedEvent.isCancelled()) {
++ return;
++ }
++ experience = entityBreedEvent.getExperience();
++ // CraftBukkit end
+
+ if (serverplayer2 != null) {
+ serverplayer2.awardStat(Stats.ANIMALS_BRED);
+@@ -938,12 +904,14 @@
+ this.partner.setAge(6000);
+ this.animal.resetLove();
+ this.partner.resetLove();
+- fox.setAge(-24000);
+- fox.moveTo(this.animal.getX(), this.animal.getY(), this.animal.getZ(), 0.0F, 0.0F);
+- serverlevel.addFreshEntityWithPassengers(fox);
++ worldserver.addFreshEntityWithPassengers(entityfox, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING); // CraftBukkit - added SpawnReason
+ this.level.broadcastEntityEvent(this.animal, (byte) 18);
+ if (this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
+- this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), this.animal.getRandom().nextInt(7) + 1));
++ // CraftBukkit start - use event experience
++ if (experience > 0) {
++ this.level.addFreshEntity(new ExperienceOrb(this.level, this.animal.getX(), this.animal.getY(), this.animal.getZ(), experience));
++ }
++ // CraftBukkit end
+ }
+
+ }
+@@ -1367,7 +1306,12 @@
+ private void pickSweetBerries(BlockState blockstate) {
+ int i = (Integer) blockstate.getValue(SweetBerryBushBlock.AGE);
+
+- blockstate.setValue(SweetBerryBushBlock.AGE, 1);
++ state.setValue(SweetBerryBushBlock.AGE, 1);
++ // CraftBukkit start - call EntityChangeBlockEvent
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(Fox.this, this.blockPos, state.setValue(SweetBerryBushBlock.AGE, 1))) {
++ return;
++ }
++ // CraftBukkit end
+ int j = 1 + Fox.this.level().random.nextInt(2) + (i == 3 ? 1 : 0);
+ ItemStack itemstack = Fox.this.getItemBySlot(EquipmentSlot.MAINHAND);
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/IronGolem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/IronGolem.java.patch
new file mode 100644
index 0000000000..d6de83afda
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/IronGolem.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/world/entity/animal/IronGolem.java
++++ b/net/minecraft/world/entity/animal/IronGolem.java
+@@ -105,8 +101,8 @@
+ @Override
+ @Override
+ protected void doPush(Entity entity) {
+- if (entity instanceof Enemy && !(entity instanceof Creeper) && this.getRandom().nextInt(20) == 0) {
+- this.setTarget((LivingEntity) entity);
++ if (entity instanceof IMonster && !(entity instanceof Creeper) && this.getRandom().nextInt(20) == 0) {
++ this.setTarget((LivingEntity) entity, org.bukkit.event.entity.EntityTargetLivingEntityEvent.TargetReason.COLLISION, true); // CraftBukkit - set reason
+ }
+
+ super.doPush(entity);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/MushroomCow.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/MushroomCow.java.patch
new file mode 100644
index 0000000000..25a849b38f
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/MushroomCow.java.patch
@@ -0,0 +1,73 @@
+--- a/net/minecraft/world/entity/animal/MushroomCow.java
++++ b/net/minecraft/world/entity/animal/MushroomCow.java
+@@ -42,6 +42,13 @@
+ import net.minecraft.world.level.block.state.BlockState;
+ import net.minecraft.world.level.gameevent.GameEvent;
+
++// CraftBukkit start
++import org.bukkit.Bukkit;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityDropItemEvent;
++import org.bukkit.event.entity.EntityTransformEvent;
++// CraftBukkit end
++
+ public class MushroomCow extends Cow implements Shearable, VariantHolder<MushroomCow.MushroomType> {
+
+ private static final EntityDataAccessor<String> DATA_TYPE = SynchedEntityData.defineId(MushroomCow.class, EntityDataSerializers.STRING);
+@@ -118,6 +121,11 @@
+ this.playSound(soundevent, 1.0F, 1.0F);
+ return InteractionResult.sidedSuccess(this.level().isClientSide);
+ } else if (itemstack.is(Items.SHEARS) && this.readyForShearing()) {
++ // CraftBukkit start
++ if (!CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand)) {
++ return InteractionResult.PASS;
++ }
++ // CraftBukkit end
+ this.shear(SoundSource.PLAYERS);
+ this.gameEvent(GameEvent.SHEAR, player);
+ if (!this.level().isClientSide) {
+@@ -166,10 +173,10 @@
+
+ if (cow != null) {
+ ((ServerLevel) this.level()).sendParticles(ParticleTypes.EXPLOSION, this.getX(), this.getY(0.5D), this.getZ(), 1, 0.0D, 0.0D, 0.0D, 0.0D);
+- this.discard();
+- cow.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot());
+- cow.setHealth(this.getHealth());
+- cow.yBodyRot = this.yBodyRot;
++ // this.discard(); // CraftBukkit - moved down
++ entitycow.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot());
++ entitycow.setHealth(this.getHealth());
++ entitycow.yBodyRot = this.yBodyRot;
+ if (this.hasCustomName()) {
+ cow.setCustomName(this.getCustomName());
+ cow.setCustomNameVisible(this.isCustomNameVisible());
+@@ -179,11 +186,26 @@
+ cow.setPersistenceRequired();
+ }
+
+- cow.setInvulnerable(this.isInvulnerable());
+- this.level().addFreshEntity(cow);
++ entitycow.setInvulnerable(this.isInvulnerable());
++ // CraftBukkit start
++ if (CraftEventFactory.callEntityTransformEvent(this, entitycow, EntityTransformEvent.TransformReason.SHEARED).isCancelled()) {
++ return;
++ }
++ this.level().addFreshEntity(entitycow, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SHEARED);
+
++ this.discard(); // CraftBukkit - from above
++ // CraftBukkit end
++
+ for (int i = 0; i < 5; ++i) {
+- this.level().addFreshEntity(new ItemEntity(this.level(), this.getX(), this.getY(1.0D), this.getZ(), new ItemStack(this.getVariant().blockState.getBlock())));
++ // CraftBukkit start
++ ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY(1.0D), this.getZ(), new ItemStack(this.getVariant().blockState.getBlock()));
++ EntityDropItemEvent event = new EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity());
++ Bukkit.getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ continue;
++ }
++ this.level().addFreshEntity(entityitem);
++ // CraftBukkit end
+ }
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Ocelot.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Ocelot.java.patch
new file mode 100644
index 0000000000..8a95a5861a
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Ocelot.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/animal/Ocelot.java
++++ b/net/minecraft/world/entity/animal/Ocelot.java
+@@ -191,7 +179,7 @@
+ if ((this.temptGoal == null || this.temptGoal.isRunning()) && !this.isTrusting() && this.isFood(itemstack) && player.distanceToSqr((Entity) this) < 9.0D) {
+ this.usePlayerItem(player, interactionhand, itemstack);
+ if (!this.level().isClientSide) {
+- if (this.random.nextInt(3) == 0) {
++ if (this.random.nextInt(3) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit - added event call and isCancelled check
+ this.setTrusting(true);
+ this.spawnTrustingParticles(true);
+ this.level().broadcastEntityEvent(this, (byte) 41);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Panda.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Panda.java.patch
new file mode 100644
index 0000000000..5d027ce3c7
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Panda.java.patch
@@ -0,0 +1,39 @@
+--- a/net/minecraft/world/entity/animal/Panda.java
++++ b/net/minecraft/world/entity/animal/Panda.java
+@@ -67,6 +67,11 @@
+ import net.minecraft.world.phys.Vec3;
+ import org.joml.Vector3f;
+
++// CraftBukkit start;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityTargetEvent;
++// CraftBukkit end
++
+ public class Panda extends Animal {
+
+ private static final EntityDataAccessor<Integer> UNHAPPY_COUNTER = SynchedEntityData.defineId(Panda.class, EntityDataSerializers.INT);
+@@ -543,11 +538,10 @@
+ }
+
+ @Override
+- @Override
+- protected void pickUpItem(ItemEntity itementity) {
+- if (this.getItemBySlot(EquipmentSlot.MAINHAND).isEmpty() && Panda.PANDA_ITEMS.test(itementity)) {
+- this.onItemPickup(itementity);
+- ItemStack itemstack = itementity.getItem();
++ protected void pickUpItem(ItemEntity itemEntity) {
++ if (!CraftEventFactory.callEntityPickupItemEvent(this, itemEntity, 0, !(this.getItemBySlot(EquipmentSlot.MAINHAND).isEmpty() && Panda.PANDA_ITEMS.test(itemEntity))).isCancelled()) { // CraftBukkit
++ this.onItemPickup(itemEntity);
++ ItemStack itemstack = itemEntity.getItem();
+
+ this.setItemSlot(EquipmentSlot.MAINHAND, itemstack);
+ this.setGuaranteedDrop(EquipmentSlot.MAINHAND);
+@@ -1160,7 +1117,7 @@
+ @Override
+ protected void alertOther(Mob mob, LivingEntity livingentity) {
+ if (mob instanceof Panda && mob.isAggressive()) {
+- mob.setTarget(livingentity);
++ mob.setTarget(target, EntityTargetEvent.TargetReason.TARGET_ATTACKED_ENTITY, true); // CraftBukkit
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Parrot.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Parrot.java.patch
new file mode 100644
index 0000000000..b0d4b5f8a0
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Parrot.java.patch
@@ -0,0 +1,46 @@
+--- a/net/minecraft/world/entity/animal/Parrot.java
++++ b/net/minecraft/world/entity/animal/Parrot.java
+@@ -272,7 +263,7 @@
+ }
+
+ if (!this.level().isClientSide) {
+- if (this.random.nextInt(10) == 0) {
++ if (this.random.nextInt(10) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit
+ this.tame(player);
+ this.level().broadcastEntityEvent(this, (byte) 7);
+ } else {
+@@ -286,7 +277,7 @@
+ itemstack.shrink(1);
+ }
+
+- this.addEffect(new MobEffectInstance(MobEffects.POISON, 900));
++ this.addEffect(new MobEffectInstance(MobEffects.POISON, 900), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.FOOD); // CraftBukkit
+ if (player.isCreative() || !this.isInvulnerable()) {
+ this.hurt(this.damageSources().playerAttack(player), Float.MAX_VALUE);
+ }
+@@ -407,7 +384,7 @@
+ @Override
+ @Override
+ public boolean isPushable() {
+- return true;
++ return super.isPushable(); // CraftBukkit - collidable API
+ }
+
+ @Override
+@@ -424,11 +399,14 @@
+ if (this.isInvulnerableTo(damagesource)) {
+ return false;
+ } else {
+- if (!this.level().isClientSide) {
++ // CraftBukkit start
++ boolean result = super.hurt(source, amount);
++ if (!this.level().isClientSide && result) {
++ // CraftBukkit end
+ this.setOrderedToSit(false);
+ }
+
+- return super.hurt(damagesource, f);
++ return result; // CraftBukkit
+ }
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Pig.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Pig.java.patch
new file mode 100644
index 0000000000..b9d1eaf983
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Pig.java.patch
@@ -0,0 +1,30 @@
+--- a/net/minecraft/world/entity/animal/Pig.java
++++ b/net/minecraft/world/entity/animal/Pig.java
+@@ -51,7 +51,9 @@
+ import net.minecraft.world.phys.Vec3;
+ import org.joml.Vector3f;
+
+-public class Pig extends Animal implements ItemSteerable, Saddleable {
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++// CraftBukkit end
+
+ private static final EntityDataAccessor<Boolean> DATA_SADDLE_ID = SynchedEntityData.defineId(Pig.class, EntityDataSerializers.BOOLEAN);
+ private static final EntityDataAccessor<Integer> DATA_BOOST_TIME = SynchedEntityData.defineId(Pig.class, EntityDataSerializers.INT);
+@@ -268,8 +255,14 @@
+ zombifiedpiglin.setCustomNameVisible(this.isCustomNameVisible());
+ }
+
+- zombifiedpiglin.setPersistenceRequired();
+- serverlevel.addFreshEntity(zombifiedpiglin);
++ entitypigzombie.setPersistenceRequired();
++ // CraftBukkit start
++ if (CraftEventFactory.callPigZapEvent(this, lightning, entitypigzombie).isCancelled()) {
++ return;
++ }
++ // CraftBukkit - added a reason for spawning this creature
++ level.addFreshEntity(entitypigzombie, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING);
++ // CraftBukkit end
+ this.discard();
+ } else {
+ super.thunderHit(serverlevel, lightningbolt);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Pufferfish.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Pufferfish.java.patch
new file mode 100644
index 0000000000..3f402b8d8c
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Pufferfish.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/entity/animal/Pufferfish.java
++++ b/net/minecraft/world/entity/animal/Pufferfish.java
+@@ -152,7 +144,7 @@
+ int i = this.getPuffState();
+
+ if (mob.hurt(this.damageSources().mobAttack(this), (float) (1 + i))) {
+- mob.addEffect(new MobEffectInstance(MobEffects.POISON, 60 * i, 0), this);
++ mob.addEffect(new MobEffectInstance(MobEffects.POISON, 60 * i, 0), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+ this.playSound(SoundEvents.PUFFER_FISH_STING, 1.0F, 1.0F);
+ }
+
+@@ -168,7 +159,7 @@
+ ((ServerPlayer) player).connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.PUFFER_FISH_STING, 0.0F));
+ }
+
+- player.addEffect(new MobEffectInstance(MobEffects.POISON, 60 * i, 0), this);
++ entity.addEffect(new MobEffectInstance(MobEffects.POISON, 60 * i, 0), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Rabbit.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Rabbit.java.patch
new file mode 100644
index 0000000000..0bdf9432a9
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Rabbit.java.patch
@@ -0,0 +1,40 @@
+--- a/net/minecraft/world/entity/animal/Rabbit.java
++++ b/net/minecraft/world/entity/animal/Rabbit.java
+@@ -66,6 +66,9 @@
+ import net.minecraft.world.level.gameevent.GameEvent;
+ import net.minecraft.world.level.pathfinder.Path;
+ import net.minecraft.world.phys.Vec3;
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++// CraftBukkit end
+
+ public class Rabbit extends Animal implements VariantHolder<Rabbit.Variant> {
+
+@@ -607,12 +579,22 @@
+ int i = (Integer) blockstate.getValue(CarrotBlock.AGE);
+
+ if (i == 0) {
+- level.setBlock(blockpos, Blocks.AIR.defaultBlockState(), 2);
+- level.destroyBlock(blockpos, true, this.rabbit);
++ // CraftBukkit start
++ if (!CraftEventFactory.callEntityChangeBlockEvent(this.rabbit, blockposition, Blocks.AIR.defaultBlockState())) {
++ return;
++ }
++ // CraftBukkit end
++ world.setBlock(blockposition, Blocks.AIR.defaultBlockState(), 2);
++ world.destroyBlock(blockposition, true, this.rabbit);
+ } else {
+- level.setBlock(blockpos, (BlockState) blockstate.setValue(CarrotBlock.AGE, i - 1), 2);
+- level.gameEvent(GameEvent.BLOCK_CHANGE, blockpos, GameEvent.Context.of((Entity) this.rabbit));
+- level.levelEvent(2001, blockpos, Block.getId(blockstate));
++ // CraftBukkit start
++ if (!CraftEventFactory.callEntityChangeBlockEvent(this.rabbit, blockposition, iblockdata.setValue(CarrotBlock.AGE, i - 1))) {
++ return;
++ }
++ // CraftBukkit end
++ world.setBlock(blockposition, (IBlockData) iblockdata.setValue(CarrotBlock.AGE, i - 1), 2);
++ world.gameEvent(GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of((Entity) this.rabbit));
++ world.levelEvent(2001, blockposition, Block.getId(iblockdata));
+ }
+
+ this.rabbit.moreCarrotTicks = 40;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Sheep.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Sheep.java.patch
new file mode 100644
index 0000000000..9669ced77d
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Sheep.java.patch
@@ -0,0 +1,73 @@
+--- a/net/minecraft/world/entity/animal/Sheep.java
++++ b/net/minecraft/world/entity/animal/Sheep.java
+@@ -65,6 +64,13 @@
+ import net.minecraft.world.level.gameevent.GameEvent;
+ import net.minecraft.world.level.storage.loot.BuiltInLootTables;
+ import org.joml.Vector3f;
++import net.minecraft.world.item.DyeColor;
++import net.minecraft.world.item.DyeItem;
++import net.minecraft.world.item.Item;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.SheepRegrowWoolEvent;
++import org.bukkit.inventory.InventoryView;
++// CraftBukkit end
+
+ public class Sheep extends Animal implements Shearable {
+
+@@ -253,6 +252,11 @@
+
+ if (itemstack.is(Items.SHEARS)) {
+ if (!this.level().isClientSide && this.readyForShearing()) {
++ // CraftBukkit start
++ if (!CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand)) {
++ return InteractionResult.PASS;
++ }
++ // CraftBukkit end
+ this.shear(SoundSource.PLAYERS);
+ this.gameEvent(GameEvent.SHEAR, player);
+ itemstack.hurtAndBreak(1, player, (player1) -> {
+@@ -275,7 +278,9 @@
+ int i = 1 + this.random.nextInt(3);
+
+ for (int j = 0; j < i; ++j) {
+- ItemEntity itementity = this.spawnAtLocation((ItemLike) Sheep.ITEM_BY_DYE.get(this.getColor()), 1);
++ this.forceDrops = true; // CraftBukkit
++ ItemEntity entityitem = this.spawnAtLocation((IMaterial) Sheep.ITEM_BY_DYE.get(this.getColor()), 1);
++ this.forceDrops = false; // CraftBukkit
+
+ if (itementity != null) {
+ itementity.setDeltaMovement(itementity.getDeltaMovement().add((double) ((this.random.nextFloat() - this.random.nextFloat()) * 0.1F), (double) (this.random.nextFloat() * 0.05F), (double) ((this.random.nextFloat() - this.random.nextFloat()) * 0.1F)));
+@@ -377,6 +373,12 @@
+ @Override
+ @Override
+ public void ate() {
++ // CraftBukkit start
++ SheepRegrowWoolEvent event = new SheepRegrowWoolEvent((org.bukkit.entity.Sheep) this.getBukkitEntity());
++ this.level().getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) return;
++ // CraftBukkit end
+ super.ate();
+ this.setSheared(false);
+ if (this.isBaby()) {
+@@ -422,10 +421,18 @@
+ public boolean stillValid(Player player) {
+ return false;
+ }
++
++ // CraftBukkit start
++ @Override
++ public InventoryView getBukkitView() {
++ return null; // TODO: O.O
++ }
++ // CraftBukkit end
+ }, 2, 1);
+
+- transientcraftingcontainer.setItem(0, new ItemStack(DyeItem.byColor(dyecolor)));
+- transientcraftingcontainer.setItem(1, new ItemStack(DyeItem.byColor(dyecolor1)));
++ transientcraftingcontainer.setItem(0, new ItemStack(DyeItem.byColor(fatherColor)));
++ transientcraftingcontainer.setItem(1, new ItemStack(DyeItem.byColor(motherColor)));
++ transientcraftingcontainer.resultInventory = new ResultContainer(); // CraftBukkit - add result slot for event
+ return transientcraftingcontainer;
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/SnowGolem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/SnowGolem.java.patch
new file mode 100644
index 0000000000..344040376e
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/SnowGolem.java.patch
@@ -0,0 +1,60 @@
+--- a/net/minecraft/world/entity/animal/SnowGolem.java
++++ b/net/minecraft/world/entity/animal/SnowGolem.java
+@@ -40,6 +40,9 @@
+ import net.minecraft.world.level.block.state.BlockState;
+ import net.minecraft.world.level.gameevent.GameEvent;
+ import net.minecraft.world.phys.Vec3;
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++// CraftBukkit end
+
+ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackMob {
+
+@@ -103,7 +100,7 @@
+ super.aiStep();
+ if (!this.level().isClientSide) {
+ if (this.level().getBiome(this.blockPosition()).is(BiomeTags.SNOW_GOLEM_MELTS)) {
+- this.hurt(this.damageSources().onFire(), 1.0F);
++ this.hurt(this.damageSources().melting, 1.0F); // CraftBukkit - DamageSource.BURN -> CraftEventFactory.MELTING
+ }
+
+ if (!this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+@@ -118,9 +115,13 @@
+ int l = Mth.floor(this.getZ() + (double) ((float) (i / 2 % 2 * 2 - 1) * 0.25F));
+ BlockPos blockpos = new BlockPos(j, k, l);
+
+- if (this.level().getBlockState(blockpos).isAir() && blockstate.canSurvive(this.level(), blockpos)) {
+- this.level().setBlockAndUpdate(blockpos, blockstate);
+- this.level().gameEvent(GameEvent.BLOCK_PLACE, blockpos, GameEvent.Context.of(this, blockstate));
++ if (this.level().getBlockState(blockposition).isAir() && iblockdata.canSurvive(this.level(), blockposition)) {
++ // CraftBukkit start
++ if (!CraftEventFactory.handleBlockFormEvent(this.level(), blockposition, iblockdata, this)) {
++ continue;
++ }
++ // CraftBukkit end
++ this.level().gameEvent(GameEvent.BLOCK_PLACE, blockposition, GameEvent.Context.of(this, iblockdata));
+ }
+ }
+ }
+@@ -154,6 +152,11 @@
+ ItemStack itemstack = player.getItemInHand(interactionhand);
+
+ if (itemstack.is(Items.SHEARS) && this.readyForShearing()) {
++ // CraftBukkit start
++ if (!CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand)) {
++ return InteractionResult.PASS;
++ }
++ // CraftBukkit end
+ this.shear(SoundSource.PLAYERS);
+ this.gameEvent(GameEvent.SHEAR, player);
+ if (!this.level().isClientSide) {
+@@ -174,7 +176,9 @@
+ this.level().playSound((Player) null, (Entity) this, SoundEvents.SNOW_GOLEM_SHEAR, soundsource, 1.0F, 1.0F);
+ if (!this.level().isClientSide()) {
+ this.setPumpkin(false);
++ this.forceDrops = true; // CraftBukkit
+ this.spawnAtLocation(new ItemStack(Items.CARVED_PUMPKIN), 1.7F);
++ this.forceDrops = false; // CraftBukkit
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Turtle.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Turtle.java.patch
new file mode 100644
index 0000000000..2bcafa973e
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Turtle.java.patch
@@ -0,0 +1,45 @@
+--- a/net/minecraft/world/entity/animal/Turtle.java
++++ b/net/minecraft/world/entity/animal/Turtle.java
+@@ -330,7 +307,9 @@
+ protected void ageBoundaryReached() {
+ super.ageBoundaryReached();
+ if (!this.isBaby() && this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
++ this.forceDrops = true; // CraftBukkit
+ this.spawnAtLocation(Items.SCUTE, 1);
++ this.forceDrops = false; // CraftBukkit
+ }
+
+ }
+@@ -358,9 +335,10 @@
+ }
+
+ @Override
+- @Override
+- public void thunderHit(ServerLevel serverlevel, LightningBolt lightningbolt) {
++ public void thunderHit(ServerLevel level, LightningBolt lightning) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.entityDamage = lightning; // CraftBukkit
+ this.hurt(this.damageSources().lightningBolt(), Float.MAX_VALUE);
++ org.bukkit.craftbukkit.event.CraftEventFactory.entityDamage = null; // CraftBukkit
+ }
+
+ @Override
+@@ -525,12 +495,14 @@
+ } else if (this.turtle.layEggCounter > this.adjustedTickDelay(200)) {
+ Level level = this.turtle.level();
+
+- level.playSound((Player) null, blockpos, SoundEvents.TURTLE_LAY_EGG, SoundSource.BLOCKS, 0.3F, 0.9F + level.random.nextFloat() * 0.2F);
+- BlockPos blockpos1 = this.blockPos.above();
+- BlockState blockstate = (BlockState) Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, this.turtle.random.nextInt(4) + 1);
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this.turtle, this.blockPos.above(), (IBlockData) Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, this.turtle.random.nextInt(4) + 1))) { // CraftBukkit
++ world.playSound((Player) null, blockposition, SoundEvents.TURTLE_LAY_EGG, SoundSource.BLOCKS, 0.3F, 0.9F + world.random.nextFloat() * 0.2F);
++ BlockPos blockposition1 = this.blockPos.above();
++ IBlockData iblockdata = (IBlockData) Blocks.TURTLE_EGG.defaultBlockState().setValue(TurtleEggBlock.EGGS, this.turtle.random.nextInt(4) + 1);
+
+- level.setBlock(blockpos1, blockstate, 3);
+- level.gameEvent(GameEvent.BLOCK_PLACE, blockpos1, GameEvent.Context.of(this.turtle, blockstate));
++ world.setBlock(blockposition1, iblockdata, 3);
++ world.gameEvent(GameEvent.BLOCK_PLACE, blockposition1, GameEvent.Context.of(this.turtle, iblockdata));
++ } // CraftBukkit
+ this.turtle.setHasEgg(false);
+ this.turtle.setLayingEgg(false);
+ this.turtle.setInLoveTime(600);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Wolf.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Wolf.java.patch
new file mode 100644
index 0000000000..659203726a
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/Wolf.java.patch
@@ -0,0 +1,76 @@
+--- a/net/minecraft/world/entity/animal/Wolf.java
++++ b/net/minecraft/world/entity/animal/Wolf.java
+@@ -70,6 +70,12 @@
+ import net.minecraft.world.phys.Vec3;
+ import org.joml.Vector3f;
+
++// CraftBukkit start
++import org.bukkit.event.entity.EntityRegainHealthEvent;
++import org.bukkit.event.entity.EntityTargetEvent;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++// CraftBukkit end
++
+ public class Wolf extends TamableAnimal implements NeutralMob {
+
+ private static final EntityDataAccessor<Boolean> DATA_INTERESTED_ID = SynchedEntityData.defineId(Wolf.class, EntityDataSerializers.BOOLEAN);
+@@ -313,15 +304,19 @@
+ } else {
+ Entity entity = damagesource.getEntity();
+
+- if (!this.level().isClientSide) {
+- this.setOrderedToSit(false);
+- }
++ // CraftBukkit - move diff down
+
+ if (entity != null && !(entity instanceof Player) && !(entity instanceof AbstractArrow)) {
+ f = (f + 1.0F) / 2.0F;
+ }
+
+- return super.hurt(damagesource, f);
++ // CraftBukkit start
++ boolean result = super.hurt(source, amount);
++ if (!this.level().isClientSide && result) {
++ this.setOrderedToSit(false);
++ }
++ return result;
++ // CraftBukkit end
+ }
+ }
+
+@@ -343,7 +336,7 @@
+ super.setTame(flag);
+ if (flag) {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(20.0D);
+- this.setHealth(20.0F);
++ this.setHealth(this.getMaxHealth()); // CraftBukkit - 20.0 -> getMaxHealth()
+ } else {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(8.0D);
+ }
+@@ -367,7 +359,7 @@
+ itemstack.shrink(1);
+ }
+
+- this.heal((float) item.getFoodProperties().getNutrition());
++ this.heal((float) item.getFoodProperties().getNutrition(), EntityRegainHealthEvent.RegainReason.EATING); // CraftBukkit
+ return InteractionResult.SUCCESS;
+ } else {
+ if (item instanceof DyeItem) {
+@@ -395,7 +387,7 @@
+ this.setOrderedToSit(!this.isOrderedToSit());
+ this.jumping = false;
+ this.navigation.stop();
+- this.setTarget((LivingEntity) null);
++ this.setTarget((LivingEntity) null, EntityTargetEvent.TargetReason.FORGOT_TARGET, true); // CraftBukkit - reason
+ return InteractionResult.SUCCESS;
+ } else {
+ return interactionresult;
+@@ -406,7 +398,8 @@
+ itemstack.shrink(1);
+ }
+
+- if (this.random.nextInt(3) == 0) {
++ // CraftBukkit - added event call and isCancelled check.
++ if (this.random.nextInt(3) == 0 && !CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) {
+ this.tame(player);
+ this.navigation.stop();
+ this.setTarget((LivingEntity) null);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/allay/Allay.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/allay/Allay.java.patch
new file mode 100644
index 0000000000..a7a166d05c
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/allay/Allay.java.patch
@@ -0,0 +1,75 @@
+--- a/net/minecraft/world/entity/animal/allay/Allay.java
++++ b/net/minecraft/world/entity/animal/allay/Allay.java
+@@ -99,6 +99,7 @@
+ private float dancingAnimationTicks;
+ private float spinningAnimationTicks;
+ private float spinningAnimationTicks0;
++ public boolean forceDancing = false; // CraftBukkit
+
+ public Allay(EntityType<? extends Allay> entitytype, Level level) {
+ super(entitytype, level);
+@@ -110,6 +111,12 @@
+ this.dynamicJukeboxListener = new DynamicGameEventListener<>(new Allay.JukeboxListener(this.vibrationUser.getPositionSource(), GameEvent.JUKEBOX_PLAY.getNotificationRadius()));
+ }
+
++ // CraftBukkit start
++ public void setCanDuplicate(boolean canDuplicate) {
++ this.entityData.set(Allay.DATA_CAN_DUPLICATE, canDuplicate);
++ }
++ // CraftBukkit end
++
+ @Override
+ @Override
+ protected Brain.Provider<Allay> brainProvider() {
+@@ -245,7 +236,7 @@
+ public void aiStep() {
+ super.aiStep();
+ if (!this.level().isClientSide && this.isAlive() && this.tickCount % 10 == 0) {
+- this.heal(1.0F);
++ this.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit
+ }
+
+ if (this.isDancing() && this.shouldStopDancing() && this.tickCount % 20 == 0) {
+@@ -319,7 +306,12 @@
+ ItemStack itemstack1 = this.getItemInHand(InteractionHand.MAIN_HAND);
+
+ if (this.isDancing() && this.isDuplicationItem(itemstack) && this.canDuplicate()) {
+- this.duplicateAllay();
++ // CraftBukkit start - handle cancel duplication
++ Allay allay = this.duplicateAllay();
++ if (allay == null) {
++ return InteractionResult.SUCCESS;
++ }
++ // CraftBukkit end
+ this.level().broadcastEntityEvent(this, (byte) 18);
+ this.level().playSound(player, (Entity) this, SoundEvents.AMETHYST_BLOCK_CHIME, SoundSource.NEUTRAL, 2.0F, 1.0F);
+ this.removeInteractionItem(player, itemstack);
+@@ -454,6 +439,7 @@
+ }
+
+ private boolean shouldStopDancing() {
++ if (this.forceDancing) {return false;} // CraftBukkit
+ return this.jukeboxPos == null || !this.jukeboxPos.closerToCenterThan(this.position(), (double) GameEvent.JUKEBOX_PLAY.getNotificationRadius()) || !this.level().getBlockState(this.jukeboxPos).is(Blocks.JUKEBOX);
+ }
+
+@@ -553,7 +533,7 @@
+ return Allay.DUPLICATION_ITEM.test(itemstack);
+ }
+
+- private void duplicateAllay() {
++ public Allay duplicateAllay() { // CraftBukkit - return allay
+ Allay allay = (Allay) EntityType.ALLAY.create(this.level());
+
+ if (allay != null) {
+@@ -561,9 +541,9 @@
+ allay.setPersistenceRequired();
+ allay.resetDuplicationCooldown();
+ this.resetDuplicationCooldown();
+- this.level().addFreshEntity(allay);
++ this.level().addFreshEntity(allay, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DUPLICATION); // CraftBukkit - reason for duplicated allay
+ }
+-
++ return allay; // CraftBukkit
+ }
+
+ private void resetDuplicationCooldown() {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/axolotl/Axolotl.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/axolotl/Axolotl.java.patch
new file mode 100644
index 0000000000..3621a730df
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/axolotl/Axolotl.java.patch
@@ -0,0 +1,33 @@
+--- a/net/minecraft/world/entity/animal/axolotl/Axolotl.java
++++ b/net/minecraft/world/entity/animal/axolotl/Axolotl.java
+@@ -68,6 +68,12 @@
+
+ public class Axolotl extends Animal implements LerpingModel, VariantHolder<Axolotl.Variant>, Bucketable {
+
++ // CraftBukkit start - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
++ @Override
++ public int getDefaultMaxAirSupply() {
++ return AXOLOTL_TOTAL_AIR_SUPPLY;
++ }
++ // CraftBukkit end
+ public static final int TOTAL_PLAYDEAD_TIME = 200;
+ protected static final ImmutableList<? extends SensorType<? extends Sensor<? super Axolotl>>> SENSOR_TYPES = ImmutableList.of(SensorType.NEAREST_LIVING_ENTITIES, SensorType.NEAREST_ADULT, SensorType.HURT_BY, SensorType.AXOLOTL_ATTACKABLES, SensorType.AXOLOTL_TEMPTATIONS);
+ protected static final ImmutableList<? extends MemoryModuleType<?>> MEMORY_TYPES = ImmutableList.of(MemoryModuleType.BREED_TARGET, MemoryModuleType.NEAREST_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, MemoryModuleType.LOOK_TARGET, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.ATTACK_TARGET, MemoryModuleType.ATTACK_COOLING_DOWN, MemoryModuleType.NEAREST_VISIBLE_ADULT, new MemoryModuleType[]{MemoryModuleType.HURT_BY_ENTITY, MemoryModuleType.PLAY_DEAD_TICKS, MemoryModuleType.NEAREST_ATTACKABLE, MemoryModuleType.TEMPTING_PLAYER, MemoryModuleType.TEMPTATION_COOLDOWN_TICKS, MemoryModuleType.IS_TEMPTED, MemoryModuleType.HAS_HUNTING_COOLDOWN, MemoryModuleType.IS_PANICKING});
+@@ -197,7 +195,7 @@
+ @Override
+ @Override
+ public int getMaxAirSupply() {
+- return 6000;
++ return maxAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
+ }
+
+ @Override
+@@ -441,7 +416,7 @@
+ int i = mobeffectinstance != null ? mobeffectinstance.getDuration() : 0;
+ int j = Math.min(2400, 100 + i);
+
+- player.addEffect(new MobEffectInstance(MobEffects.REGENERATION, j, 0), this);
++ player.addEffect(new MobEffectInstance(MobEffects.REGENERATION, j, 0), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.AXOLOTL); // CraftBukkit
+ }
+
+ player.removeEffect(MobEffects.DIG_SLOWDOWN);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/frog/Tadpole.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/frog/Tadpole.java.patch
new file mode 100644
index 0000000000..68454adde5
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/frog/Tadpole.java.patch
@@ -0,0 +1,18 @@
+--- a/net/minecraft/world/entity/animal/frog/Tadpole.java
++++ b/net/minecraft/world/entity/animal/frog/Tadpole.java
+@@ -253,8 +233,14 @@
+ }
+
+ frog.setPersistenceRequired();
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTransformEvent(this, frog, org.bukkit.event.entity.EntityTransformEvent.TransformReason.METAMORPHOSIS).isCancelled()) {
++ this.setAge(0); // Sets the age to 0 for avoid a loop if the event is canceled
++ return;
++ }
++ // CraftBukkit end
+ this.playSound(SoundEvents.TADPOLE_GROW_UP, 0.15F, 1.0F);
+- serverlevel.addFreshEntityWithPassengers(frog);
++ worldserver.addFreshEntityWithPassengers(frog, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.METAMORPHOSIS); // CraftBukkit - add SpawnReason
+ this.discard();
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/goat/Goat.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/goat/Goat.java.patch
new file mode 100644
index 0000000000..d3e0177a47
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/goat/Goat.java.patch
@@ -0,0 +1,32 @@
+--- a/net/minecraft/world/entity/animal/goat/Goat.java
++++ b/net/minecraft/world/entity/animal/goat/Goat.java
+@@ -55,6 +55,12 @@
+ import net.minecraft.world.phys.Vec3;
+ import org.joml.Vector3f;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.player.PlayerBucketFillEvent;
++// CraftBukkit end
++
+ public class Goat extends Animal {
+
+ public static final EntityDimensions LONG_JUMPING_DIMENSIONS = EntityDimensions.scalable(0.9F, 1.3F).scale(0.7F);
+@@ -235,8 +226,15 @@
+ ItemStack itemstack = player.getItemInHand(interactionhand);
+
+ if (itemstack.is(Items.BUCKET) && !this.isBaby()) {
++ // CraftBukkit start - Got milk?
++ PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) player.level(), player, this.blockPosition(), this.blockPosition(), null, itemstack, Items.MILK_BUCKET, hand);
++
++ if (event.isCancelled()) {
++ return InteractionResult.PASS;
++ }
++ // CraftBukkit end
+ player.playSound(this.getMilkingSound(), 1.0F, 1.0F);
+- ItemStack itemstack1 = ItemUtils.createFilledResult(itemstack, player, Items.MILK_BUCKET.getDefaultInstance());
++ ItemStack itemstack1 = ItemUtils.createFilledResult(itemstack, player, CraftItemStack.asNMSCopy(event.getItemStack())); // CraftBukkit
+
+ player.setItemInHand(interactionhand, itemstack1);
+ return InteractionResult.sidedSuccess(this.level().isClientSide);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch
new file mode 100644
index 0000000000..18aa0c920f
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch
@@ -0,0 +1,105 @@
+--- a/net/minecraft/world/entity/animal/horse/AbstractHorse.java
++++ b/net/minecraft/world/entity/animal/horse/AbstractHorse.java
+@@ -79,6 +79,11 @@
+ import net.minecraft.world.phys.Vec3;
+ import org.joml.Vector3f;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityRegainHealthEvent;
++// CraftBukkit end
++
+ public abstract class AbstractHorse extends Animal implements ContainerListener, HasCustomInventoryScreen, OwnableEntity, PlayerRideableJumping, Saddleable {
+
+ public static final int EQUIPMENT_SLOT_OFFSET = 400;
+@@ -140,6 +145,7 @@
+ protected int gallopSoundCounter;
+ @Nullable
+ private UUID owner;
++ public int maxDomestication = 100; // CraftBukkit - store max domestication value
+
+ protected AbstractHorse(EntityType<? extends AbstractHorse> entitytype, Level level) {
+ super(entitytype, level);
+@@ -345,10 +341,10 @@
+ protected void createInventory() {
+ SimpleContainer simplecontainer = this.inventory;
+
+- this.inventory = new SimpleContainer(this.getInventorySize());
+- if (simplecontainer != null) {
+- simplecontainer.removeListener(this);
+- int i = Math.min(simplecontainer.getContainerSize(), this.inventory.getContainerSize());
++ this.inventory = new SimpleContainer(this.getInventorySize(), (org.bukkit.entity.AbstractHorse) this.getBukkitEntity()); // CraftBukkit
++ if (inventorysubcontainer != null) {
++ inventorysubcontainer.removeListener(this);
++ int i = Math.min(inventorysubcontainer.getContainerSize(), this.inventory.getContainerSize());
+
+ for (int j = 0; j < i; ++j) {
+ ItemStack itemstack = simplecontainer.getItem(j);
+@@ -457,7 +449,7 @@
+ }
+
+ public int getMaxTemper() {
+- return 100;
++ return this.maxDomestication; // CraftBukkit - return stored max domestication instead of 100
+ }
+
+ @Override
+@@ -531,7 +520,7 @@
+ }
+
+ if (this.getHealth() < this.getMaxHealth() && f > 0.0F) {
+- this.heal(f);
++ this.heal(f, EntityRegainHealthEvent.RegainReason.EATING); // CraftBukkit
+ flag = true;
+ }
+
+@@ -609,7 +594,7 @@
+ super.aiStep();
+ if (!this.level().isClientSide && this.isAlive()) {
+ if (this.random.nextInt(900) == 0 && this.deathTime == 0) {
+- this.heal(1.0F);
++ this.heal(1.0F, EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit
+ }
+
+ if (this.canEatGrass()) {
+@@ -882,6 +861,7 @@
+ if (this.getOwnerUUID() != null) {
+ compoundtag.putUUID("Owner", this.getOwnerUUID());
+ }
++ compound.putInt("Bukkit.MaxDomestication", this.maxDomestication); // CraftBukkit
+
+ if (!this.inventory.getItem(0).isEmpty()) {
+ compoundtag.put("SaddleItem", this.inventory.getItem(0).save(new CompoundTag()));
+@@ -910,6 +889,11 @@
+ if (uuid != null) {
+ this.setOwnerUUID(uuid);
+ }
++ // CraftBukkit start
++ if (compound.contains("Bukkit.MaxDomestication")) {
++ this.maxDomestication = compound.getInt("Bukkit.MaxDomestication");
++ }
++ // CraftBukkit end
+
+ if (compoundtag.contains("SaddleItem", 10)) {
+ ItemStack itemstack = ItemStack.of(compoundtag.getCompound("SaddleItem"));
+@@ -1015,8 +995,18 @@
+ }
+
+ @Override
+- @Override
+- public void handleStartJump(int i) {
++ public void handleStartJump(int jumpPower) {
++ // CraftBukkit start
++ float power;
++ if (jumpPower >= 90) {
++ power = 1.0F;
++ } else {
++ power = 0.4F + 0.4F * (float) jumpPower / 90.0F;
++ }
++ if (!CraftEventFactory.callHorseJumpEvent(this, power)) {
++ return;
++ }
++ // CraftBukkit end
+ this.allowStandSliding = true;
+ this.standIfPossible();
+ this.playJumpSound();
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/horse/Llama.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/horse/Llama.java.patch
new file mode 100644
index 0000000000..14597356bb
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/horse/Llama.java.patch
@@ -0,0 +1,19 @@
+--- a/net/minecraft/world/entity/animal/horse/Llama.java
++++ b/net/minecraft/world/entity/animal/horse/Llama.java
+@@ -84,9 +84,14 @@
+ return false;
+ }
+
+- private void setStrength(int i) {
+- this.entityData.set(Llama.DATA_STRENGTH_ID, Math.max(1, Math.min(5, i)));
++ // CraftBukkit start
++ public void setStrengthPublic(int i) {
++ this.setStrength(i);
+ }
++ // CraftBukkit end
++ private void setStrength(int strength) {
++ this.entityData.set(Llama.DATA_STRENGTH_ID, Math.max(1, Math.min(5, strength)));
++ }
+
+ private void setRandomStrength(RandomSource randomsource) {
+ int i = randomsource.nextFloat() < 0.04F ? 5 : 3;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java.patch
new file mode 100644
index 0000000000..fd45a9e6d2
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java.patch
@@ -0,0 +1,41 @@
+--- a/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java
++++ b/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java
+@@ -40,15 +38,15 @@
+ this.horse.setAge(0);
+ LightningBolt lightningbolt = (LightningBolt) EntityType.LIGHTNING_BOLT.create(serverlevel);
+
+- if (lightningbolt != null) {
+- lightningbolt.moveTo(this.horse.getX(), this.horse.getY(), this.horse.getZ());
+- lightningbolt.setVisualOnly(true);
+- serverlevel.addFreshEntity(lightningbolt);
+- Skeleton skeleton = this.createSkeleton(difficultyinstance, this.horse);
++ if (entitylightning != null) {
++ entitylightning.moveTo(this.horse.getX(), this.horse.getY(), this.horse.getZ());
++ entitylightning.setVisualOnly(true);
++ worldserver.strikeLightning(entitylightning, org.bukkit.event.weather.LightningStrikeEvent.Cause.TRAP); // CraftBukkit
++ Skeleton entityskeleton = this.createSkeleton(difficultydamagescaler, this.horse);
+
+- if (skeleton != null) {
+- skeleton.startRiding(this.horse);
+- serverlevel.addFreshEntityWithPassengers(skeleton);
++ if (entityskeleton != null) {
++ entityskeleton.startRiding(this.horse);
++ worldserver.addFreshEntityWithPassengers(entityskeleton, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.TRAP); // CraftBukkit
+
+ for (int i = 0; i < 3; ++i) {
+ AbstractHorse abstracthorse = this.createHorse(difficultyinstance);
+@@ -56,10 +54,10 @@
+ if (abstracthorse != null) {
+ Skeleton skeleton1 = this.createSkeleton(difficultyinstance, abstracthorse);
+
+- if (skeleton1 != null) {
+- skeleton1.startRiding(abstracthorse);
+- abstracthorse.push(this.horse.getRandom().triangle(0.0D, 1.1485D), 0.0D, this.horse.getRandom().triangle(0.0D, 1.1485D));
+- serverlevel.addFreshEntityWithPassengers(abstracthorse);
++ if (entityskeleton1 != null) {
++ entityskeleton1.startRiding(entityhorseabstract);
++ entityhorseabstract.push(this.horse.getRandom().triangle(0.0D, 1.1485D), 0.0D, this.horse.getRandom().triangle(0.0D, 1.1485D));
++ worldserver.addFreshEntityWithPassengers(entityhorseabstract, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.JOCKEY); // CraftBukkit
+ }
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/horse/TraderLlama.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/horse/TraderLlama.java.patch
new file mode 100644
index 0000000000..45a098f4b5
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/horse/TraderLlama.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/animal/horse/TraderLlama.java
++++ b/net/minecraft/world/entity/animal/horse/TraderLlama.java
+@@ -163,7 +153,7 @@
+ @Override
+ @Override
+ public void start() {
+- this.mob.setTarget(this.ownerLastHurtBy);
++ this.mob.setTarget(this.ownerLastHurtBy, org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_OWNER, true); // CraftBukkit
+ Entity entity = this.llama.getLeashHolder();
+
+ if (entity instanceof WanderingTrader) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/sniffer/Sniffer.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/sniffer/Sniffer.java.patch
new file mode 100644
index 0000000000..3d68a6c97f
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/animal/sniffer/Sniffer.java.patch
@@ -0,0 +1,50 @@
+--- a/net/minecraft/world/entity/animal/sniffer/Sniffer.java
++++ b/net/minecraft/world/entity/animal/sniffer/Sniffer.java
+@@ -81,17 +81,25 @@
+ return Mob.createMobAttributes().add(Attributes.MOVEMENT_SPEED, 0.10000000149011612D).add(Attributes.MAX_HEALTH, 14.0D);
+ }
+
+- public Sniffer(EntityType<? extends Animal> entitytype, Level level) {
+- super(entitytype, level);
+- this.entityData.define(Sniffer.DATA_STATE, Sniffer.State.IDLING);
+- this.entityData.define(Sniffer.DATA_DROP_SEED_AT_TICK, 0);
++ public Sniffer(EntityType<? extends Animal> entityType, Level level) {
++ super(entityType, level);
++ // this.entityData.define(Sniffer.DATA_STATE, Sniffer.State.IDLING); // CraftBukkit - moved down to appropriate location
++ // this.entityData.define(Sniffer.DATA_DROP_SEED_AT_TICK, 0); // CraftBukkit - moved down to appropriate location
+ this.getNavigation().setCanFloat(true);
+ this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F);
+ this.setPathfindingMalus(BlockPathTypes.DANGER_POWDER_SNOW, -1.0F);
+ this.setPathfindingMalus(BlockPathTypes.DAMAGE_CAUTIOUS, -1.0F);
+ }
+
++ // CraftBukkit start - SPIGOT-7295: moved from constructor to appropriate location
+ @Override
++ protected void defineSynchedData() {
++ super.defineSynchedData();
++ this.entityData.define(Sniffer.DATA_STATE, Sniffer.State.IDLING);
++ this.entityData.define(Sniffer.DATA_DROP_SEED_AT_TICK, 0);
++ }
++ // CraftBukkit end
++
+ @Override
+ protected float getStandingEyeHeight(Pose pose, EntityDimensions entitydimensions) {
+ return this.getDimensions(pose).height * 0.6F;
+@@ -272,8 +276,15 @@
+ ItemStack itemstack = (ItemStack) iterator.next();
+ ItemEntity itementity = new ItemEntity(serverlevel, (double) blockpos.getX(), (double) blockpos.getY(), (double) blockpos.getZ(), itemstack);
+
+- itementity.setDefaultPickUpDelay();
+- serverlevel.addFreshEntity(itementity);
++ // CraftBukkit start - handle EntityDropItemEvent
++ org.bukkit.event.entity.EntityDropItemEvent event = new org.bukkit.event.entity.EntityDropItemEvent(this.getBukkitEntity(), (org.bukkit.entity.Item) entityitem.getBukkitEntity());
++ org.bukkit.Bukkit.getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ continue;
++ }
++ // CraftBukkit end
++ entityitem.setDefaultPickUpDelay();
++ worldserver.addFreshEntity(entityitem);
+ }
+
+ this.playSound(SoundEvents.SNIFFER_DROP_SEED, 1.0F, 1.0F);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch
new file mode 100644
index 0000000000..45151e7e26
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch
@@ -0,0 +1,53 @@
+--- a/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java
++++ b/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java
+@@ -19,6 +19,10 @@
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.block.BaseFireBlock;
+ import net.minecraft.world.level.dimension.end.EndDragonFight;
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.ExplosionPrimeEvent;
++// CraftBukkit end
+
+ public class EndCrystal extends Entity {
+
+@@ -57,8 +58,12 @@
+ if (this.level() instanceof ServerLevel) {
+ BlockPos blockpos = this.blockPosition();
+
+- if (((ServerLevel) this.level()).getDragonFight() != null && this.level().getBlockState(blockpos).isAir()) {
+- this.level().setBlockAndUpdate(blockpos, BaseFireBlock.getState(this.level(), blockpos));
++ if (((ServerLevel) this.level()).getDragonFight() != null && this.level().getBlockState(blockposition).isAir()) {
++ // CraftBukkit start
++ if (!CraftEventFactory.callBlockIgniteEvent(this.level(), blockposition, this).isCancelled()) {
++ this.level().setBlockAndUpdate(blockposition, BaseFireBlock.getState(this.level(), blockposition));
++ }
++ // CraftBukkit end
+ }
+ }
+
+@@ -102,11 +103,23 @@
+ return false;
+ } else {
+ if (!this.isRemoved() && !this.level().isClientSide) {
++ // CraftBukkit start - All non-living entities need this
++ if (CraftEventFactory.handleNonLivingEntityDamageEvent(this, source, amount, false)) {
++ return false;
++ }
++ // CraftBukkit end
+ this.remove(Entity.RemovalReason.KILLED);
+ if (!damagesource.is(DamageTypeTags.IS_EXPLOSION)) {
+ DamageSource damagesource1 = damagesource.getEntity() != null ? this.damageSources().explosion(this, damagesource.getEntity()) : null;
+
+- this.level().explode(this, damagesource1, (ExplosionDamageCalculator) null, this.getX(), this.getY(), this.getZ(), 6.0F, false, Level.ExplosionInteraction.BLOCK);
++ // CraftBukkit start
++ ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent(this, 6.0F, false);
++ if (event.isCancelled()) {
++ this.unsetRemoved();
++ return false;
++ }
++ this.level().explode(this, damagesource1, (ExplosionDamageCalculator) null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.a.BLOCK);
++ // CraftBukkit end
+ }
+
+ this.onDestroyedBy(damagesource);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch
new file mode 100644
index 0000000000..9c402d8c12
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch
@@ -0,0 +1,231 @@
+--- a/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
++++ b/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
+@@ -53,7 +51,19 @@
+ import org.joml.Vector3f;
+ import org.slf4j.Logger;
+
+-public class EnderDragon extends Mob implements Enemy {
++// CraftBukkit start
++import net.minecraft.world.item.ItemStack;
++import net.minecraft.world.level.Explosion;
++import net.minecraft.world.level.block.Block;
++import net.minecraft.world.level.block.entity.BlockEntity;
++import net.minecraft.world.level.storage.loot.LootParams;
++import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
++import net.minecraft.world.phys.AABB;
++import net.minecraft.world.phys.Vec3;
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.event.entity.EntityExplodeEvent;
++import org.bukkit.event.entity.EntityRegainHealthEvent;
++// CraftBukkit end
+
+ private static final Logger LOGGER = LogUtils.getLogger();
+ public static final EntityDataAccessor<Integer> DATA_PHASE = SynchedEntityData.defineId(EnderDragon.class, EntityDataSerializers.INT);
+@@ -90,6 +102,7 @@
+ private final Node[] nodes;
+ private final int[] nodeAdjacency;
+ private final BinaryHeap openSet;
++ private final Explosion explosionSource; // CraftBukkit - reusable source for CraftTNTPrimed.getSource()
+
+ public EnderDragon(EntityType<? extends EnderDragon> entitytype, Level level) {
+ super(EntityType.ENDER_DRAGON, level);
+@@ -111,6 +124,7 @@
+ this.noPhysics = true;
+ this.noCulling = true;
+ this.phaseManager = new EnderDragonPhaseManager(this);
++ this.explosionSource = new Explosion(level, this, null, null, Double.NaN, Double.NaN, Double.NaN, Float.NaN, true, Explosion.Effect.DESTROY, ParticleTypes.EXPLOSION, ParticleTypes.EXPLOSION_EMITTER, SoundEvents.GENERIC_EXPLODE); // CraftBukkit
+ }
+
+ public void setDragonFight(EndDragonFight enddragonfight) {
+@@ -262,10 +272,10 @@
+
+ Vec3 vec31 = dragonphaseinstance.getFlyTargetLocation();
+
+- if (vec31 != null) {
+- double d0 = vec31.x - this.getX();
+- double d1 = vec31.y - this.getY();
+- double d2 = vec31.z - this.getZ();
++ if (vec3d1 != null && idragoncontroller.getPhase() != EnderDragonPhase.HOVERING) { // CraftBukkit - Don't move when hovering
++ double d0 = vec3d1.x - this.getX();
++ double d1 = vec3d1.y - this.getY();
++ double d2 = vec3d1.z - this.getZ();
+ double d3 = d0 * d0 + d1 * d1 + d2 * d2;
+ float f6 = dragonphaseinstance.getFlySpeed();
+ double d4 = Math.sqrt(d0 * d0 + d2 * d2);
+@@ -403,7 +413,14 @@
+ if (this.nearestCrystal.isRemoved()) {
+ this.nearestCrystal = null;
+ } else if (this.tickCount % 10 == 0 && this.getHealth() < this.getMaxHealth()) {
+- this.setHealth(this.getHealth() + 1.0F);
++ // CraftBukkit start
++ EntityRegainHealthEvent event = new EntityRegainHealthEvent(this.getBukkitEntity(), 1.0F, EntityRegainHealthEvent.RegainReason.ENDER_CRYSTAL);
++ this.level().getCraftServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ this.setHealth((float) (this.getHealth() + event.getAmount()));
++ }
++ // CraftBukkit end
+ }
+ }
+
+@@ -478,6 +495,9 @@
+ int j1 = Mth.floor(aabb.maxZ);
+ boolean flag = false;
+ boolean flag1 = false;
++ // CraftBukkit start - Create a list to hold all the destroyed blocks
++ List<org.bukkit.block.Block> destroyedBlocks = new java.util.ArrayList<org.bukkit.block.Block>();
++ // CraftBukkit end
+
+ for (int k1 = i; k1 <= l; ++k1) {
+ for (int l1 = j; l1 <= i1; ++l1) {
+@@ -485,9 +505,13 @@
+ BlockPos blockpos = new BlockPos(k1, l1, i2);
+ BlockState blockstate = this.level().getBlockState(blockpos);
+
+- if (!blockstate.isAir() && !blockstate.is(BlockTags.DRAGON_TRANSPARENT)) {
+- if (this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && !blockstate.is(BlockTags.DRAGON_IMMUNE)) {
+- flag1 = this.level().removeBlock(blockpos, false) || flag1;
++ if (!iblockdata.isAir() && !iblockdata.is(BlockTags.DRAGON_TRANSPARENT)) {
++ if (this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && !iblockdata.is(BlockTags.DRAGON_IMMUNE)) {
++ // CraftBukkit start - Add blocks to list rather than destroying them
++ // flag1 = this.level().removeBlock(blockposition, false) || flag1;
++ flag1 = true;
++ destroyedBlocks.add(CraftBlock.at(this.level(), blockposition));
++ // CraftBukkit end
+ } else {
+ flag = true;
+ }
+@@ -496,6 +520,51 @@
+ }
+ }
+
++ // CraftBukkit start - Set off an EntityExplodeEvent for the dragon exploding all these blocks
++ // SPIGOT-4882: don't fire event if nothing hit
++ if (!flag1) {
++ return flag;
++ }
++
++ org.bukkit.entity.Entity bukkitEntity = this.getBukkitEntity();
++ EntityExplodeEvent event = new EntityExplodeEvent(bukkitEntity, bukkitEntity.getLocation(), destroyedBlocks, 0F);
++ bukkitEntity.getServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ // This flag literally means 'Dragon hit something hard' (Obsidian, White Stone or Bedrock) and will cause the dragon to slow down.
++ // We should consider adding an event extension for it, or perhaps returning true if the event is cancelled.
++ return flag;
++ } else if (event.getYield() == 0F) {
++ // Yield zero ==> no drops
++ for (org.bukkit.block.Block block : event.blockList()) {
++ this.level().removeBlock(new BlockPos(block.getX(), block.getY(), block.getZ()), false);
++ }
++ } else {
++ for (org.bukkit.block.Block block : event.blockList()) {
++ org.bukkit.Material blockId = block.getType();
++ if (blockId.isAir()) {
++ continue;
++ }
++
++ CraftBlock craftBlock = ((CraftBlock) block);
++ BlockPos blockposition = craftBlock.getPosition();
++
++ Block nmsBlock = craftBlock.getNMS().getBlock();
++ if (nmsBlock.dropFromExplosion(explosionSource)) {
++ BlockEntity tileentity = craftBlock.getNMS().hasBlockEntity() ? this.level().getBlockEntity(blockposition) : null;
++ LootParams.Builder loottableinfo_builder = (new LootParams.Builder((ServerLevel) this.level())).withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(blockposition)).withParameter(LootContextParams.TOOL, ItemStack.EMPTY).withParameter(LootContextParams.EXPLOSION_RADIUS, 1.0F / event.getYield()).withOptionalParameter(LootContextParams.BLOCK_ENTITY, tileentity);
++
++ craftBlock.getNMS().getDrops(loottableinfo_builder).forEach((itemstack) -> {
++ Block.popResource(this.level(), blockposition, itemstack);
++ });
++ craftBlock.getNMS().spawnAfterBreak((ServerLevel) this.level(), blockposition, ItemStack.EMPTY, false);
++ }
++ nmsBlock.wasExploded(this.level(), blockposition, explosionSource);
++
++ this.level().removeBlock(blockposition, false);
++ }
++ }
++ // CraftBukkit end
++
+ if (flag1) {
+ BlockPos blockpos1 = new BlockPos(i + this.random.nextInt(l - i + 1), j + this.random.nextInt(i1 - j + 1), k + this.random.nextInt(j1 - k + 1));
+
+@@ -562,7 +629,21 @@
+
+ }
+
++ // CraftBukkit start - SPIGOT-2420: Special case, the ender dragon drops 12000 xp for the first kill and 500 xp for every other kill and this over time.
+ @Override
++ public int getExpReward() {
++ // CraftBukkit - Moved from #tickDeath method
++ boolean flag = this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT);
++ short short0 = 500;
++
++ if (this.dragonFight != null && !this.dragonFight.hasPreviouslyKilledDragon()) {
++ short0 = 12000;
++ }
++
++ return flag ? short0 : 0;
++ }
++ // CraftBukkit end
++
+ @Override
+ protected void tickDeath() {
+ if (this.dragonFight != null) {
+@@ -578,15 +659,20 @@
+ this.level().addParticle(ParticleTypes.EXPLOSION_EMITTER, this.getX() + (double) f, this.getY() + 2.0D + (double) f1, this.getZ() + (double) f2, 0.0D, 0.0D, 0.0D);
+ }
+
++ // CraftBukkit start - SPIGOT-2420: Moved up to #getExpReward method
++ /*
+ boolean flag = this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT);
+ short short0 = 500;
+
+ if (this.dragonFight != null && !this.dragonFight.hasPreviouslyKilledDragon()) {
+ short0 = 12000;
+ }
++ */
++ int short0 = expToDrop;
++ // CraftBukkit end
+
+ if (this.level() instanceof ServerLevel) {
+- if (this.dragonDeathTime > 150 && this.dragonDeathTime % 5 == 0 && flag) {
++ if (this.dragonDeathTime > 150 && this.dragonDeathTime % 5 == 0 && true) { // CraftBukkit - SPIGOT-2420: Already checked for the game rule when calculating the xp
+ ExperienceOrb.award((ServerLevel) this.level(), this.position(), Mth.floor((float) short0 * 0.08F));
+ }
+
+@@ -597,7 +683,7 @@
+
+ this.move(MoverType.SELF, new Vec3(0.0D, 0.10000000149011612D, 0.0D));
+ if (this.dragonDeathTime == 200 && this.level() instanceof ServerLevel) {
+- if (flag) {
++ if (true) { // CraftBukkit - SPIGOT-2420: Already checked for the game rule when calculating the xp
+ ExperienceOrb.award((ServerLevel) this.level(), this.position(), Mth.floor((float) short0 * 0.2F));
+ }
+
+@@ -814,11 +900,11 @@
+ }
+
+ @Override
+- @Override
+- public void addAdditionalSaveData(CompoundTag compoundtag) {
+- super.addAdditionalSaveData(compoundtag);
+- compoundtag.putInt("DragonPhase", this.phaseManager.getCurrentPhase().getPhase().getId());
+- compoundtag.putInt("DragonDeathTime", this.dragonDeathTime);
++ public void addAdditionalSaveData(CompoundTag compound) {
++ super.addAdditionalSaveData(compound);
++ compound.putInt("DragonPhase", this.phaseManager.getCurrentPhase().getPhase().getId());
++ compound.putInt("DragonDeathTime", this.dragonDeathTime);
++ compound.putInt("Bukkit.expToDrop", expToDrop); // CraftBukkit - SPIGOT-2420: The ender dragon drops xp over time which can also happen between server starts
+ }
+
+ @Override
+@@ -833,6 +918,11 @@
+ this.dragonDeathTime = compoundtag.getInt("DragonDeathTime");
+ }
+
++ // CraftBukkit start - SPIGOT-2420: The ender dragon drops xp over time which can also happen between server starts
++ if (compound.contains("Bukkit.expToDrop")) {
++ this.expToDrop = compound.getInt("Bukkit.expToDrop");
++ }
++ // CraftBukkit end
+ }
+
+ @Override
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch
new file mode 100644
index 0000000000..ec9afdecce
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch
@@ -0,0 +1,36 @@
+--- a/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java
++++ b/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java
+@@ -5,6 +5,11 @@
+ import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
+ import org.slf4j.Logger;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.entity.CraftEnderDragon;
++import org.bukkit.event.entity.EnderDragonChangePhaseEvent;
++// CraftBukkit end
++
+ public class EnderDragonPhaseManager {
+
+ private static final Logger LOGGER = LogUtils.getLogger();
+@@ -24,7 +29,20 @@
+ this.currentPhase.end();
+ }
+
+- this.currentPhase = this.getPhase(enderdragonphase);
++ // CraftBukkit start - Call EnderDragonChangePhaseEvent
++ EnderDragonChangePhaseEvent event = new EnderDragonChangePhaseEvent(
++ (CraftEnderDragon) this.dragon.getBukkitEntity(),
++ (this.currentPhase == null) ? null : CraftEnderDragon.getBukkitPhase(this.currentPhase.getPhase()),
++ CraftEnderDragon.getBukkitPhase(phase)
++ );
++ this.dragon.level().getCraftServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ return;
++ }
++ phase = CraftEnderDragon.getMinecraftPhase(event.getNewPhase());
++ // CraftBukkit end
++
++ this.currentPhase = this.getPhase(phase);
+ if (!this.dragon.level().isClientSide) {
+ this.dragon.getEntityData().set(EnderDragon.DATA_PHASE, enderdragonphase.getId());
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch
new file mode 100644
index 0000000000..ed242f6462
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch
@@ -0,0 +1,108 @@
+--- a/net/minecraft/world/entity/boss/wither/WitherBoss.java
++++ b/net/minecraft/world/entity/boss/wither/WitherBoss.java
+@@ -54,6 +43,26 @@
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.block.state.BlockState;
+ import net.minecraft.world.phys.Vec3;
++import net.minecraft.core.BlockPos;
++import net.minecraft.core.particles.ParticleTypes;
++import net.minecraft.nbt.CompoundTag;
++import net.minecraft.network.chat.Component;
++import net.minecraft.network.protocol.game.ClientboundLevelEventPacket;
++import net.minecraft.network.syncher.EntityDataAccessor;
++import net.minecraft.network.syncher.EntityDataSerializers;
++import net.minecraft.network.syncher.SynchedEntityData;
++import net.minecraft.server.MinecraftServer;
++import net.minecraft.server.level.ServerBossEvent;
++import net.minecraft.server.level.ServerLevel;
++import net.minecraft.server.level.ServerPlayer;
++import net.minecraft.sounds.SoundEvent;
++import net.minecraft.sounds.SoundEvents;
++import net.minecraft.world.level.block.Blocks;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityRegainHealthEvent;
++import org.bukkit.event.entity.EntityTargetEvent;
++import org.bukkit.event.entity.ExplosionPrimeEvent;
++// CraftBukkit end
+
+ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob {
+
+@@ -259,15 +257,40 @@
+ i = this.getInvulnerableTicks() - 1;
+ this.bossEvent.setProgress(1.0F - (float) i / 220.0F);
+ if (i <= 0) {
+- this.level().explode(this, this.getX(), this.getEyeY(), this.getZ(), 7.0F, false, Level.ExplosionInteraction.MOB);
++ // CraftBukkit start
++ // this.level().explode(this, this.getX(), this.getEyeY(), this.getZ(), 7.0F, false, World.a.MOB);
++ ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), 7.0F, false);
++ this.level().getCraftServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ this.level().explode(this, this.getX(), this.getEyeY(), this.getZ(), event.getRadius(), event.getFire(), Level.a.MOB);
++ }
++ // CraftBukkit end
++
+ if (!this.isSilent()) {
+- this.level().globalLevelEvent(1023, this.blockPosition(), 0);
++ // CraftBukkit start - Use relative location for far away sounds
++ // this.level().globalLevelEvent(1023, new BlockPosition(this), 0);
++ int viewDistance = ((ServerLevel) this.level()).getCraftServer().getViewDistance() * 16;
++ for (ServerPlayer player : (List<ServerPlayer>) MinecraftServer.getServer().getPlayerList().players) {
++ double deltaX = this.getX() - player.getX();
++ double deltaZ = this.getZ() - player.getZ();
++ double distanceSquared = deltaX * deltaX + deltaZ * deltaZ;
++ if (distanceSquared > viewDistance * viewDistance) {
++ double deltaLength = Math.sqrt(distanceSquared);
++ double relativeX = player.getX() + (deltaX / deltaLength) * viewDistance;
++ double relativeZ = player.getZ() + (deltaZ / deltaLength) * viewDistance;
++ player.connection.send(new ClientboundLevelEventPacket(1023, new BlockPos((int) relativeX, (int) this.getY(), (int) relativeZ), 0, true));
++ } else {
++ player.connection.send(new ClientboundLevelEventPacket(1023, this.blockPosition(), 0, true));
++ }
++ }
++ // CraftBukkit end
+ }
+ }
+
+ this.setInvulnerableTicks(i);
+ if (this.tickCount % 10 == 0) {
+- this.heal(10.0F);
++ this.heal(10.0F, EntityRegainHealthEvent.RegainReason.WITHER_SPAWN); // CraftBukkit
+ }
+
+ } else {
+@@ -312,7 +335,8 @@
+ if (!list.isEmpty()) {
+ LivingEntity livingentity1 = (LivingEntity) list.get(this.random.nextInt(list.size()));
+
+- this.setAlternativeTarget(i, livingentity1.getId());
++ if (CraftEventFactory.callEntityTargetLivingEvent(this, entityliving1, EntityTargetEvent.TargetReason.CLOSEST_ENTITY).isCancelled()) continue; // CraftBukkit
++ this.setAlternativeTarget(i, entityliving1.getId());
+ }
+ }
+ }
+@@ -341,8 +365,13 @@
+ BlockPos blockpos = new BlockPos(i2, j2, k2);
+ BlockState blockstate = this.level().getBlockState(blockpos);
+
+- if (canDestroy(blockstate)) {
+- flag = this.level().destroyBlock(blockpos, true, this) || flag;
++ if (canDestroy(iblockdata)) {
++ // CraftBukkit start
++ if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, Blocks.AIR.defaultBlockState())) {
++ continue;
++ }
++ // CraftBukkit end
++ flag = this.level().destroyBlock(blockposition, true, this) || flag;
+ }
+ }
+ }
+@@ -355,7 +384,7 @@
+ }
+
+ if (this.tickCount % 20 == 0) {
+- this.heal(1.0F);
++ this.heal(1.0F, EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit
+ }
+
+ this.bossEvent.setProgress(this.getHealth() / this.getMaxHealth());
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/decoration/ArmorStand.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/decoration/ArmorStand.java.patch
new file mode 100644
index 0000000000..8cb4d0c164
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/decoration/ArmorStand.java.patch
@@ -0,0 +1,183 @@
+--- a/net/minecraft/world/entity/decoration/ArmorStand.java
++++ b/net/minecraft/world/entity/decoration/ArmorStand.java
+@@ -43,6 +41,13 @@
+ import net.minecraft.world.level.material.PushReaction;
+ import net.minecraft.world.phys.AABB;
+ import net.minecraft.world.phys.Vec3;
++// CraftBukkit start
++import org.bukkit.inventory.EquipmentSlot;
++import org.bukkit.craftbukkit.CraftEquipmentSlot;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.entity.Player;
++import org.bukkit.event.player.PlayerArmorStandManipulateEvent;
++// CraftBukkit end
+
+ public class ArmorStand extends LivingEntity {
+
+@@ -106,7 +111,13 @@
+ this.setPos(d0, d1, d2);
+ }
+
++ // CraftBukkit start - SPIGOT-3607, SPIGOT-3637
+ @Override
++ public float getBukkitYaw() {
++ return this.getYRot();
++ }
++ // CraftBukkit end
++
+ @Override
+ public void refreshDimensions() {
+ double d0 = this.getX();
+@@ -166,15 +172,21 @@
+ }
+
+ @Override
++ public void setItemSlot(net.minecraft.world.entity.EquipmentSlot slot, ItemStack stack) {
++ // CraftBukkit start
++ this.setItemSlot(slot, stack, false);
++ }
++
+ @Override
+- public void setItemSlot(EquipmentSlot equipmentslot, ItemStack itemstack) {
++ public void setItemSlot(net.minecraft.world.entity.EquipmentSlot enumitemslot, ItemStack itemstack, boolean silent) {
++ // CraftBukkit end
+ this.verifyEquippedItem(itemstack);
+ switch (equipmentslot.getType()) {
+ case HAND:
+- this.onEquipItem(equipmentslot, (ItemStack) this.handItems.set(equipmentslot.getIndex(), itemstack), itemstack);
++ this.onEquipItem(enumitemslot, (ItemStack) this.handItems.set(enumitemslot.getIndex(), itemstack), itemstack, silent); // CraftBukkit
+ break;
+ case ARMOR:
+- this.onEquipItem(equipmentslot, (ItemStack) this.armorItems.set(equipmentslot.getIndex(), itemstack), itemstack);
++ this.onEquipItem(enumitemslot, (ItemStack) this.armorItems.set(enumitemslot.getIndex(), itemstack), itemstack, silent); // CraftBukkit
+ }
+
+ }
+@@ -416,8 +421,26 @@
+ return false;
+ } else if (itemstack1.isEmpty() && (this.disabledSlots & 1 << equipmentslot.getFilterFlag() + 16) != 0) {
+ return false;
+- } else if (player.getAbilities().instabuild && itemstack1.isEmpty() && !itemstack.isEmpty()) {
+- this.setItemSlot(equipmentslot, itemstack.copyWithCount(1));
++ // CraftBukkit start
++ } else {
++ org.bukkit.inventory.ItemStack armorStandItem = CraftItemStack.asCraftMirror(itemstack1);
++ org.bukkit.inventory.ItemStack playerHeldItem = CraftItemStack.asCraftMirror(stack);
++
++ Player player1 = (Player) player.getBukkitEntity();
++ org.bukkit.entity.ArmorStand self = (org.bukkit.entity.ArmorStand) this.getBukkitEntity();
++
++ EquipmentSlot slot1 = CraftEquipmentSlot.getSlot(slot);
++ EquipmentSlot hand = CraftEquipmentSlot.getHand(hand);
++ PlayerArmorStandManipulateEvent armorStandManipulateEvent = new PlayerArmorStandManipulateEvent(player1, self, playerHeldItem, armorStandItem, slot1, hand);
++ this.level().getCraftServer().getPluginManager().callEvent(armorStandManipulateEvent);
++
++ if (armorStandManipulateEvent.isCancelled()) {
++ return true;
++ }
++
++ if (player.getAbilities().instabuild && itemstack1.isEmpty() && !stack.isEmpty()) {
++ // CraftBukkit end
++ this.setItemSlot(slot, stack.copyWithCount(1));
+ return true;
+ } else if (!itemstack.isEmpty() && itemstack.getCount() > 1) {
+ if (!itemstack1.isEmpty()) {
+@@ -431,18 +454,29 @@
+ player.setItemInHand(interactionhand, itemstack1);
+ return true;
+ }
++ } // CraftBukkit
+ }
+
+ @Override
+ @Override
+ public boolean hurt(DamageSource damagesource, float f) {
+ if (!this.level().isClientSide && !this.isRemoved()) {
+- if (damagesource.is(DamageTypeTags.BYPASSES_INVULNERABILITY)) {
++ if (source.is(DamageTypeTags.BYPASSES_INVULNERABILITY)) {
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, source, amount)) {
++ return false;
++ }
++ // CraftBukkit end
+ this.kill();
+ return false;
+- } else if (!this.isInvulnerableTo(damagesource) && !this.invisible && !this.isMarker()) {
+- if (damagesource.is(DamageTypeTags.IS_EXPLOSION)) {
+- this.brokenByAnything(damagesource);
++ } else if (!this.isInvulnerableTo(source) && (true || !this.invisible) && !this.isMarker()) { // CraftBukkit
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, source, amount, true, this.invisible)) {
++ return false;
++ }
++ // CraftBukkit end
++ if (source.is(DamageTypeTags.IS_EXPLOSION)) {
++ this.brokenByAnything(source);
+ this.kill();
+ return false;
+ } else if (damagesource.is(DamageTypeTags.IGNITES_ARMOR_STANDS)) {
+@@ -488,7 +521,7 @@
+ } else {
+ this.brokenByPlayer(damagesource);
+ this.showBreakingParticles();
+- this.kill();
++ this.discard(); // CraftBukkit - SPIGOT-4890: remain as this.discard() since above damagesource method will call death event
+ }
+
+ return true;
+@@ -558,13 +589,13 @@
+ itemstack.setHoverName(this.getCustomName());
+ }
+
+- Block.popResource(this.level(), this.blockPosition(), itemstack);
+- this.brokenByAnything(damagesource);
++ drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); // CraftBukkit - add to drops
++ this.brokenByAnything(damageSource);
+ }
+
+ private void brokenByAnything(DamageSource damagesource) {
+ this.playBrokenSound();
+- this.dropAllDeathLoot(damagesource);
++ // this.dropAllDeathLoot(damagesource); // CraftBukkit - moved down
+
+ ItemStack itemstack;
+ int i;
+@@ -572,7 +603,7 @@
+ for (i = 0; i < this.handItems.size(); ++i) {
+ itemstack = (ItemStack) this.handItems.get(i);
+ if (!itemstack.isEmpty()) {
+- Block.popResource(this.level(), this.blockPosition().above(), itemstack);
++ drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); // CraftBukkit - add to drops
+ this.handItems.set(i, ItemStack.EMPTY);
+ }
+ }
+@@ -580,10 +611,11 @@
+ for (i = 0; i < this.armorItems.size(); ++i) {
+ itemstack = (ItemStack) this.armorItems.get(i);
+ if (!itemstack.isEmpty()) {
+- Block.popResource(this.level(), this.blockPosition().above(), itemstack);
++ drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); // CraftBukkit - add to drops
+ this.armorItems.set(i, ItemStack.EMPTY);
+ }
+ }
++ this.dropAllDeathLoot(damageSource); // CraftBukkit - moved from above
+
+ }
+
+@@ -688,9 +711,16 @@
+ return this.isSmall();
+ }
+
++ // CraftBukkit start
+ @Override
++ public boolean shouldDropExperience() {
++ return true; // MC-157395, SPIGOT-5193 even baby (small) armor stands should drop
++ }
++ // CraftBukkit end
++
+ @Override
+ public void kill() {
++ org.bukkit.craftbukkit.event.CraftEventFactory.callEntityDeathEvent(this, drops); // CraftBukkit - call event
+ this.remove(Entity.RemovalReason.KILLED);
+ this.gameEvent(GameEvent.ENTITY_DIE);
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/decoration/HangingEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/decoration/HangingEntity.java.patch
new file mode 100644
index 0000000000..fae392e24a
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/decoration/HangingEntity.java.patch
@@ -0,0 +1,152 @@
+--- a/net/minecraft/world/entity/decoration/HangingEntity.java
++++ b/net/minecraft/world/entity/decoration/HangingEntity.java
+@@ -25,6 +20,17 @@
+ import net.minecraft.world.phys.Vec3;
+ import org.apache.commons.lang3.Validate;
+ import org.slf4j.Logger;
++import net.minecraft.core.BlockPos;
++import net.minecraft.core.Direction;
++import net.minecraft.nbt.CompoundTag;
++import net.minecraft.server.level.ServerLevel;
++// CraftBukkit start
++import net.minecraft.tags.DamageTypeTags;
++import net.minecraft.util.Mth;
++import org.bukkit.entity.Hanging;
++import org.bukkit.event.hanging.HangingBreakByEntityEvent;
++import org.bukkit.event.hanging.HangingBreakEvent;
++// CraftBukkit end
+
+ public abstract class HangingEntity extends Entity {
+
+@@ -61,9 +66,18 @@
+
+ protected void recalculateBoundingBox() {
+ if (this.direction != null) {
+- double d0 = (double) this.pos.getX() + 0.5D;
+- double d1 = (double) this.pos.getY() + 0.5D;
+- double d2 = (double) this.pos.getZ() + 0.5D;
++ // CraftBukkit start code moved in to calculateBoundingBox
++ this.setBoundingBox(calculateBoundingBox(this, this.pos, this.direction, this.getWidth(), this.getHeight()));
++ // CraftBukkit end
++ }
++ }
++
++ // CraftBukkit start - break out BB calc into own method
++ public static AABB calculateBoundingBox(@Nullable Entity entity, BlockPos blockPosition, Direction direction, int width, int height) {
++ {
++ double d0 = (double) blockPosition.getX() + 0.5D;
++ double d1 = (double) blockPosition.getY() + 0.5D;
++ double d2 = (double) blockPosition.getZ() + 0.5D;
+ double d3 = 0.46875D;
+ double d4 = this.offs(this.getWidth());
+ double d5 = this.offs(this.getHeight());
+@@ -92,8 +108,9 @@
+ this.setBoundingBox(new AABB(d0 - d6, d1 - d7, d2 - d8, d0 + d6, d1 + d7, d2 + d8));
+ }
+ }
++ // CraftBukkit end
+
+- private double offs(int i) {
++ private static double offs(int i) { // CraftBukkit - static
+ return i % 32 == 0 ? 0.5D : 0.0D;
+ }
+
+@@ -105,6 +121,24 @@
+ if (this.checkInterval++ == 100) {
+ this.checkInterval = 0;
+ if (!this.isRemoved() && !this.survives()) {
++ // CraftBukkit start - fire break events
++ IBlockData material = this.level().getBlockState(this.blockPosition());
++ HangingBreakEvent.RemoveCause cause;
++
++ if (!material.isAir()) {
++ // TODO: This feels insufficient to catch 100% of suffocation cases
++ cause = HangingBreakEvent.RemoveCause.OBSTRUCTION;
++ } else {
++ cause = HangingBreakEvent.RemoveCause.PHYSICS;
++ }
++
++ HangingBreakEvent event = new HangingBreakEvent((Hanging) this.getBukkitEntity(), cause);
++ this.level().getCraftServer().getPluginManager().callEvent(event);
++
++ if (this.isRemoved() || event.isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
+ this.discard();
+ this.dropItem((Entity) null);
+ }
+@@ -172,6 +202,22 @@
+ return false;
+ } else {
+ if (!this.isRemoved() && !this.level().isClientSide) {
++ // CraftBukkit start - fire break events
++ Entity damager = (source.isIndirect()) ? source.getEntity() : source.getDirectEntity();
++ HangingBreakEvent event;
++ if (damager != null) {
++ event = new HangingBreakByEntityEvent((Hanging) this.getBukkitEntity(), damager.getBukkitEntity(), source.is(DamageTypeTags.IS_EXPLOSION) ? HangingBreakEvent.RemoveCause.EXPLOSION : HangingBreakEvent.RemoveCause.ENTITY);
++ } else {
++ event = new HangingBreakEvent((Hanging) this.getBukkitEntity(), source.is(DamageTypeTags.IS_EXPLOSION) ? HangingBreakEvent.RemoveCause.EXPLOSION : HangingBreakEvent.RemoveCause.DEFAULT);
++ }
++
++ this.level().getCraftServer().getPluginManager().callEvent(event);
++
++ if (this.isRemoved() || event.isCancelled()) {
++ return true;
++ }
++ // CraftBukkit end
++
+ this.kill();
+ this.markHurt();
+ this.dropItem(damagesource.getEntity());
+@@ -182,9 +228,20 @@
+ }
+
+ @Override
+- @Override
+- public void move(MoverType movertype, Vec3 vec3) {
+- if (!this.level().isClientSide && !this.isRemoved() && vec3.lengthSqr() > 0.0D) {
++ public void move(EnumMoveType type, Vec3 pos) {
++ if (!this.level().isClientSide && !this.isRemoved() && pos.lengthSqr() > 0.0D) {
++ if (this.isRemoved()) return; // CraftBukkit
++
++ // CraftBukkit start - fire break events
++ // TODO - Does this need its own cause? Seems to only be triggered by pistons
++ HangingBreakEvent event = new HangingBreakEvent((Hanging) this.getBukkitEntity(), HangingBreakEvent.RemoveCause.PHYSICS);
++ this.level().getCraftServer().getPluginManager().callEvent(event);
++
++ if (this.isRemoved() || event.isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
++
+ this.kill();
+ this.dropItem((Entity) null);
+ }
+@@ -192,16 +249,23 @@
+ }
+
+ @Override
+- @Override
+- public void push(double d0, double d1, double d2) {
+- if (!this.level().isClientSide && !this.isRemoved() && d0 * d0 + d1 * d1 + d2 * d2 > 0.0D) {
++ public void push(double x, double d1, double y) {
++ if (false && !this.level().isClientSide && !this.isRemoved() && x * x + d1 * d1 + y * y > 0.0D) { // CraftBukkit - not needed
+ this.kill();
+ this.dropItem((Entity) null);
+ }
+
+ }
+
++ // CraftBukkit start - selectively save tile position
+ @Override
++ public void addAdditionalSaveData(CompoundTag nbttagcompound, boolean includeAll) {
++ if (includeAll) {
++ addAdditionalSaveData(nbttagcompound);
++ }
++ }
++ // CraftBukkit end
++
+ @Override
+ public void addAdditionalSaveData(CompoundTag compoundtag) {
+ BlockPos blockpos = this.getPos();
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/decoration/ItemFrame.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/decoration/ItemFrame.java.patch
new file mode 100644
index 0000000000..1097eada2c
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/decoration/ItemFrame.java.patch
@@ -0,0 +1,65 @@
+--- a/net/minecraft/world/entity/decoration/ItemFrame.java
++++ b/net/minecraft/world/entity/decoration/ItemFrame.java
+@@ -100,6 +96,15 @@
+ @Override
+ protected void recalculateBoundingBox() {
+ if (this.direction != null) {
++ // CraftBukkit start code moved in to calculateBoundingBox
++ this.setBoundingBox(calculateBoundingBox(this, this.pos, this.direction, this.getWidth(), this.getHeight()));
++ // CraftBukkit end
++ }
++ }
++
++ // CraftBukkit start - break out BB calc into own method
++ public static AABB calculateBoundingBox(@Nullable Entity entity, BlockPos blockPosition, Direction direction, int width, int height) {
++ {
+ double d0 = 0.46875D;
+ double d1 = (double) this.pos.getX() + 0.5D - (double) this.direction.getStepX() * 0.46875D;
+ double d2 = (double) this.pos.getY() + 0.5D - (double) this.direction.getStepY() * 0.46875D;
+@@ -128,6 +135,7 @@
+ this.setBoundingBox(new AABB(d1 - d4, d2 - d5, d3 - d6, d1 + d4, d2 + d5, d3 + d6));
+ }
+ }
++ // CraftBukkit end
+
+ @Override
+ @Override
+@@ -177,8 +180,13 @@
+ return false;
+ } else if (!damagesource.is(DamageTypeTags.IS_EXPLOSION) && !this.getItem().isEmpty()) {
+ if (!this.level().isClientSide) {
+- this.dropItem(damagesource.getEntity(), false);
+- this.gameEvent(GameEvent.BLOCK_CHANGE, damagesource.getEntity());
++ // CraftBukkit start - fire EntityDamageEvent
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, source, amount, false) || this.isRemoved()) {
++ return true;
++ }
++ // CraftBukkit end
++ this.dropItem(source.getEntity(), false);
++ this.gameEvent(GameEvent.BLOCK_CHANGE, source.getEntity());
+ this.playSound(this.getRemoveItemSound(), 1.0F, 1.0F);
+ }
+
+@@ -310,14 +313,20 @@
+ this.setItem(itemstack, true);
+ }
+
+- public void setItem(ItemStack itemstack, boolean flag) {
++ public void setItem(ItemStack stack, boolean updateNeighbours) {
++ // CraftBukkit start
++ this.setItem(stack, updateNeighbours, true);
++ }
++
++ public void setItem(ItemStack itemstack, boolean flag, boolean playSound) {
++ // CraftBukkit end
+ if (!itemstack.isEmpty()) {
+ itemstack = itemstack.copyWithCount(1);
+ }
+
+ this.onItemChanged(itemstack);
+ this.getEntityData().set(ItemFrame.DATA_ITEM, itemstack);
+- if (!itemstack.isEmpty()) {
++ if (!itemstack.isEmpty() && playSound) { // CraftBukkit
+ this.playSound(this.getAddItemSound(), 1.0F, 1.0F);
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java.patch
new file mode 100644
index 0000000000..12bf5ad58a
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java.patch
@@ -0,0 +1,65 @@
+--- a/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java
++++ b/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java
+@@ -25,6 +27,8 @@
+ import net.minecraft.world.level.gameevent.GameEvent;
+ import net.minecraft.world.phys.AABB;
+ import net.minecraft.world.phys.Vec3;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++// CraftBukkit end
+
+ public class LeashFenceKnotEntity extends HangingEntity {
+
+@@ -105,8 +99,14 @@
+ while (iterator.hasNext()) {
+ Mob mob = (Mob) iterator.next();
+
+- if (mob.getLeashHolder() == player) {
+- mob.setLeashedTo(this, true);
++ if (entityinsentient.getLeashHolder() == player) {
++ // CraftBukkit start
++ if (CraftEventFactory.callPlayerLeashEntityEvent(entityinsentient, this, player, hand).isCancelled()) {
++ ((ServerPlayer) player).connection.send(new ClientboundSetEntityLinkPacket(entityinsentient, entityinsentient.getLeashHolder()));
++ continue;
++ }
++ // CraftBukkit end
++ entityinsentient.setLeashedTo(this, true);
+ flag = true;
+ }
+ }
+@@ -114,18 +114,32 @@
+ boolean flag1 = false;
+
+ if (!flag) {
+- this.discard();
+- if (player.getAbilities().instabuild) {
++ // CraftBukkit start - Move below
++ // this.discard();
++ boolean die = true;
++ // CraftBukkit end
++ if (true || player.getAbilities().instabuild) { // CraftBukkit - Process for non-creative as well
+ Iterator iterator1 = list.iterator();
+
+ while (iterator1.hasNext()) {
+ Mob mob1 = (Mob) iterator1.next();
+
+- if (mob1.isLeashed() && mob1.getLeashHolder() == this) {
+- mob1.dropLeash(true, false);
++ if (entityinsentient1.isLeashed() && entityinsentient1.getLeashHolder() == this) {
++ // CraftBukkit start
++ if (CraftEventFactory.callPlayerUnleashEntityEvent(entityinsentient1, player, hand).isCancelled()) {
++ die = false;
++ continue;
++ }
++ entityinsentient1.dropLeash(true, !player.getAbilities().instabuild); // false -> survival mode boolean
++ // CraftBukkit end
+ flag1 = true;
+ }
+ }
++ // CraftBukkit start
++ if (die) {
++ this.discard();
++ }
++ // CraftBukkit end
+ }
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/item/FallingBlockEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/item/FallingBlockEntity.java.patch
new file mode 100644
index 0000000000..2a0c5ba3a4
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/item/FallingBlockEntity.java.patch
@@ -0,0 +1,65 @@
+--- a/net/minecraft/world/entity/item/FallingBlockEntity.java
++++ b/net/minecraft/world/entity/item/FallingBlockEntity.java
+@@ -49,6 +49,10 @@
+ import net.minecraft.world.phys.Vec3;
+ import org.slf4j.Logger;
+
++// CraftBukkit start;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++// CraftBukkit end
++
+ public class FallingBlockEntity extends Entity {
+
+ private static final Logger LOGGER = LogUtils.getLogger();
+@@ -82,12 +86,19 @@
+ this.setStartPos(this.blockPosition());
+ }
+
+- public static FallingBlockEntity fall(Level level, BlockPos blockpos, BlockState blockstate) {
+- FallingBlockEntity fallingblockentity = new FallingBlockEntity(level, (double) blockpos.getX() + 0.5D, (double) blockpos.getY(), (double) blockpos.getZ() + 0.5D, blockstate.hasProperty(BlockStateProperties.WATERLOGGED) ? (BlockState) blockstate.setValue(BlockStateProperties.WATERLOGGED, false) : blockstate);
++ public static FallingBlockEntity fall(Level level, BlockPos pos, IBlockData blockState) {
++ // CraftBukkit start
++ return fall(level, pos, blockState, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
++ }
+
+- level.setBlock(blockpos, blockstate.getFluidState().createLegacyBlock(), 3);
+- level.addFreshEntity(fallingblockentity);
+- return fallingblockentity;
++ public static FallingBlockEntity fall(Level world, BlockPos blockposition, IBlockData iblockdata, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason) {
++ // CraftBukkit end
++ FallingBlockEntity entityfallingblock = new FallingBlockEntity(world, (double) blockposition.getX() + 0.5D, (double) blockposition.getY(), (double) blockposition.getZ() + 0.5D, iblockdata.hasProperty(BlockStateProperties.WATERLOGGED) ? (IBlockData) iblockdata.setValue(BlockStateProperties.WATERLOGGED, false) : iblockdata);
++ if (!CraftEventFactory.callEntityChangeBlockEvent(entityfallingblock, blockposition, iblockdata.getFluidState().createLegacyBlock())) return entityfallingblock; // CraftBukkit
++
++ world.setBlock(blockposition, iblockdata.getFluidState().createLegacyBlock(), 3);
++ world.addFreshEntity(entityfallingblock, spawnReason); // CraftBukkit
++ return entityfallingblock;
+ }
+
+ @Override
+@@ -174,8 +180,14 @@
+ this.blockState = (BlockState) this.blockState.setValue(BlockStateProperties.WATERLOGGED, true);
+ }
+
+- if (this.level().setBlock(blockpos, this.blockState, 3)) {
+- ((ServerLevel) this.level()).getChunkSource().chunkMap.broadcast(this, new ClientboundBlockUpdatePacket(blockpos, this.level().getBlockState(blockpos)));
++ // CraftBukkit start
++ if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, this.blockState)) {
++ this.discard(); // SPIGOT-6586 called before the event in previous versions
++ return;
++ }
++ // CraftBukkit end
++ if (this.level().setBlock(blockposition, this.blockState, 3)) {
++ ((ServerLevel) this.level()).getChunkSource().chunkMap.broadcast(this, new ClientboundBlockUpdatePacket(blockposition, this.level().getBlockState(blockposition)));
+ this.discard();
+ if (block instanceof Fallable) {
+ ((Fallable) block).onLand(this.level(), blockpos, this.blockState, blockstate, this);
+@@ -261,7 +272,9 @@
+ float f2 = (float) Math.min(Mth.floor((float) i * this.fallDamagePerDistance), this.fallDamageMax);
+
+ this.level().getEntities((Entity) this, this.getBoundingBox(), predicate).forEach((entity) -> {
++ CraftEventFactory.entityDamage = this; // CraftBukkit
+ entity.hurt(damagesource2, f2);
++ CraftEventFactory.entityDamage = null; // CraftBukkit
+ });
+ boolean flag = this.blockState.is(BlockTags.ANVIL);
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/item/ItemEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/item/ItemEntity.java.patch
new file mode 100644
index 0000000000..e36cc3a090
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/item/ItemEntity.java.patch
@@ -0,0 +1,162 @@
+--- a/net/minecraft/world/entity/item/ItemEntity.java
++++ b/net/minecraft/world/entity/item/ItemEntity.java
+@@ -30,6 +20,22 @@
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.gameevent.GameEvent;
+ import net.minecraft.world.phys.Vec3;
++import net.minecraft.core.BlockPos;
++import net.minecraft.nbt.CompoundTag;
++import net.minecraft.network.chat.Component;
++import net.minecraft.network.syncher.EntityDataAccessor;
++import net.minecraft.network.syncher.EntityDataSerializers;
++import net.minecraft.network.syncher.SynchedEntityData;
++// CraftBukkit start
++import net.minecraft.server.MinecraftServer;
++import net.minecraft.server.level.ServerLevel;
++import net.minecraft.sounds.SoundSource;
++import net.minecraft.stats.Stats;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.entity.Player;
++import org.bukkit.event.entity.EntityPickupItemEvent;
++import org.bukkit.event.player.PlayerPickupItemEvent;
++// CraftBukkit end
+
+ public class ItemEntity extends Entity implements TraceableEntity {
+
+@@ -47,6 +53,7 @@
+ @Nullable
+ private UUID target;
+ public final float bobOffs;
++ private int lastTick = MinecraftServer.currentTick - 1; // CraftBukkit
+
+ public ItemEntity(EntityType<? extends ItemEntity> entitytype, Level level) {
+ super(entitytype, level);
+@@ -134,9 +135,12 @@
+ this.discard();
+ } else {
+ super.tick();
+- if (this.pickupDelay > 0 && this.pickupDelay != 32767) {
+- --this.pickupDelay;
+- }
++ // CraftBukkit start - Use wall time for pickup and despawn timers
++ int elapsedTicks = MinecraftServer.currentTick - this.lastTick;
++ if (this.pickupDelay != 32767) this.pickupDelay -= elapsedTicks;
++ if (this.age != -32768) this.age += elapsedTicks;
++ this.lastTick = MinecraftServer.currentTick;
++ // CraftBukkit end
+
+ this.xo = this.getX();
+ this.yo = this.getY();
+@@ -186,9 +190,11 @@
+ this.mergeWithNeighbours();
+ }
+
++ /* CraftBukkit start - moved up
+ if (this.age != -32768) {
+ ++this.age;
+ }
++ // CraftBukkit end */
+
+ this.hasImpulse |= this.updateInWaterStateAndDoFluidPushing();
+ if (!this.level().isClientSide) {
+@@ -200,6 +206,12 @@
+ }
+
+ if (!this.level().isClientSide && this.age >= 6000) {
++ // CraftBukkit start - fire ItemDespawnEvent
++ if (CraftEventFactory.callItemDespawnEvent(this).isCancelled()) {
++ this.age = 0;
++ return;
++ }
++ // CraftBukkit end
+ this.discard();
+ }
+
+@@ -283,13 +294,18 @@
+ itementity.setItem(itemstack2);
+ }
+
+- private static void merge(ItemEntity itementity, ItemStack itemstack, ItemEntity itementity1, ItemStack itemstack1) {
+- merge(itementity, itemstack, itemstack1);
+- itementity.pickupDelay = Math.max(itementity.pickupDelay, itementity1.pickupDelay);
+- itementity.age = Math.min(itementity.age, itementity1.age);
+- if (itemstack1.isEmpty()) {
+- itementity1.discard();
++ private static void merge(ItemEntity destinationEntity, ItemStack destinationStack, ItemEntity originEntity, ItemStack originStack) {
++ // CraftBukkit start
++ if (!CraftEventFactory.callItemMergeEvent(originEntity, destinationEntity)) {
++ return;
+ }
++ // CraftBukkit end
++ merge(destinationEntity, destinationStack, originStack);
++ destinationEntity.pickupDelay = Math.max(destinationEntity.pickupDelay, originEntity.pickupDelay);
++ destinationEntity.age = Math.min(destinationEntity.age, originEntity.age);
++ if (originStack.isEmpty()) {
++ originEntity.discard();
++ }
+
+ }
+
+@@ -311,6 +325,11 @@
+ } else if (this.level().isClientSide) {
+ return true;
+ } else {
++ // CraftBukkit start
++ if (CraftEventFactory.handleNonLivingEntityDamageEvent(this, source, amount)) {
++ return false;
++ }
++ // CraftBukkit end
+ this.markHurt();
+ this.health = (int) ((float) this.health - f);
+ this.gameEvent(GameEvent.ENTITY_DAMAGE, damagesource.getEntity());
+@@ -378,8 +394,48 @@
+ Item item = itemstack.getItem();
+ int i = itemstack.getCount();
+
+- if (this.pickupDelay == 0 && (this.target == null || this.target.equals(player.getUUID())) && player.getInventory().add(itemstack)) {
+- player.take(this, i);
++ // CraftBukkit start - fire PlayerPickupItemEvent
++ int canHold = entity.getInventory().canHold(itemstack);
++ int remaining = i - canHold;
++
++ if (this.pickupDelay <= 0 && canHold > 0) {
++ itemstack.setCount(canHold);
++ // Call legacy event
++ PlayerPickupItemEvent playerEvent = new PlayerPickupItemEvent((Player) entity.getBukkitEntity(), (org.bukkit.entity.Item) this.getBukkitEntity(), remaining);
++ playerEvent.setCancelled(!playerEvent.getPlayer().getCanPickupItems());
++ this.level().getCraftServer().getPluginManager().callEvent(playerEvent);
++ if (playerEvent.isCancelled()) {
++ itemstack.setCount(i); // SPIGOT-5294 - restore count
++ return;
++ }
++
++ // Call newer event afterwards
++ EntityPickupItemEvent entityEvent = new EntityPickupItemEvent((Player) entity.getBukkitEntity(), (org.bukkit.entity.Item) this.getBukkitEntity(), remaining);
++ entityEvent.setCancelled(!entityEvent.getEntity().getCanPickupItems());
++ this.level().getCraftServer().getPluginManager().callEvent(entityEvent);
++ if (entityEvent.isCancelled()) {
++ itemstack.setCount(i); // SPIGOT-5294 - restore count
++ return;
++ }
++
++ // Update the ItemStack if it was changed in the event
++ ItemStack current = this.getItem();
++ if (!itemstack.equals(current)) {
++ itemstack = current;
++ } else {
++ itemstack.setCount(canHold + remaining); // = i
++ }
++
++ // Possibly < 0; fix here so we do not have to modify code below
++ this.pickupDelay = 0;
++ } else if (this.pickupDelay == 0) {
++ // ensure that the code below isn't triggered if canHold says we can't pick the items up
++ this.pickupDelay = -1;
++ }
++ // CraftBukkit end
++
++ if (this.pickupDelay == 0 && (this.target == null || this.target.equals(entity.getUUID())) && entity.getInventory().add(itemstack)) {
++ entity.take(this, i);
+ if (itemstack.isEmpty()) {
+ this.discard();
+ itemstack.setCount(i);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/item/PrimedTnt.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/item/PrimedTnt.java.patch
new file mode 100644
index 0000000000..ece1ce8332
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/item/PrimedTnt.java.patch
@@ -0,0 +1,57 @@
+--- a/net/minecraft/world/entity/item/PrimedTnt.java
++++ b/net/minecraft/world/entity/item/PrimedTnt.java
+@@ -19,6 +19,11 @@
+ import net.minecraft.world.level.block.Blocks;
+ import net.minecraft.world.level.block.state.BlockState;
+
++// CraftBukkit start;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.ExplosionPrimeEvent;
++// CraftBukkit end
++
+ public class PrimedTnt extends Entity implements TraceableEntity {
+
+ private static final EntityDataAccessor<Integer> DATA_FUSE_ID = SynchedEntityData.defineId(PrimedTnt.class, EntityDataSerializers.INT);
+@@ -27,7 +32,9 @@
+ private static final String TAG_BLOCK_STATE = "block_state";
+ public static final String TAG_FUSE = "fuse";
+ @Nullable
+- private LivingEntity owner;
++ public LivingEntity owner;
++ public float yield = 4; // CraftBukkit - add field
++ public boolean isIncendiary = false; // CraftBukkit - add field
+
+ public PrimedTnt(EntityType<? extends PrimedTnt> entitytype, Level level) {
+ super(entitytype, level);
+@@ -83,10 +86,13 @@
+
+ this.setFuse(i);
+ if (i <= 0) {
+- this.discard();
++ // CraftBukkit start - Need to reverse the order of the explosion and the entity death so we have a location for the event
++ // this.discard();
+ if (!this.level().isClientSide) {
+ this.explode();
+ }
++ this.discard();
++ // CraftBukkit end
+ } else {
+ this.updateInWaterStateAndDoFluidPushing();
+ if (this.level().isClientSide) {
+@@ -97,9 +103,14 @@
+ }
+
+ private void explode() {
+- float f = 4.0F;
++ // CraftBukkit start
++ // float f = 4.0F;
++ ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent((org.bukkit.entity.Explosive)this.getBukkitEntity());
+
+- this.level().explode(this, this.getX(), this.getY(0.0625D), this.getZ(), 4.0F, Level.ExplosionInteraction.TNT);
++ if (!event.isCancelled()) {
++ this.level().explode(this, this.getX(), this.getY(0.0625D), this.getZ(), event.getRadius(), event.getFire(), Level.a.TNT);
++ }
++ // CraftBukkit end
+ }
+
+ @Override
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch
new file mode 100644
index 0000000000..88a4ace98b
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch
@@ -0,0 +1,25 @@
+--- a/net/minecraft/world/entity/monster/AbstractSkeleton.java
++++ b/net/minecraft/world/entity/monster/AbstractSkeleton.java
+@@ -209,9 +199,20 @@
+ double d2 = livingentity.getZ() - this.getZ();
+ double d3 = Math.sqrt(d0 * d0 + d2 * d2);
+
+- abstractarrow.shoot(d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, (float) (14 - this.level().getDifficulty().getId() * 4));
++ entityarrow.shoot(d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, (float) (14 - this.level().getDifficulty().getId() * 4));
++ // CraftBukkit start
++ org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, this.getMainHandItem(), null, entityarrow, net.minecraft.world.EnumHand.MAIN_HAND, 0.8F, true);
++ if (event.isCancelled()) {
++ event.getProjectile().remove();
++ return;
++ }
++
++ if (event.getProjectile() == entityarrow.getBukkitEntity()) {
++ this.level().addFreshEntity(entityarrow);
++ }
++ // CraftBukkit end
+ this.playSound(SoundEvents.SKELETON_SHOOT, 1.0F, 1.0F / (this.getRandom().nextFloat() * 0.4F + 0.8F));
+- this.level().addFreshEntity(abstractarrow);
++ // this.level().addFreshEntity(entityarrow); // CraftBukkit - moved up
+ }
+
+ protected AbstractArrow getArrow(ItemStack itemstack, float f) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/CaveSpider.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/CaveSpider.java.patch
new file mode 100644
index 0000000000..5af24b6b00
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/CaveSpider.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/monster/CaveSpider.java
++++ b/net/minecraft/world/entity/monster/CaveSpider.java
+@@ -43,7 +42,7 @@
+ }
+
+ if (b0 > 0) {
+- ((LivingEntity) entity).addEffect(new MobEffectInstance(MobEffects.POISON, b0 * 20, 0), this);
++ ((LivingEntity) entity).addEffect(new MobEffectInstance(MobEffects.POISON, b0 * 20, 0), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+ }
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Creeper.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Creeper.java.patch
new file mode 100644
index 0000000000..36a4a02c69
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Creeper.java.patch
@@ -0,0 +1,98 @@
+--- a/net/minecraft/world/entity/monster/Creeper.java
++++ b/net/minecraft/world/entity/monster/Creeper.java
+@@ -43,6 +43,12 @@
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.gameevent.GameEvent;
+
++// CraftBukkit start;
++import org.bukkit.event.entity.CreatureSpawnEvent;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.ExplosionPrimeEvent;
++// CraftBukkit end
++
+ public class Creeper extends Monster implements PowerableMob {
+
+ private static final EntityDataAccessor<Integer> DATA_SWELL_DIR = SynchedEntityData.defineId(Creeper.class, EntityDataSerializers.INT);
+@@ -229,12 +222,22 @@
+ }
+
+ @Override
+- @Override
+- public void thunderHit(ServerLevel serverlevel, LightningBolt lightningbolt) {
+- super.thunderHit(serverlevel, lightningbolt);
++ public void thunderHit(ServerLevel level, LightningBolt lightning) {
++ super.thunderHit(level, lightning);
++ // CraftBukkit start
++ if (CraftEventFactory.callCreeperPowerEvent(this, lightning, org.bukkit.event.entity.CreeperPowerEvent.PowerCause.LIGHTNING).isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
+ this.entityData.set(Creeper.DATA_IS_POWERED, true);
+ }
+
++ // CraftBukkit start
++ public void setPowered(boolean powered) {
++ this.entityData.set(Creeper.DATA_IS_POWERED, powered);
++ }
++ // CraftBukkit end
++
+ @Override
+ @Override
+ protected InteractionResult mobInteract(Player player, InteractionHand interactionhand) {
+@@ -246,7 +248,7 @@
+ this.level().playSound(player, this.getX(), this.getY(), this.getZ(), soundevent, this.getSoundSource(), 1.0F, this.random.nextFloat() * 0.4F + 0.8F);
+ if (!this.level().isClientSide) {
+ this.ignite();
+- if (!itemstack.isDamageableItem()) {
++ if (itemstack.getItem().getMaxDamage() == 0) { // CraftBukkit - fix MC-264285: unbreakable flint and steels are completely consumed when igniting a creeper
+ itemstack.shrink(1);
+ } else {
+ itemstack.hurtAndBreak(1, player, (player1) -> {
+@@ -265,10 +267,19 @@
+ if (!this.level().isClientSide) {
+ float f = this.isPowered() ? 2.0F : 1.0F;
+
++ // CraftBukkit start
++ ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent(this, this.explosionRadius * f, false);
++ if (!event.isCancelled()) {
++ // CraftBukkit end
+ this.dead = true;
+- this.level().explode(this, this.getX(), this.getY(), this.getZ(), (float) this.explosionRadius * f, Level.ExplosionInteraction.MOB);
++ this.level().explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.a.MOB); // CraftBukkit
+ this.discard();
+ this.spawnLingeringCloud();
++ // CraftBukkit start
++ } else {
++ swell = 0;
++ }
++ // CraftBukkit end
+ }
+
+ }
+@@ -279,11 +290,12 @@
+ if (!collection.isEmpty()) {
+ AreaEffectCloud areaeffectcloud = new AreaEffectCloud(this.level(), this.getX(), this.getY(), this.getZ());
+
+- areaeffectcloud.setRadius(2.5F);
+- areaeffectcloud.setRadiusOnUse(-0.5F);
+- areaeffectcloud.setWaitTime(10);
+- areaeffectcloud.setDuration(areaeffectcloud.getDuration() / 2);
+- areaeffectcloud.setRadiusPerTick(-areaeffectcloud.getRadius() / (float) areaeffectcloud.getDuration());
++ entityareaeffectcloud.setOwner(this); // CraftBukkit
++ entityareaeffectcloud.setRadius(2.5F);
++ entityareaeffectcloud.setRadiusOnUse(-0.5F);
++ entityareaeffectcloud.setWaitTime(10);
++ entityareaeffectcloud.setDuration(entityareaeffectcloud.getDuration() / 2);
++ entityareaeffectcloud.setRadiusPerTick(-entityareaeffectcloud.getRadius() / (float) entityareaeffectcloud.getDuration());
+ Iterator iterator = collection.iterator();
+
+ while (iterator.hasNext()) {
+@@ -292,7 +304,7 @@
+ areaeffectcloud.addEffect(new MobEffectInstance(mobeffectinstance));
+ }
+
+- this.level().addFreshEntity(areaeffectcloud);
++ this.level().addFreshEntity(entityareaeffectcloud, CreatureSpawnEvent.SpawnReason.EXPLOSION); // CraftBukkit
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Drowned.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Drowned.java.patch
new file mode 100644
index 0000000000..67ee44fa5b
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Drowned.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/entity/monster/Drowned.java
++++ b/net/minecraft/world/entity/monster/Drowned.java
+@@ -260,12 +243,11 @@
+ }
+
+ @Override
+- @Override
+- public void performRangedAttack(LivingEntity livingentity, float f) {
+- ThrownTrident throwntrident = new ThrownTrident(this.level(), this, new ItemStack(Items.TRIDENT));
+- double d0 = livingentity.getX() - this.getX();
+- double d1 = livingentity.getY(0.3333333333333333D) - throwntrident.getY();
+- double d2 = livingentity.getZ() - this.getZ();
++ public void performRangedAttack(LivingEntity target, float distanceFactor) {
++ ThrownTrident entitythrowntrident = new ThrownTrident(this.level(), this, this.getItemInHand(net.minecraft.world.entity.projectile.ProjectileUtil.getWeaponHoldingHand(this, Items.TRIDENT))); // CraftBukkit - Use Trident in hand like skeletons (SPIGOT-7025)
++ double d0 = target.getX() - this.getX();
++ double d1 = target.getY(0.3333333333333333D) - entitythrowntrident.getY();
++ double d2 = target.getZ() - this.getZ();
+ double d3 = Math.sqrt(d0 * d0 + d2 * d2);
+
+ throwntrident.shoot(d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, (float) (14 - this.level().getDifficulty().getId() * 4));
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/ElderGuardian.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/ElderGuardian.java.patch
new file mode 100644
index 0000000000..c15872accd
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/ElderGuardian.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/world/entity/monster/ElderGuardian.java
++++ b/net/minecraft/world/entity/monster/ElderGuardian.java
+@@ -75,8 +69,8 @@
+ protected void customServerAiStep() {
+ super.customServerAiStep();
+ if ((this.tickCount + this.getId()) % 1200 == 0) {
+- MobEffectInstance mobeffectinstance = new MobEffectInstance(MobEffects.DIG_SLOWDOWN, 6000, 2);
+- List<ServerPlayer> list = MobEffectUtil.addEffectToPlayersAround((ServerLevel) this.level(), this, this.position(), 50.0D, mobeffectinstance, 1200);
++ MobEffectInstance mobeffect = new MobEffectInstance(MobEffects.DIG_SLOWDOWN, 6000, 2);
++ List<ServerPlayer> list = MobEffectUtil.addEffectToPlayersAround((ServerLevel) this.level(), this, this.position(), 50.0D, mobeffect, 1200, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+
+ list.forEach((serverplayer) -> {
+ serverplayer.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.GUARDIAN_ELDER_EFFECT, this.isSilent() ? 0.0F : 1.0F));
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/EnderMan.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/EnderMan.java.patch
new file mode 100644
index 0000000000..93aa700933
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/EnderMan.java.patch
@@ -0,0 +1,75 @@
+--- a/net/minecraft/world/entity/monster/EnderMan.java
++++ b/net/minecraft/world/entity/monster/EnderMan.java
+@@ -71,6 +71,11 @@
+ import net.minecraft.world.phys.Vec3;
+ import org.joml.Vector3f;
+
++// CraftBukkit start;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityTargetEvent;
++// CraftBukkit end
++
+ public class EnderMan extends Monster implements NeutralMob {
+
+ private static final UUID SPEED_MODIFIER_ATTACKING_UUID = UUID.fromString("020E0DFB-87AE-4653-9556-831010E291A0");
+@@ -115,10 +119,19 @@
+ }
+
+ @Override
++ public void setTarget(@Nullable LivingEntity livingEntity) {
++ // CraftBukkit start - fire event
++ setTarget(livingEntity, EntityTargetEvent.TargetReason.UNKNOWN, true);
++ }
++
+ @Override
+- public void setTarget(@Nullable LivingEntity livingentity) {
+- super.setTarget(livingentity);
+- AttributeInstance attributeinstance = this.getAttribute(Attributes.MOVEMENT_SPEED);
++ public boolean setTarget(LivingEntity entityliving, EntityTargetEvent.TargetReason reason, boolean fireEvent) {
++ if (!super.setTarget(entityliving, reason, fireEvent)) {
++ return false;
++ }
++ entityliving = getTarget();
++ // CraftBukkit end
++ AttributeInstance attributemodifiable = this.getAttribute(Attributes.MOVEMENT_SPEED);
+
+ if (livingentity == null) {
+ this.targetChangeTime = 0;
+@@ -517,12 +506,14 @@
+ BlockState blockstate1 = level.getBlockState(blockpos1);
+ BlockState blockstate2 = this.enderman.getCarriedBlock();
+
+- if (blockstate2 != null) {
+- blockstate2 = Block.updateFromNeighbourShapes(blockstate2, this.enderman.level(), blockpos);
+- if (this.canPlaceBlock(level, blockpos, blockstate2, blockstate, blockstate1, blockpos1)) {
+- level.setBlock(blockpos, blockstate2, 3);
+- level.gameEvent(GameEvent.BLOCK_PLACE, blockpos, GameEvent.Context.of(this.enderman, blockstate2));
+- this.enderman.setCarriedBlock((BlockState) null);
++ if (iblockdata2 != null) {
++ iblockdata2 = Block.updateFromNeighbourShapes(iblockdata2, this.enderman.level(), blockposition);
++ if (this.canPlaceBlock(world, blockposition, iblockdata2, iblockdata, iblockdata1, blockposition1)) {
++ if (CraftEventFactory.callEntityChangeBlockEvent(this.enderman, blockposition, iblockdata2)) { // CraftBukkit - Place event
++ world.setBlock(blockposition, iblockdata2, 3);
++ world.gameEvent(GameEvent.BLOCK_PLACE, blockposition, GameEvent.Context.of(this.enderman, iblockdata2));
++ this.enderman.setCarriedBlock((IBlockData) null);
++ } // CraftBukkit
+ }
+
+ }
+@@ -562,10 +551,12 @@
+ BlockHitResult blockhitresult = level.clip(new ClipContext(vec3, vec31, ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, this.enderman));
+ boolean flag = blockhitresult.getBlockPos().equals(blockpos);
+
+- if (blockstate.is(BlockTags.ENDERMAN_HOLDABLE) && flag) {
+- level.removeBlock(blockpos, false);
+- level.gameEvent(GameEvent.BLOCK_DESTROY, blockpos, GameEvent.Context.of(this.enderman, blockstate));
+- this.enderman.setCarriedBlock(blockstate.getBlock().defaultBlockState());
++ if (iblockdata.is(BlockTags.ENDERMAN_HOLDABLE) && flag) {
++ if (CraftEventFactory.callEntityChangeBlockEvent(this.enderman, blockposition, Blocks.AIR.defaultBlockState())) { // CraftBukkit - Place event
++ world.removeBlock(blockposition, false);
++ world.gameEvent(GameEvent.BLOCK_DESTROY, blockposition, GameEvent.Context.of(this.enderman, iblockdata));
++ this.enderman.setCarriedBlock(iblockdata.getBlock().defaultBlockState());
++ } // CraftBukkit
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Evoker.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Evoker.java.patch
new file mode 100644
index 0000000000..9eafb5e220
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Evoker.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/world/entity/monster/Evoker.java
++++ b/net/minecraft/world/entity/monster/Evoker.java
+@@ -217,8 +200,8 @@
+ serverlevel.getScoreboard().addPlayerToTeam(vex.getScoreboardName(), playerteam);
+ }
+
+- serverlevel.addFreshEntityWithPassengers(vex);
+- serverlevel.gameEvent(GameEvent.ENTITY_PLACE, blockpos, GameEvent.Context.of((Entity) Evoker.this));
++ worldserver.addFreshEntityWithPassengers(entityvex, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPELL); // CraftBukkit - Add SpawnReason
++ worldserver.gameEvent(GameEvent.ENTITY_PLACE, blockposition, GameEvent.Context.of((Entity) Evoker.this));
+ }
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Ghast.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Ghast.java.patch
new file mode 100644
index 0000000000..d4f69001e5
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Ghast.java.patch
@@ -0,0 +1,15 @@
+--- a/net/minecraft/world/entity/monster/Ghast.java
++++ b/net/minecraft/world/entity/monster/Ghast.java
+@@ -376,8 +348,10 @@
+
+ LargeFireball largefireball = new LargeFireball(level, this.ghast, d2, d3, d4, this.ghast.getExplosionPower());
+
+- largefireball.setPos(this.ghast.getX() + vec3.x * 4.0D, this.ghast.getY(0.5D) + 0.5D, largefireball.getZ() + vec3.z * 4.0D);
+- level.addFreshEntity(largefireball);
++ // CraftBukkit - set bukkitYield when setting explosionpower
++ entitylargefireball.bukkitYield = entitylargefireball.explosionPower = this.ghast.getExplosionPower();
++ entitylargefireball.setPos(this.ghast.getX() + vec3d.x * 4.0D, this.ghast.getY(0.5D) + 0.5D, entitylargefireball.getZ() + vec3d.z * 4.0D);
++ world.addFreshEntity(entitylargefireball);
+ this.chargeTime = -40;
+ }
+ } else if (this.chargeTime > 0) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Guardian.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Guardian.java.patch
new file mode 100644
index 0000000000..f5f9f6ea39
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Guardian.java.patch
@@ -0,0 +1,23 @@
+--- a/net/minecraft/world/entity/monster/Guardian.java
++++ b/net/minecraft/world/entity/monster/Guardian.java
+@@ -62,7 +62,8 @@
+ private int clientSideAttackTime;
+ private boolean clientSideTouchedGround;
+ @Nullable
+- protected RandomStrollGoal randomStrollGoal;
++ public RandomStrollGoal randomStrollGoal;
++ public Guardian.GuardianAttackGoal guardianAttackGoal; // CraftBukkit - add field
+
+ public Guardian(EntityType<? extends Guardian> entitytype, Level level) {
+ super(entitytype, level);
+@@ -79,8 +79,8 @@
+ MoveTowardsRestrictionGoal movetowardsrestrictiongoal = new MoveTowardsRestrictionGoal(this, 1.0D);
+
+ this.randomStrollGoal = new RandomStrollGoal(this, 1.0D, 80);
+- this.goalSelector.addGoal(4, new Guardian.GuardianAttackGoal(this));
+- this.goalSelector.addGoal(5, movetowardsrestrictiongoal);
++ this.goalSelector.addGoal(4, guardianAttackGoal = new Guardian.GuardianAttackGoal(this)); // CraftBukkit - assign field
++ this.goalSelector.addGoal(5, pathfindergoalmovetowardsrestriction);
+ this.goalSelector.addGoal(7, this.randomStrollGoal);
+ this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F));
+ this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Guardian.class, 12.0F, 0.01F));
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Husk.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Husk.java.patch
new file mode 100644
index 0000000000..a4d25b959c
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Husk.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/monster/Husk.java
++++ b/net/minecraft/world/entity/monster/Husk.java
+@@ -66,7 +60,7 @@
+ if (flag && this.getMainHandItem().isEmpty() && entity instanceof LivingEntity) {
+ float f = this.level().getCurrentDifficultyAt(this.blockPosition()).getEffectiveDifficulty();
+
+- ((LivingEntity) entity).addEffect(new MobEffectInstance(MobEffects.HUNGER, 140 * (int) f), this);
++ ((LivingEntity) entity).addEffect(new MobEffectInstance(MobEffects.HUNGER, 140 * (int) f), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+ }
+
+ return flag;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Illusioner.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Illusioner.java.patch
new file mode 100644
index 0000000000..61b1f950ce
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Illusioner.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/entity/monster/Illusioner.java
++++ b/net/minecraft/world/entity/monster/Illusioner.java
+@@ -246,7 +228,7 @@
+ @Override
+ @Override
+ protected void performSpellCasting() {
+- Illusioner.this.addEffect(new MobEffectInstance(MobEffects.INVISIBILITY, 1200));
++ Illusioner.this.addEffect(new MobEffectInstance(MobEffects.INVISIBILITY, 1200), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ILLUSION); // CraftBukkit
+ }
+
+ @Nullable
+@@ -304,7 +279,7 @@
+ @Override
+ @Override
+ protected void performSpellCasting() {
+- Illusioner.this.getTarget().addEffect(new MobEffectInstance(MobEffects.BLINDNESS, 400), Illusioner.this);
++ Illusioner.this.getTarget().addEffect(new MobEffectInstance(MobEffects.BLINDNESS, 400), Illusioner.this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+ }
+
+ @Override
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Phantom.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Phantom.java.patch
new file mode 100644
index 0000000000..c80803c72b
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Phantom.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/world/entity/monster/Phantom.java
++++ b/net/minecraft/world/entity/monster/Phantom.java
+@@ -589,8 +549,8 @@
+ while (iterator.hasNext()) {
+ Player player = (Player) iterator.next();
+
+- if (Phantom.this.canAttack(player, TargetingConditions.DEFAULT)) {
+- Phantom.this.setTarget(player);
++ if (Phantom.this.canAttack(entityhuman, TargetingConditions.DEFAULT)) {
++ Phantom.this.setTarget(entityhuman, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER, true); // CraftBukkit - reason
+ return true;
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Ravager.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Ravager.java.patch
new file mode 100644
index 0000000000..ed04ec54ac
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Ravager.java.patch
@@ -0,0 +1,27 @@
+--- a/net/minecraft/world/entity/monster/Ravager.java
++++ b/net/minecraft/world/entity/monster/Ravager.java
+@@ -43,6 +43,10 @@
+ import net.minecraft.world.phys.Vec3;
+ import org.joml.Vector3f;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++// CraftBukkit end
++
+ public class Ravager extends Raider {
+
+ private static final Predicate<Entity> NO_RAVAGER_AND_ALIVE = (entity) -> {
+@@ -161,7 +157,12 @@
+ Block block = blockstate.getBlock();
+
+ if (block instanceof LeavesBlock) {
+- flag = this.level().destroyBlock(blockpos, true, this) || flag;
++ // CraftBukkit start
++ if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState())) {
++ continue;
++ }
++ // CraftBukkit end
++ flag = this.level().destroyBlock(blockposition, true, this) || flag;
+ }
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Shulker.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Shulker.java.patch
new file mode 100644
index 0000000000..2e8b3aa748
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Shulker.java.patch
@@ -0,0 +1,47 @@
+--- a/net/minecraft/world/entity/monster/Shulker.java
++++ b/net/minecraft/world/entity/monster/Shulker.java
+@@ -59,7 +59,11 @@
+ import net.minecraft.world.phys.Vec3;
+ import org.joml.Vector3f;
+
+-public class Shulker extends AbstractGolem implements VariantHolder<Optional<DyeColor>>, Enemy {
++// CraftBukkit start
++import org.bukkit.craftbukkit.util.CraftLocation;
++import org.bukkit.event.entity.EntityTeleportEvent;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++// CraftBukkit end
+
+ private static final UUID COVERED_ARMOR_MODIFIER_UUID = UUID.fromString("7E0292F2-9434-48D5-A29F-9583AF7DF27F");
+ private static final AttributeModifier COVERED_ARMOR_MODIFIER = new AttributeModifier(Shulker.COVERED_ARMOR_MODIFIER_UUID, "Covered armor bonus", 20.0D, AttributeModifier.Operation.ADDITION);
+@@ -419,7 +405,15 @@
+ if (blockpos1.getY() > this.level().getMinBuildHeight() && this.level().isEmptyBlock(blockpos1) && this.level().getWorldBorder().isWithinBounds(blockpos1) && this.level().noCollision(this, (new AABB(blockpos1)).deflate(1.0E-6D))) {
+ Direction direction = this.findAttachableSurface(blockpos1);
+
+- if (direction != null) {
++ if (enumdirection != null) {
++ // CraftBukkit start
++ EntityTeleportEvent teleportEvent = CraftEventFactory.callEntityTeleportEvent(this, blockposition1.getX(), blockposition1.getY(), blockposition1.getZ());
++ if (teleportEvent.isCancelled()) {
++ return false;
++ } else {
++ blockposition1 = CraftLocation.toBlockPosition(teleportEvent.getTo());
++ }
++ // CraftBukkit end
+ this.unRide();
+ this.setAttachFace(direction);
+ this.playSound(SoundEvents.SHULKER_TELEPORT, 1.0F, 1.0F);
+@@ -489,10 +481,10 @@
+ if (this.level().random.nextFloat() >= f) {
+ Shulker shulker = (Shulker) EntityType.SHULKER.create(this.level());
+
+- if (shulker != null) {
+- shulker.setVariant(this.getVariant());
+- shulker.moveTo(vec3);
+- this.level().addFreshEntity(shulker);
++ if (entityshulker != null) {
++ entityshulker.setVariant(this.getVariant());
++ entityshulker.moveTo(vec3d);
++ this.level().addFreshEntity(entityshulker, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING); // CraftBukkit - the mysteries of life
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Silverfish.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Silverfish.java.patch
new file mode 100644
index 0000000000..78592dad43
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Silverfish.java.patch
@@ -0,0 +1,45 @@
+--- a/net/minecraft/world/entity/monster/Silverfish.java
++++ b/net/minecraft/world/entity/monster/Silverfish.java
+@@ -34,6 +34,10 @@
+ import net.minecraft.world.level.block.state.BlockState;
+ import org.joml.Vector3f;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++// CraftBukkit end
++
+ public class Silverfish extends Monster {
+
+ @Nullable
+@@ -191,8 +180,13 @@
+ Block block = blockstate.getBlock();
+
+ if (block instanceof InfestedBlock) {
+- if (level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+- level.destroyBlock(blockpos1, true, this.silverfish);
++ // CraftBukkit start
++ if (!CraftEventFactory.callEntityChangeBlockEvent(this.silverfish, blockposition1, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState())) {
++ continue;
++ }
++ // CraftBukkit end
++ if (world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
++ world.destroyBlock(blockposition1, true, this.silverfish);
+ } else {
+ level.setBlock(blockpos1, ((InfestedBlock) block).hostStateByInfested(level.getBlockState(blockpos1)), 3);
+ }
+@@ -262,8 +253,13 @@
+ BlockPos blockpos = BlockPos.containing(this.mob.getX(), this.mob.getY() + 0.5D, this.mob.getZ()).relative(this.selectedDirection);
+ BlockState blockstate = level.getBlockState(blockpos);
+
+- if (InfestedBlock.isCompatibleHostBlock(blockstate)) {
+- level.setBlock(blockpos, InfestedBlock.infestedStateByHost(blockstate), 3);
++ if (InfestedBlock.isCompatibleHostBlock(iblockdata)) {
++ // CraftBukkit start
++ if (!CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition, InfestedBlock.infestedStateByHost(iblockdata))) {
++ return;
++ }
++ // CraftBukkit end
++ world.setBlock(blockposition, InfestedBlock.infestedStateByHost(iblockdata), 3);
+ this.mob.spawnAnim();
+ this.mob.discard();
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Skeleton.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Skeleton.java.patch
new file mode 100644
index 0000000000..c9a8225075
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Skeleton.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/monster/Skeleton.java
++++ b/net/minecraft/world/entity/monster/Skeleton.java
+@@ -95,7 +90,7 @@
+ }
+
+ protected void doFreezeConversion() {
+- this.convertTo(EntityType.STRAY, true);
++ this.convertTo(EntityType.STRAY, true, org.bukkit.event.entity.EntityTransformEvent.TransformReason.FROZEN, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.FROZEN); // CraftBukkit - add spawn and transform reasons
+ if (!this.isSilent()) {
+ this.level().levelEvent((Player) null, 1048, this.blockPosition(), 0);
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Slime.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Slime.java.patch
new file mode 100644
index 0000000000..ffdbc9be15
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Slime.java.patch
@@ -0,0 +1,67 @@
+--- a/net/minecraft/world/entity/monster/Slime.java
++++ b/net/minecraft/world/entity/monster/Slime.java
+@@ -44,7 +44,13 @@
+ import net.minecraft.world.phys.Vec3;
+ import org.joml.Vector3f;
+
+-public class Slime extends Mob implements Enemy {
++// CraftBukkit start
++import java.util.ArrayList;
++import java.util.List;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityTransformEvent;
++import org.bukkit.event.entity.SlimeSplitEvent;
++// CraftBukkit end
+
+ private static final EntityDataAccessor<Integer> ID_SIZE = SynchedEntityData.defineId(Slime.class, EntityDataSerializers.INT);
+ public static final int MIN_SIZE = 1;
+@@ -217,6 +214,19 @@
+ int j = i / 2;
+ int k = 2 + this.random.nextInt(3);
+
++ // CraftBukkit start
++ SlimeSplitEvent event = new SlimeSplitEvent((org.bukkit.entity.Slime) this.getBukkitEntity(), k);
++ this.level().getCraftServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled() && event.getCount() > 0) {
++ k = event.getCount();
++ } else {
++ super.remove(reason);
++ return;
++ }
++ List<LivingEntity> slimes = new ArrayList<>(j);
++ // CraftBukkit end
++
+ for (int l = 0; l < k; ++l) {
+ float f1 = ((float) (l % 2) - 0.5F) * f;
+ float f2 = ((float) (l / 2) - 0.5F) * f;
+@@ -227,14 +237,23 @@
+ slime.setPersistenceRequired();
+ }
+
+- slime.setCustomName(component);
+- slime.setNoAi(flag);
+- slime.setInvulnerable(this.isInvulnerable());
+- slime.setSize(j, true);
+- slime.moveTo(this.getX() + (double) f1, this.getY() + 0.5D, this.getZ() + (double) f2, this.random.nextFloat() * 360.0F, 0.0F);
+- this.level().addFreshEntity(slime);
++ entityslime.setCustomName(ichatbasecomponent);
++ entityslime.setNoAi(flag);
++ entityslime.setInvulnerable(this.isInvulnerable());
++ entityslime.setSize(j, true);
++ entityslime.moveTo(this.getX() + (double) f1, this.getY() + 0.5D, this.getZ() + (double) f2, this.random.nextFloat() * 360.0F, 0.0F);
++ slimes.add(entityslime); // CraftBukkit
+ }
+ }
++ // CraftBukkit start
++ if (CraftEventFactory.callEntityTransformEvent(this, slimes, EntityTransformEvent.TransformReason.SPLIT).isCancelled()) {
++ super.remove(reason);
++ return;
++ }
++ for (LivingEntity living : slimes) {
++ this.level().addFreshEntity(living, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SLIME_SPLIT); // CraftBukkit - SpawnReason
++ }
++ // CraftBukkit end
+ }
+
+ super.remove(entity_removalreason);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/SpellcasterIllager.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/SpellcasterIllager.java.patch
new file mode 100644
index 0000000000..f1eae8779a
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/SpellcasterIllager.java.patch
@@ -0,0 +1,24 @@
+--- a/net/minecraft/world/entity/monster/SpellcasterIllager.java
++++ b/net/minecraft/world/entity/monster/SpellcasterIllager.java
+@@ -15,6 +15,9 @@
+ import net.minecraft.world.entity.LivingEntity;
+ import net.minecraft.world.entity.ai.goal.Goal;
+ import net.minecraft.world.level.Level;
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++// CraftBukkit end
+
+ public abstract class SpellcasterIllager extends AbstractIllager {
+
+@@ -165,6 +158,11 @@
+ public void tick() {
+ --this.attackWarmupDelay;
+ if (this.attackWarmupDelay == 0) {
++ // CraftBukkit start
++ if (!CraftEventFactory.handleEntitySpellCastEvent(SpellcasterIllager.this, this.getSpell())) {
++ return;
++ }
++ // CraftBukkit end
+ this.performSpellCasting();
+ SpellcasterIllager.this.playSound(SpellcasterIllager.this.getCastingSoundEvent(), 1.0F, 1.0F);
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Spider.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Spider.java.patch
new file mode 100644
index 0000000000..b3af84dbd5
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Spider.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/world/entity/monster/Spider.java
++++ b/net/minecraft/world/entity/monster/Spider.java
+@@ -195,8 +181,8 @@
+ Spider.SpiderEffectsGroupData spider_spidereffectsgroupdata = (Spider.SpiderEffectsGroupData) object;
+ MobEffect mobeffect = spider_spidereffectsgroupdata.effect;
+
+- if (mobeffect != null) {
+- this.addEffect(new MobEffectInstance(mobeffect, -1));
++ if (mobeffectlist != null) {
++ this.addEffect(new MobEffectInstance(mobeffectlist, -1), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.SPIDER_SPAWN); // CraftBukkit
+ }
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Strider.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Strider.java.patch
new file mode 100644
index 0000000000..fb4fab2cf1
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Strider.java.patch
@@ -0,0 +1,18 @@
+--- a/net/minecraft/world/entity/monster/Strider.java
++++ b/net/minecraft/world/entity/monster/Strider.java
+@@ -372,7 +351,14 @@
+
+ boolean flag2 = flag1;
+
+- this.setSuffocating(!flag || flag2);
++ // CraftBukkit start
++ boolean suffocating = !flag || flag2;
++ if (suffocating ^ this.isSuffocating()) {
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callStriderTemperatureChangeEvent(this, suffocating)) {
++ this.setSuffocating(suffocating);
++ }
++ }
++ // CraftBukkit end
+ }
+
+ super.tick();
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Vex.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Vex.java.patch
new file mode 100644
index 0000000000..b2dc532475
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Vex.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/monster/Vex.java
++++ b/net/minecraft/world/entity/monster/Vex.java
+@@ -434,7 +404,7 @@
+ @Override
+ @Override
+ public void start() {
+- Vex.this.setTarget(Vex.this.owner.getTarget());
++ Vex.this.setTarget(Vex.this.owner.getTarget(), org.bukkit.event.entity.EntityTargetEvent.TargetReason.OWNER_ATTACKED_TARGET, true); // CraftBukkit
+ super.start();
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Witch.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Witch.java.patch
new file mode 100644
index 0000000000..433ba0519b
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Witch.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/monster/Witch.java
++++ b/net/minecraft/world/entity/monster/Witch.java
+@@ -140,7 +134,7 @@
+ while (iterator.hasNext()) {
+ MobEffectInstance mobeffectinstance = (MobEffectInstance) iterator.next();
+
+- this.addEffect(new MobEffectInstance(mobeffectinstance));
++ this.addEffect(new MobEffectInstance(mobeffect), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+ }
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/WitherSkeleton.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/WitherSkeleton.java.patch
new file mode 100644
index 0000000000..96a88f3467
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/WitherSkeleton.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/monster/WitherSkeleton.java
++++ b/net/minecraft/world/entity/monster/WitherSkeleton.java
+@@ -123,7 +111,7 @@
+ return false;
+ } else {
+ if (entity instanceof LivingEntity) {
+- ((LivingEntity) entity).addEffect(new MobEffectInstance(MobEffects.WITHER, 200), this);
++ ((LivingEntity) entity).addEffect(new MobEffectInstance(MobEffects.WITHER, 200), this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+ }
+
+ return true;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Zombie.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Zombie.java.patch
new file mode 100644
index 0000000000..e6851623ef
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/Zombie.java.patch
@@ -0,0 +1,186 @@
+--- a/net/minecraft/world/entity/monster/Zombie.java
++++ b/net/minecraft/world/entity/monster/Zombie.java
+@@ -66,6 +54,25 @@
+ import net.minecraft.world.level.block.Blocks;
+ import net.minecraft.world.level.block.state.BlockState;
+ import org.joml.Vector3f;
++import net.minecraft.core.BlockPos;
++import net.minecraft.nbt.CompoundTag;
++import net.minecraft.nbt.NbtOps;
++import net.minecraft.nbt.Tag;
++import net.minecraft.network.syncher.EntityDataAccessor;
++import net.minecraft.network.syncher.EntityDataSerializers;
++import net.minecraft.network.syncher.SynchedEntityData;
++// CraftBukkit start
++import net.minecraft.server.MinecraftServer;
++import net.minecraft.server.level.ServerLevel;
++import net.minecraft.sounds.SoundEvent;
++import net.minecraft.sounds.SoundEvents;
++import net.minecraft.sounds.SoundSource;
++import net.minecraft.tags.FluidTags;
++import org.bukkit.event.entity.CreatureSpawnEvent;
++import org.bukkit.event.entity.EntityCombustByEntityEvent;
++import org.bukkit.event.entity.EntityTargetEvent;
++import org.bukkit.event.entity.EntityTransformEvent;
++// CraftBukkit end
+
+ public class Zombie extends Monster {
+
+@@ -86,7 +93,8 @@
+ private final BreakDoorGoal breakDoorGoal;
+ private boolean canBreakDoors;
+ private int inWaterTime;
+- private int conversionTime;
++ public int conversionTime;
++ private int lastTick = MinecraftServer.currentTick; // CraftBukkit - add field
+
+ public Zombie(EntityType<? extends Zombie> entitytype, Level level) {
+ super(entitytype, level);
+@@ -210,7 +211,10 @@
+ public void tick() {
+ if (!this.level().isClientSide && this.isAlive() && !this.isNoAi()) {
+ if (this.isUnderWaterConverting()) {
+- --this.conversionTime;
++ // CraftBukkit start - Use wall time instead of ticks for conversion
++ int elapsedTicks = MinecraftServer.currentTick - this.lastTick;
++ this.conversionTime -= elapsedTicks;
++ // CraftBukkit end
+ if (this.conversionTime < 0) {
+ this.doUnderWaterConversion();
+ }
+@@ -227,6 +231,7 @@
+ }
+
+ super.tick();
++ this.lastTick = MinecraftServer.currentTick; // CraftBukkit
+ }
+
+ @Override
+@@ -259,8 +263,9 @@
+ super.aiStep();
+ }
+
+- private void startUnderWaterConversion(int i) {
+- this.conversionTime = i;
++ public void startUnderWaterConversion(int conversionTime) {
++ this.lastTick = MinecraftServer.currentTick; // CraftBukkit
++ this.conversionTime = conversionTime;
+ this.getEntityData().set(Zombie.DATA_DROWNED_CONVERSION_ID, true);
+ }
+
+@@ -275,9 +280,13 @@
+ protected void convertToZombieType(EntityType<? extends Zombie> entitytype) {
+ Zombie zombie = (Zombie) this.convertTo(entitytype, true);
+
+- if (zombie != null) {
+- zombie.handleAttributes(zombie.level().getCurrentDifficultyAt(zombie.blockPosition()).getSpecialMultiplier());
+- zombie.setCanBreakDoors(zombie.supportsBreakDoorGoal() && this.canBreakDoors());
++ if (entityzombie != null) {
++ entityzombie.handleAttributes(entityzombie.level().getCurrentDifficultyAt(entityzombie.blockPosition()).getSpecialMultiplier());
++ entityzombie.setCanBreakDoors(entityzombie.supportsBreakDoorGoal() && this.canBreakDoors());
++ // CraftBukkit start - SPIGOT-5208: End conversion to stop event spam
++ } else {
++ ((org.bukkit.entity.Zombie) getBukkitEntity()).setConversionTime(-1);
++ // CraftBukkit end
+ }
+
+ }
+@@ -315,12 +323,12 @@
+ EntityType<?> entitytype = zombie.getType();
+ SpawnPlacements.Type spawnplacements_type = SpawnPlacements.getPlacementType(entitytype);
+
+- if (NaturalSpawner.isSpawnPositionOk(spawnplacements_type, this.level(), blockpos, entitytype) && SpawnPlacements.checkSpawnRules(entitytype, serverlevel, MobSpawnType.REINFORCEMENT, blockpos, this.level().random)) {
+- zombie.setPos((double) i1, (double) j1, (double) k1);
+- if (!this.level().hasNearbyAlivePlayer((double) i1, (double) j1, (double) k1, 7.0D) && this.level().isUnobstructed(zombie) && this.level().noCollision((Entity) zombie) && !this.level().containsAnyLiquid(zombie.getBoundingBox())) {
+- zombie.setTarget(livingentity);
+- zombie.finalizeSpawn(serverlevel, this.level().getCurrentDifficultyAt(zombie.blockPosition()), MobSpawnType.REINFORCEMENT, (SpawnGroupData) null, (CompoundTag) null);
+- serverlevel.addFreshEntityWithPassengers(zombie);
++ if (NaturalSpawner.isSpawnPositionOk(entitypositiontypes_surface, this.level(), blockposition, entitytypes) && SpawnPlacements.checkSpawnRules(entitytypes, worldserver, EnumMobSpawn.REINFORCEMENT, blockposition, this.level().random)) {
++ entityzombie.setPos((double) i1, (double) j1, (double) k1);
++ if (!this.level().hasNearbyAlivePlayer((double) i1, (double) j1, (double) k1, 7.0D) && this.level().isUnobstructed(entityzombie) && this.level().noCollision((Entity) entityzombie) && !this.level().containsAnyLiquid(entityzombie.getBoundingBox())) {
++ entityzombie.setTarget(entityliving, EntityTargetEvent.TargetReason.REINFORCEMENT_TARGET, true); // CraftBukkit
++ entityzombie.finalizeSpawn(worldserver, this.level().getCurrentDifficultyAt(entityzombie.blockPosition()), EnumMobSpawn.REINFORCEMENT, (GroupDataEntity) null, (CompoundTag) null);
++ worldserver.addFreshEntityWithPassengers(entityzombie, CreatureSpawnEvent.SpawnReason.REINFORCEMENTS); // CraftBukkit
+ this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).addPermanentModifier(new AttributeModifier("Zombie reinforcement caller charge", -0.05000000074505806D, AttributeModifier.Operation.ADDITION));
+ zombie.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).addPermanentModifier(new AttributeModifier("Zombie reinforcement callee charge", -0.05000000074505806D, AttributeModifier.Operation.ADDITION));
+ break;
+@@ -342,7 +349,14 @@
+ float f = this.level().getCurrentDifficultyAt(this.blockPosition()).getEffectiveDifficulty();
+
+ if (this.getMainHandItem().isEmpty() && this.isOnFire() && this.random.nextFloat() < f * 0.3F) {
+- entity.setSecondsOnFire(2 * (int) f);
++ // CraftBukkit start
++ EntityCombustByEntityEvent event = new EntityCombustByEntityEvent(this.getBukkitEntity(), entity.getBukkitEntity(), 2 * (int) f); // PAIL: fixme
++ this.level().getCraftServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ entity.setSecondsOnFire(event.getDuration(), false);
++ }
++ // CraftBukkit end
+ }
+ }
+
+@@ -433,24 +438,35 @@
+ if (serverlevel.getDifficulty() != Difficulty.HARD && this.random.nextBoolean()) {
+ return flag;
+ }
++ // CraftBukkit start
++ flag = zombifyVillager(level, entityvillager, this.blockPosition(), this.isSilent(), CreatureSpawnEvent.SpawnReason.INFECTION) == null;
++ }
+
+ ZombieVillager zombievillager = (ZombieVillager) villager.convertTo(EntityType.ZOMBIE_VILLAGER, false);
+
+- if (zombievillager != null) {
+- zombievillager.finalizeSpawn(serverlevel, serverlevel.getCurrentDifficultyAt(zombievillager.blockPosition()), MobSpawnType.CONVERSION, new Zombie.ZombieGroupData(false, true), (CompoundTag) null);
+- zombievillager.setVillagerData(villager.getVillagerData());
+- zombievillager.setGossips((Tag) villager.getGossips().store(NbtOps.INSTANCE));
+- zombievillager.setTradeOffers(villager.getOffers().createTag());
+- zombievillager.setVillagerXp(villager.getVillagerXp());
+- if (!this.isSilent()) {
+- serverlevel.levelEvent((Player) null, 1026, this.blockPosition(), 0);
++ public static ZombieVillager zombifyVillager(ServerLevel worldserver, Villager entityvillager, net.minecraft.core.BlockPos blockPosition, boolean silent, CreatureSpawnEvent.SpawnReason spawnReason) {
++ {
++ ZombieVillager entityzombievillager = (ZombieVillager) entityvillager.convertTo(EntityType.ZOMBIE_VILLAGER, false, EntityTransformEvent.TransformReason.INFECTION, spawnReason);
++ // CraftBukkit end
++
++ if (entityzombievillager != null) {
++ entityzombievillager.finalizeSpawn(worldserver, worldserver.getCurrentDifficultyAt(entityzombievillager.blockPosition()), EnumMobSpawn.CONVERSION, new Zombie.ZombieGroupData(false, true), (CompoundTag) null);
++ entityzombievillager.setVillagerData(entityvillager.getVillagerData());
++ entityzombievillager.setGossips((Tag) entityvillager.getGossips().store(NbtOps.INSTANCE));
++ entityzombievillager.setTradeOffers(entityvillager.getOffers().createTag());
++ entityzombievillager.setVillagerXp(entityvillager.getVillagerXp());
++ // CraftBukkit start
++ if (!silent) {
++ worldserver.levelEvent((Player) null, 1026, blockPosition, 0);
+ }
+
+ flag = false;
+ }
+ }
+
+- return flag;
++ return entityzombievillager;
++ }
++ // CraftBukkit end
+ }
+
+ @Override
+@@ -502,12 +514,12 @@
+ } else if ((double) randomsource.nextFloat() < 0.05D) {
+ Chicken chicken1 = (Chicken) EntityType.CHICKEN.create(this.level());
+
+- if (chicken1 != null) {
+- chicken1.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), 0.0F);
+- chicken1.finalizeSpawn(serverlevelaccessor, difficultyinstance, MobSpawnType.JOCKEY, (SpawnGroupData) null, (CompoundTag) null);
+- chicken1.setChickenJockey(true);
+- this.startRiding(chicken1);
+- serverlevelaccessor.addFreshEntity(chicken1);
++ if (entitychicken1 != null) {
++ entitychicken1.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), 0.0F);
++ entitychicken1.finalizeSpawn(level, difficulty, EnumMobSpawn.JOCKEY, (GroupDataEntity) null, (CompoundTag) null);
++ entitychicken1.setChickenJockey(true);
++ this.startRiding(entitychicken1);
++ level.addFreshEntity(entitychicken1, CreatureSpawnEvent.SpawnReason.MOUNT); // CraftBukkit
+ }
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/ZombieVillager.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/ZombieVillager.java.patch
new file mode 100644
index 0000000000..41eb20eaef
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/ZombieVillager.java.patch
@@ -0,0 +1,99 @@
+--- a/net/minecraft/world/entity/monster/ZombieVillager.java
++++ b/net/minecraft/world/entity/monster/ZombieVillager.java
+@@ -50,6 +46,16 @@
+ import org.joml.Vector3f;
+ import org.slf4j.Logger;
+
++// CraftBukkit start
++import net.minecraft.server.MinecraftServer;
++import net.minecraft.server.level.ServerLevel;
++import net.minecraft.server.level.ServerPlayer;
++import net.minecraft.sounds.SoundEvent;
++import net.minecraft.sounds.SoundEvents;
++import org.bukkit.event.entity.CreatureSpawnEvent;
++import org.bukkit.event.entity.EntityTransformEvent;
++// CraftBukkit end
++
+ public class ZombieVillager extends Zombie implements VillagerDataHolder {
+
+ private static final Logger LOGGER = LogUtils.getLogger();
+@@ -67,6 +73,7 @@
+ @Nullable
+ private CompoundTag tradeOffers;
+ private int villagerXp;
++ private int lastTick = MinecraftServer.currentTick; // CraftBukkit - add field
+
+ public ZombieVillager(EntityType<? extends ZombieVillager> entitytype, Level level) {
+ super(entitytype, level);
+@@ -145,6 +148,10 @@
+ public void tick() {
+ if (!this.level().isClientSide && this.isAlive() && this.isConverting()) {
+ int i = this.getConversionProgress();
++ // CraftBukkit start - Use wall time instead of ticks for villager conversion
++ int elapsedTicks = MinecraftServer.currentTick - this.lastTick;
++ i *= elapsedTicks;
++ // CraftBukkit end
+
+ this.villagerConversionTime -= i;
+ if (this.villagerConversionTime <= 0) {
+@@ -153,6 +160,7 @@
+ }
+
+ super.tick();
++ this.lastTick = MinecraftServer.currentTick; // CraftBukkit
+ }
+
+ @Override
+@@ -199,8 +204,10 @@
+ this.conversionStarter = uuid;
+ this.villagerConversionTime = i;
+ this.getEntityData().set(ZombieVillager.DATA_CONVERTING_ID, true);
+- this.removeEffect(MobEffects.WEAKNESS);
+- this.addEffect(new MobEffectInstance(MobEffects.DAMAGE_BOOST, i, Math.min(this.level().getDifficulty().getId() - 1, 0)));
++ // CraftBukkit start
++ this.removeEffect(MobEffects.WEAKNESS, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION);
++ this.addEffect(new MobEffectInstance(MobEffects.DAMAGE_BOOST, villagerConversionTime, Math.min(this.level().getDifficulty().getId() - 1, 0)), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION);
++ // CraftBukkit end
+ this.level().broadcastEntityEvent(this, (byte) 16);
+ }
+
+@@ -217,10 +223,16 @@
+ }
+ }
+
+- private void finishConversion(ServerLevel serverlevel) {
+- Villager villager = (Villager) this.convertTo(EntityType.VILLAGER, false);
+- EquipmentSlot[] aequipmentslot = EquipmentSlot.values();
+- int i = aequipmentslot.length;
++ private void finishConversion(ServerLevel serverLevel) {
++ // CraftBukkit start
++ Villager entityvillager = (Villager) this.convertTo(EntityType.VILLAGER, false, EntityTransformEvent.TransformReason.CURED, CreatureSpawnEvent.SpawnReason.CURED);
++ if (entityvillager == null) {
++ ((org.bukkit.entity.ZombieVillager) getBukkitEntity()).setConversionTime(-1); // SPIGOT-5208: End conversion to stop event spam
++ return;
++ }
++ // CraftBukkit end
++ EquipmentSlot[] aenumitemslot = EquipmentSlot.values();
++ int i = aenumitemslot.length;
+
+ for (int j = 0; j < i; ++j) {
+ EquipmentSlot equipmentslot = aequipmentslot[j];
+@@ -233,7 +245,9 @@
+ double d0 = (double) this.getEquipmentDropChance(equipmentslot);
+
+ if (d0 > 1.0D) {
++ this.forceDrops = true; // CraftBukkit
+ this.spawnAtLocation(itemstack);
++ this.forceDrops = false; // CraftBukkit
+ }
+ }
+ }
+@@ -260,7 +274,7 @@
+ }
+ }
+
+- villager.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 200, 0));
++ entityvillager.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 200, 0), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION); // CraftBukkit
+ if (!this.isSilent()) {
+ serverlevel.levelEvent((Player) null, 1027, this.blockPosition(), 0);
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/ZombifiedPiglin.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/ZombifiedPiglin.java.patch
new file mode 100644
index 0000000000..92f5c3d734
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/ZombifiedPiglin.java.patch
@@ -0,0 +1,62 @@
+--- a/net/minecraft/world/entity/monster/ZombifiedPiglin.java
++++ b/net/minecraft/world/entity/monster/ZombifiedPiglin.java
+@@ -148,14 +143,14 @@
+ double d0 = this.getAttributeValue(Attributes.FOLLOW_RANGE);
+ AABB aabb = AABB.unitCubeFromLowerCorner(this.position()).inflate(d0, 10.0D, d0);
+
+- this.level().getEntitiesOfClass(ZombifiedPiglin.class, aabb, EntitySelector.NO_SPECTATORS).stream().filter((zombifiedpiglin) -> {
+- return zombifiedpiglin != this;
+- }).filter((zombifiedpiglin) -> {
+- return zombifiedpiglin.getTarget() == null;
+- }).filter((zombifiedpiglin) -> {
+- return !zombifiedpiglin.isAlliedTo((Entity) this.getTarget());
+- }).forEach((zombifiedpiglin) -> {
+- zombifiedpiglin.setTarget(this.getTarget());
++ this.level().getEntitiesOfClass(ZombifiedPiglin.class, axisalignedbb, EntitySelector.NO_SPECTATORS).stream().filter((entitypigzombie) -> {
++ return entitypigzombie != this;
++ }).filter((entitypigzombie) -> {
++ return entitypigzombie.getTarget() == null;
++ }).filter((entitypigzombie) -> {
++ return !entitypigzombie.isAlliedTo((Entity) this.getTarget());
++ }).forEach((entitypigzombie) -> {
++ entitypigzombie.setTarget(this.getTarget(), org.bukkit.event.entity.EntityTargetEvent.TargetReason.TARGET_ATTACKED_NEARBY_ENTITY, true); // CraftBukkit
+ });
+ }
+
+@@ -164,9 +159,8 @@
+ }
+
+ @Override
+- @Override
+- public void setTarget(@Nullable LivingEntity livingentity) {
+- if (this.getTarget() == null && livingentity != null) {
++ public boolean setTarget(@Nullable LivingEntity entityliving, org.bukkit.event.entity.EntityTargetEvent.TargetReason reason, boolean fireEvent) { // CraftBukkit - signature
++ if (this.getTarget() == null && entityliving != null) {
+ this.playFirstAngerSoundIn = ZombifiedPiglin.FIRST_ANGER_SOUND_DELAY.sample(this.random);
+ this.ticksUntilNextAlert = ZombifiedPiglin.ALERT_INTERVAL.sample(this.random);
+ }
+@@ -175,13 +169,22 @@
+ this.setLastHurtByPlayer((Player) livingentity);
+ }
+
+- super.setTarget(livingentity);
++ return super.setTarget(entityliving, reason, fireEvent); // CraftBukkit
+ }
+
+ @Override
+ @Override
+ public void startPersistentAngerTimer() {
+- this.setRemainingPersistentAngerTime(ZombifiedPiglin.PERSISTENT_ANGER_TIME.sample(this.random));
++ // CraftBukkit start
++ Entity entity = ((ServerLevel) this.level()).getEntity(getPersistentAngerTarget());
++ org.bukkit.event.entity.PigZombieAngerEvent event = new org.bukkit.event.entity.PigZombieAngerEvent((org.bukkit.entity.PigZombie) this.getBukkitEntity(), (entity == null) ? null : entity.getBukkitEntity(), ZombifiedPiglin.PERSISTENT_ANGER_TIME.sample(this.random));
++ this.level().getCraftServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ this.setPersistentAngerTarget(null);
++ return;
++ }
++ this.setRemainingPersistentAngerTime(event.getNewAnger());
++ // CraftBukkit end
+ }
+
+ public static boolean checkZombifiedPiglinSpawnRules(EntityType<ZombifiedPiglin> entitytype, LevelAccessor levelaccessor, MobSpawnType mobspawntype, BlockPos blockpos, RandomSource randomsource) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java.patch
new file mode 100644
index 0000000000..6642497f69
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java
++++ b/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java
+@@ -121,8 +114,8 @@
+ return !this.level().dimensionType().piglinSafe() && !this.isImmuneToZombification() && !this.isNoAi();
+ }
+
+- protected void finishConversion(ServerLevel serverlevel) {
+- ZombifiedPiglin zombifiedpiglin = (ZombifiedPiglin) this.convertTo(EntityType.ZOMBIFIED_PIGLIN, true);
++ protected void finishConversion(ServerLevel serverLevel) {
++ ZombifiedPiglin entitypigzombie = (ZombifiedPiglin) this.convertTo(EntityType.ZOMBIFIED_PIGLIN, true, org.bukkit.event.entity.EntityTransformEvent.TransformReason.PIGLIN_ZOMBIFIED, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.PIGLIN_ZOMBIFIED); // CraftBukkit - add spawn and transform reasons
+
+ if (zombifiedpiglin != null) {
+ zombifiedpiglin.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 200, 0));
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/piglin/Piglin.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/piglin/Piglin.java.patch
new file mode 100644
index 0000000000..012d5e6e7d
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/piglin/Piglin.java.patch
@@ -0,0 +1,117 @@
+--- a/net/minecraft/world/entity/monster/piglin/Piglin.java
++++ b/net/minecraft/world/entity/monster/piglin/Piglin.java
+@@ -55,6 +47,26 @@
+ import net.minecraft.world.level.block.Blocks;
+ import net.minecraft.world.level.block.state.BlockState;
+
++// CraftBukkit start
++import java.util.stream.Collectors;
++import java.util.HashSet;
++import java.util.Set;
++import net.minecraft.core.BlockPos;
++import net.minecraft.core.registries.BuiltInRegistries;
++import net.minecraft.nbt.CompoundTag;
++import net.minecraft.nbt.ListTag;
++import net.minecraft.nbt.StringTag;
++import net.minecraft.nbt.Tag;
++import net.minecraft.network.syncher.EntityDataAccessor;
++import net.minecraft.network.syncher.EntityDataSerializers;
++import net.minecraft.network.syncher.SynchedEntityData;
++import net.minecraft.resources.ResourceLocation;
++import net.minecraft.server.level.ServerLevel;
++import net.minecraft.sounds.SoundEvent;
++import net.minecraft.sounds.SoundEvents;
++import net.minecraft.world.item.Item;
++// CraftBukkit end
++
+ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, InventoryCarrier {
+
+ private static final EntityDataAccessor<Boolean> DATA_BABY_ID = SynchedEntityData.defineId(Piglin.class, EntityDataSerializers.BOOLEAN);
+@@ -75,6 +87,10 @@
+ private boolean cannotHunt;
+ protected static final ImmutableList<SensorType<? extends Sensor<? super Piglin>>> SENSOR_TYPES = ImmutableList.of(SensorType.NEAREST_LIVING_ENTITIES, SensorType.NEAREST_PLAYERS, SensorType.NEAREST_ITEMS, SensorType.HURT_BY, SensorType.PIGLIN_SPECIFIC_SENSOR);
+ protected static final ImmutableList<MemoryModuleType<?>> MEMORY_TYPES = ImmutableList.of(MemoryModuleType.LOOK_TARGET, MemoryModuleType.DOORS_TO_CLOSE, MemoryModuleType.NEAREST_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.NEAREST_VISIBLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, MemoryModuleType.NEAREST_VISIBLE_ADULT_PIGLINS, MemoryModuleType.NEARBY_ADULT_PIGLINS, MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, MemoryModuleType.ITEM_PICKUP_COOLDOWN_TICKS, MemoryModuleType.HURT_BY, MemoryModuleType.HURT_BY_ENTITY, new MemoryModuleType[]{MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.ATTACK_TARGET, MemoryModuleType.ATTACK_COOLING_DOWN, MemoryModuleType.INTERACTION_TARGET, MemoryModuleType.PATH, MemoryModuleType.ANGRY_AT, MemoryModuleType.UNIVERSAL_ANGER, MemoryModuleType.AVOID_TARGET, MemoryModuleType.ADMIRING_ITEM, MemoryModuleType.TIME_TRYING_TO_REACH_ADMIRE_ITEM, MemoryModuleType.ADMIRING_DISABLED, MemoryModuleType.DISABLE_WALK_TO_ADMIRE_ITEM, MemoryModuleType.CELEBRATE_LOCATION, MemoryModuleType.DANCING, MemoryModuleType.HUNTED_RECENTLY, MemoryModuleType.NEAREST_VISIBLE_BABY_HOGLIN, MemoryModuleType.NEAREST_VISIBLE_NEMESIS, MemoryModuleType.NEAREST_VISIBLE_ZOMBIFIED, MemoryModuleType.RIDE_TARGET, MemoryModuleType.VISIBLE_ADULT_PIGLIN_COUNT, MemoryModuleType.VISIBLE_ADULT_HOGLIN_COUNT, MemoryModuleType.NEAREST_VISIBLE_HUNTABLE_HOGLIN, MemoryModuleType.NEAREST_TARGETABLE_PLAYER_NOT_WEARING_GOLD, MemoryModuleType.NEAREST_PLAYER_HOLDING_WANTED_ITEM, MemoryModuleType.ATE_RECENTLY, MemoryModuleType.NEAREST_REPELLENT});
++ // CraftBukkit start - Custom bartering and interest list
++ public Set<Item> allowedBarterItems = new HashSet<>();
++ public Set<Item> interestItems = new HashSet<>();
++ // CraftBukkit end
+
+ public Piglin(EntityType<? extends AbstractPiglin> entitytype, Level level) {
+ super(entitytype, level);
+@@ -93,16 +108,27 @@
+ compoundtag.putBoolean("CannotHunt", true);
+ }
+
+- this.writeInventoryToTag(compoundtag);
++ this.writeInventoryToTag(compound);
++ // CraftBukkit start
++ ListTag barterList = new ListTag();
++ allowedBarterItems.stream().map(BuiltInRegistries.ITEM::getKey).map(ResourceLocation::toString).map(StringTag::valueOf).forEach(barterList::add);
++ compound.put("Bukkit.BarterList", barterList);
++ ListTag interestList = new ListTag();
++ interestItems.stream().map(BuiltInRegistries.ITEM::getKey).map(ResourceLocation::toString).map(StringTag::valueOf).forEach(interestList::add);
++ compound.put("Bukkit.InterestList", interestList);
++ // CraftBukkit end
+ }
+
+ @Override
+- @Override
+- public void readAdditionalSaveData(CompoundTag compoundtag) {
+- super.readAdditionalSaveData(compoundtag);
+- this.setBaby(compoundtag.getBoolean("IsBaby"));
+- this.setCannotHunt(compoundtag.getBoolean("CannotHunt"));
+- this.readInventoryFromTag(compoundtag);
++ public void readAdditionalSaveData(CompoundTag compound) {
++ super.readAdditionalSaveData(compound);
++ this.setBaby(compound.getBoolean("IsBaby"));
++ this.setCannotHunt(compound.getBoolean("CannotHunt"));
++ this.readInventoryFromTag(compound);
++ // CraftBukkit start
++ this.allowedBarterItems = compound.getList("Bukkit.BarterList", 8).stream().map(Tag::getAsString).map(ResourceLocation::tryParse).map(BuiltInRegistries.ITEM::get).collect(Collectors.toCollection(HashSet::new));
++ this.interestItems = compound.getList("Bukkit.InterestList", 8).stream().map(Tag::getAsString).map(ResourceLocation::tryParse).map(BuiltInRegistries.ITEM::get).collect(Collectors.toCollection(HashSet::new));
++ // CraftBukkit end
+ }
+
+ @VisibleForDebug
+@@ -233,7 +248,7 @@
+ @Override
+ @Override
+ public Brain<Piglin> getBrain() {
+- return super.getBrain();
++ return (Brain<Piglin>) super.getBrain(); // CraftBukkit - Decompile error
+ }
+
+ @Override
+@@ -387,9 +387,9 @@
+ this.setItemSlotAndDropWhenKilled(EquipmentSlot.MAINHAND, itemstack);
+ }
+
+- protected void holdInOffHand(ItemStack itemstack) {
+- if (itemstack.is(PiglinAi.BARTERING_ITEM)) {
+- this.setItemSlot(EquipmentSlot.OFFHAND, itemstack);
++ protected void holdInOffHand(ItemStack stack) {
++ if (stack.is(PiglinAi.BARTERING_ITEM) || allowedBarterItems.contains(stack.getItem())) { // CraftBukkit - Changes to accept custom payment items
++ this.setItemSlot(EquipmentSlot.OFFHAND, stack);
+ this.setGuaranteedDrop(EquipmentSlot.OFFHAND);
+ } else {
+ this.setItemSlotAndDropWhenKilled(EquipmentSlot.OFFHAND, itemstack);
+@@ -416,8 +414,8 @@
+ if (EnchantmentHelper.hasBindingCurse(itemstack1)) {
+ return false;
+ } else {
+- boolean flag = PiglinAi.isLovedItem(itemstack) || itemstack.is(Items.CROSSBOW);
+- boolean flag1 = PiglinAi.isLovedItem(itemstack1) || itemstack1.is(Items.CROSSBOW);
++ boolean flag = PiglinAi.isLovedItem(candidate, this) || candidate.is(Items.CROSSBOW); // CraftBukkit
++ boolean flag1 = PiglinAi.isLovedItem(existing, this) || existing.is(Items.CROSSBOW); // CraftBukkit
+
+ return flag && !flag1 ? true : (!flag && flag1 ? false : (this.isAdult() && !itemstack.is(Items.CROSSBOW) && itemstack1.is(Items.CROSSBOW) ? false : super.canReplaceCurrentItem(itemstack, itemstack1)));
+ }
+@@ -449,7 +444,7 @@
+ @Override
+ @Override
+ protected SoundEvent getAmbientSound() {
+- return this.level().isClientSide ? null : (SoundEvent) PiglinAi.getSoundForCurrentActivity(this).orElse((Object) null);
++ return this.level().isClientSide ? null : (SoundEvent) PiglinAi.getSoundForCurrentActivity(this).orElse(null); // CraftBukkit - Decompile error
+ }
+
+ @Override
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/piglin/PiglinAi.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/piglin/PiglinAi.java.patch
new file mode 100644
index 0000000000..8fb400c5fa
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/piglin/PiglinAi.java.patch
@@ -0,0 +1,134 @@
+--- a/net/minecraft/world/entity/monster/piglin/PiglinAi.java
++++ b/net/minecraft/world/entity/monster/piglin/PiglinAi.java
+@@ -73,6 +73,12 @@
+ import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
+ import net.minecraft.world.level.storage.loot.parameters.LootContextParams;
+ import net.minecraft.world.phys.Vec3;
++// CraftBukkit start
++import java.util.stream.Collectors;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.entity.PiglinBarterEvent;
++// CraftBukkit end
+
+ public class PiglinAi {
+
+@@ -233,23 +240,28 @@
+ stopWalking(piglin);
+ ItemStack itemstack;
+
+- if (itementity.getItem().is(Items.GOLD_NUGGET)) {
+- piglin.take(itementity, itementity.getItem().getCount());
+- itemstack = itementity.getItem();
+- itementity.discard();
++ // CraftBukkit start
++ if (itemEntity.getItem().is(Items.GOLD_NUGGET) && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(piglin, itemEntity, 0, false).isCancelled()) {
++ piglin.take(itemEntity, itemEntity.getItem().getCount());
++ itemstack = itemEntity.getItem();
++ itemEntity.discard();
++ } else if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(piglin, itemEntity, itemEntity.getItem().getCount() - 1, false).isCancelled()) {
++ piglin.take(itemEntity, 1);
++ itemstack = removeOneItemFromItemEntity(itemEntity);
+ } else {
+ piglin.take(itementity, 1);
+ itemstack = removeOneItemFromItemEntity(itementity);
+ }
++ // CraftBukkit end
+
+- if (isLovedItem(itemstack)) {
++ if (isLovedItem(itemstack, piglin)) { // CraftBukkit - Changes to allow for custom payment in bartering
+ piglin.getBrain().eraseMemory(MemoryModuleType.TIME_TRYING_TO_REACH_ADMIRE_ITEM);
+ holdInOffhand(piglin, itemstack);
+ admireGoldItem(piglin);
+ } else if (isFood(itemstack) && !hasEatenRecently(piglin)) {
+ eat(piglin);
+ } else {
+- boolean flag = !piglin.equipItemIfPossible(itemstack).equals(ItemStack.EMPTY);
++ boolean flag = !piglin.equipItemIfPossible(itemstack, itemEntity).equals(ItemStack.EMPTY); // CraftBukkit
+
+ if (!flag) {
+ putInInventory(piglin, itemstack);
+@@ -285,9 +296,14 @@
+ boolean flag1;
+
+ if (piglin.isAdult()) {
+- flag1 = isBarterCurrency(itemstack);
+- if (flag && flag1) {
+- throwItems(piglin, getBarterResponseItems(piglin));
++ flag1 = isBarterCurrency(itemstack, piglin); // CraftBukkit - Changes to allow custom payment for bartering
++ if (shouldBarter && flag1) {
++ // CraftBukkit start
++ PiglinBarterEvent event = CraftEventFactory.callPiglinBarterEvent(piglin, getBarterResponseItems(piglin), itemstack);
++ if (!event.isCancelled()) {
++ throwItems(piglin, event.getOutcome().stream().map(CraftItemStack::asNMSCopy).collect(Collectors.toList()));
++ }
++ // CraftBukkit end
+ } else if (!flag1) {
+ boolean flag2 = !piglin.equipItemIfPossible(itemstack).isEmpty();
+
+@@ -300,7 +316,7 @@
+ if (!flag1) {
+ ItemStack itemstack1 = piglin.getMainHandItem();
+
+- if (isLovedItem(itemstack1)) {
++ if (isLovedItem(itemstack1, piglin)) { // CraftBukkit - Changes to allow for custom payment in bartering
+ putInInventory(piglin, itemstack1);
+ } else {
+ throwItems(piglin, Collections.singletonList(itemstack1));
+@@ -377,7 +393,7 @@
+ return false;
+ } else if (isAdmiringDisabled(piglin) && piglin.getBrain().hasMemoryValue(MemoryModuleType.ATTACK_TARGET)) {
+ return false;
+- } else if (isBarterCurrency(itemstack)) {
++ } else if (isBarterCurrency(stack, piglin)) { // CraftBukkit
+ return isNotHoldingLovedItemInOffHand(piglin);
+ } else {
+ boolean flag = piglin.canAddToInventory(itemstack);
+@@ -386,9 +402,11 @@
+ }
+ }
+
+- protected static boolean isLovedItem(ItemStack itemstack) {
+- return itemstack.is(ItemTags.PIGLIN_LOVED);
++ // CraftBukkit start - Added method to allow checking for custom payment items
++ protected static boolean isLovedItem(ItemStack itemstack, Piglin piglin) {
++ return isLovedItem(itemstack) || (piglin.interestItems.contains(itemstack.getItem()) || piglin.allowedBarterItems.contains(itemstack.getItem()));
+ }
++ // CraftBukkit end
+
+ private static boolean wantsToStopRiding(Piglin piglin, Entity entity) {
+ if (!(entity instanceof Mob)) {
+@@ -480,8 +502,8 @@
+ }
+ }
+
+- protected static boolean canAdmire(Piglin piglin, ItemStack itemstack) {
+- return !isAdmiringDisabled(piglin) && !isAdmiringItem(piglin) && piglin.isAdult() && isBarterCurrency(itemstack);
++ protected static boolean canAdmire(Piglin piglin, ItemStack stack) {
++ return !isAdmiringDisabled(piglin) && !isAdmiringItem(piglin) && piglin.isAdult() && isBarterCurrency(stack, piglin); // CraftBukkit
+ }
+
+ protected static void wasHurtBy(Piglin piglin, LivingEntity livingentity) {
+@@ -738,9 +760,11 @@
+ return piglin.getBrain().hasMemoryValue(MemoryModuleType.ADMIRING_ITEM);
+ }
+
+- private static boolean isBarterCurrency(ItemStack itemstack) {
+- return itemstack.is(PiglinAi.BARTERING_ITEM);
++ // CraftBukkit start - Changes to allow custom payment for bartering
++ private static boolean isBarterCurrency(ItemStack itemstack, Piglin piglin) {
++ return isBarterCurrency(itemstack) || piglin.allowedBarterItems.contains(itemstack.getItem());
+ }
++ // CraftBukkit end
+
+ private static boolean isFood(ItemStack itemstack) {
+ return itemstack.is(ItemTags.PIGLIN_FOOD);
+@@ -775,7 +803,7 @@
+ }
+
+ private static boolean isNotHoldingLovedItemInOffHand(Piglin piglin) {
+- return piglin.getOffhandItem().isEmpty() || !isLovedItem(piglin.getOffhandItem());
++ return piglin.getOffhandItem().isEmpty() || !isLovedItem(piglin.getOffhandItem(), piglin); // CraftBukkit - Changes to allow custom payment for bartering
+ }
+
+ public static boolean isZombified(EntityType<?> entitytype) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/warden/Warden.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/warden/Warden.java.patch
new file mode 100644
index 0000000000..d1278d065d
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/monster/warden/Warden.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/monster/warden/Warden.java
++++ b/net/minecraft/world/entity/monster/warden/Warden.java
+@@ -439,7 +414,7 @@
+ public static void applyDarknessAround(ServerLevel serverlevel, Vec3 vec3, @Nullable Entity entity, int i) {
+ MobEffectInstance mobeffectinstance = new MobEffectInstance(MobEffects.DARKNESS, 260, 0, false, false);
+
+- MobEffectUtil.addEffectToPlayersAround(serverlevel, entity, vec3, (double) i, mobeffectinstance, 200);
++ MobEffectUtil.addEffectToPlayersAround(level, source, pos, radius, mobeffect, 200, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.WARDEN); // CraftBukkit - Add EntityPotionEffectEvent.Cause
+ }
+
+ @Override
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/npc/AbstractVillager.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/npc/AbstractVillager.java.patch
new file mode 100644
index 0000000000..cef3364ec8
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/npc/AbstractVillager.java.patch
@@ -0,0 +1,55 @@
+--- a/net/minecraft/world/entity/npc/AbstractVillager.java
++++ b/net/minecraft/world/entity/npc/AbstractVillager.java
+@@ -34,9 +34,23 @@
+ import net.minecraft.world.level.ServerLevelAccessor;
+ import net.minecraft.world.level.pathfinder.BlockPathTypes;
+ import net.minecraft.world.phys.Vec3;
++// CraftBukkit start
++import org.bukkit.Bukkit;
++import org.bukkit.craftbukkit.inventory.CraftMerchant;
++import org.bukkit.craftbukkit.inventory.CraftMerchantRecipe;
++import org.bukkit.event.entity.VillagerAcquireTradeEvent;
++// CraftBukkit end
+
+ public abstract class AbstractVillager extends AgeableMob implements InventoryCarrier, Npc, Merchant {
+
++ // CraftBukkit start
++ private CraftMerchant craftMerchant;
++
++ @Override
++ public CraftMerchant getCraftMerchant() {
++ return (craftMerchant == null) ? craftMerchant = new CraftMerchant(this) : craftMerchant;
++ }
++ // CraftBukkit end
+ private static final EntityDataAccessor<Integer> DATA_UNHAPPY_COUNTER = SynchedEntityData.defineId(AbstractVillager.class, EntityDataSerializers.INT);
+ public static final int VILLAGER_SLOT_OFFSET = 300;
+ private static final int VILLAGER_INVENTORY_SIZE = 8;
+@@ -44,7 +58,7 @@
+ private Player tradingPlayer;
+ @Nullable
+ protected MerchantOffers offers;
+- private final SimpleContainer inventory = new SimpleContainer(8);
++ private final SimpleContainer inventory = new SimpleContainer(8, (org.bukkit.craftbukkit.entity.CraftAbstractVillager) this.getBukkitEntity()); // CraftBukkit add argument
+
+ public AbstractVillager(EntityType<? extends AbstractVillager> entitytype, Level level) {
+ super(entitytype, level);
+@@ -252,8 +246,17 @@
+ while (j < i && !arraylist.isEmpty()) {
+ MerchantOffer merchantoffer = ((VillagerTrades.ItemListing) arraylist.remove(this.random.nextInt(arraylist.size()))).getOffer(this, this.random);
+
+- if (merchantoffer != null) {
+- merchantoffers.add(merchantoffer);
++ if (merchantrecipe != null) {
++ // CraftBukkit start
++ VillagerAcquireTradeEvent event = new VillagerAcquireTradeEvent((org.bukkit.entity.AbstractVillager) getBukkitEntity(), merchantrecipe.asBukkit());
++ // Suppress during worldgen
++ if (this.valid) {
++ Bukkit.getPluginManager().callEvent(event);
++ }
++ if (!event.isCancelled()) {
++ givenMerchantOffers.add(CraftMerchantRecipe.fromBukkit(event.getRecipe()).toMinecraft());
++ }
++ // CraftBukkit end
+ ++j;
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/npc/InventoryCarrier.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/npc/InventoryCarrier.java.patch
new file mode 100644
index 0000000000..d3c77db624
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/npc/InventoryCarrier.java.patch
@@ -0,0 +1,18 @@
+--- a/net/minecraft/world/entity/npc/InventoryCarrier.java
++++ b/net/minecraft/world/entity/npc/InventoryCarrier.java
+@@ -23,7 +23,14 @@
+ return;
+ }
+
+- mob.onItemPickup(itementity);
++ // CraftBukkit start
++ ItemStack remaining = new SimpleContainer(inventorysubcontainer).addItem(itemstack);
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPickupItemEvent(mob, itemEntity, remaining.getCount(), false).isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
++
++ mob.onItemPickup(itemEntity);
+ int i = itemstack.getCount();
+ ItemStack itemstack1 = simplecontainer.addItem(itemstack);
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/npc/Villager.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/npc/Villager.java.patch
new file mode 100644
index 0000000000..b583c81780
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/npc/Villager.java.patch
@@ -0,0 +1,80 @@
+--- a/net/minecraft/world/entity/npc/Villager.java
++++ b/net/minecraft/world/entity/npc/Villager.java
+@@ -92,6 +92,13 @@
+ import net.minecraft.world.phys.AABB;
+ import org.slf4j.Logger;
+
++// CraftBukkit start
++import org.bukkit.Bukkit;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityTransformEvent;
++import org.bukkit.event.entity.VillagerReplenishTradeEvent;
++// CraftBukkit end
++
+ public class Villager extends AbstractVillager implements ReputationEventHandler, VillagerDataHolder {
+
+ private static final Logger LOGGER = LogUtils.getLogger();
+@@ -238,7 +240,7 @@
+ this.increaseProfessionLevelOnUpdate = false;
+ }
+
+- this.addEffect(new MobEffectInstance(MobEffects.REGENERATION, 200, 0));
++ this.addEffect(new MobEffectInstance(MobEffects.REGENERATION, 200, 0), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.VILLAGER_TRADE); // CraftBukkit
+ }
+ }
+
+@@ -372,7 +368,13 @@
+ while (iterator.hasNext()) {
+ MerchantOffer merchantoffer = (MerchantOffer) iterator.next();
+
+- merchantoffer.resetUses();
++ // CraftBukkit start
++ VillagerReplenishTradeEvent event = new VillagerReplenishTradeEvent((org.bukkit.entity.Villager) this.getBukkitEntity(), merchantrecipe.asBukkit());
++ Bukkit.getPluginManager().callEvent(event);
++ if (!event.isCancelled()) {
++ merchantrecipe.resetUses();
++ }
++ // CraftBukkit end
+ }
+
+ this.resendOffersToTradingPlayer();
+@@ -441,7 +443,13 @@
+ while (iterator.hasNext()) {
+ MerchantOffer merchantoffer = (MerchantOffer) iterator.next();
+
+- merchantoffer.resetUses();
++ // CraftBukkit start
++ VillagerReplenishTradeEvent event = new VillagerReplenishTradeEvent((org.bukkit.entity.Villager) this.getBukkitEntity(), merchantrecipe.asBukkit());
++ Bukkit.getPluginManager().callEvent(event);
++ if (!event.isCancelled()) {
++ merchantrecipe.resetUses();
++ }
++ // CraftBukkit end
+ }
+ }
+
+@@ -862,8 +852,13 @@
+ witch.setCustomNameVisible(this.isCustomNameVisible());
+ }
+
+- witch.setPersistenceRequired();
+- serverlevel.addFreshEntityWithPassengers(witch);
++ entitywitch.setPersistenceRequired();
++ // CraftBukkit start
++ if (CraftEventFactory.callEntityTransformEvent(this, entitywitch, EntityTransformEvent.TransformReason.LIGHTNING).isCancelled()) {
++ return;
++ }
++ level.addFreshEntityWithPassengers(entitywitch, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING);
++ // CraftBukkit end
+ this.releaseAllPois();
+ this.discard();
+ } else {
+@@ -965,7 +957,7 @@
+ }).limit(5L).collect(Collectors.toList());
+
+ if (list1.size() >= j) {
+- if (!SpawnUtil.trySpawnMob(EntityType.IRON_GOLEM, MobSpawnType.MOB_SUMMONED, serverlevel, this.blockPosition(), 10, 8, 6, SpawnUtil.Strategy.LEGACY_IRON_GOLEM).isEmpty()) {
++ if (!SpawnUtil.trySpawnMob(EntityType.IRON_GOLEM, EnumMobSpawn.MOB_SUMMONED, serverLevel, this.blockPosition(), 10, 8, 6, SpawnUtil.Strategy.LEGACY_IRON_GOLEM, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.VILLAGE_DEFENSE).isEmpty()) { // CraftBukkit
+ list.forEach(GolemSensor::golemDetected);
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/npc/WanderingTrader.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/npc/WanderingTrader.java.patch
new file mode 100644
index 0000000000..48dd4579db
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/npc/WanderingTrader.java.patch
@@ -0,0 +1,47 @@
+--- a/net/minecraft/world/entity/npc/WanderingTrader.java
++++ b/net/minecraft/world/entity/npc/WanderingTrader.java
+@@ -47,15 +47,21 @@
+ import net.minecraft.world.phys.Vec3;
+ import org.apache.commons.lang3.tuple.Pair;
+
+-public class WanderingTrader extends AbstractVillager {
++// CraftBukkit start
++import org.bukkit.Bukkit;
++import org.bukkit.craftbukkit.inventory.CraftMerchantRecipe;
++import org.bukkit.entity.AbstractVillager;
++import org.bukkit.event.entity.VillagerAcquireTradeEvent;
++// CraftBukkit end
+
+ private static final int NUMBER_OF_TRADE_OFFERS = 5;
+ @Nullable
+ private BlockPos wanderTarget;
+ private int despawnDelay;
+
+- public WanderingTrader(EntityType<? extends WanderingTrader> entitytype, Level level) {
+- super(entitytype, level);
++ public WanderingTrader(EntityType<? extends WanderingTrader> entityType, Level level) {
++ super(entityType, level);
++ this.setDespawnDelay(48000); // CraftBukkit - set default from MobSpawnerTrader
+ }
+
+ @Override
+@@ -140,8 +143,17 @@
+ VillagerTrades.ItemListing villagertrades_itemlisting = avillagertrades_itemlisting1[i];
+ MerchantOffer merchantoffer = villagertrades_itemlisting.getOffer(this, this.random);
+
+- if (merchantoffer != null) {
+- merchantoffers.add(merchantoffer);
++ if (merchantrecipe != null) {
++ // CraftBukkit start
++ VillagerAcquireTradeEvent event = new VillagerAcquireTradeEvent((AbstractVillager) getBukkitEntity(), merchantrecipe.asBukkit());
++ // Suppress during worldgen
++ if (this.valid) {
++ Bukkit.getPluginManager().callEvent(event);
++ }
++ if (!event.isCancelled()) {
++ merchantrecipelist.add(CraftMerchantRecipe.fromBukkit(event.getRecipe()).toMinecraft());
++ }
++ // CraftBukkit end
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch
new file mode 100644
index 0000000000..d3b5420fdb
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch
@@ -0,0 +1,36 @@
+--- a/net/minecraft/world/entity/npc/WanderingTraderSpawner.java
++++ b/net/minecraft/world/entity/npc/WanderingTraderSpawner.java
+@@ -111,17 +110,17 @@
+ return false;
+ }
+
+- WanderingTrader wanderingtrader = (WanderingTrader) EntityType.WANDERING_TRADER.spawn(serverlevel, blockpos2, MobSpawnType.EVENT);
++ WanderingTrader entityvillagertrader = (WanderingTrader) EntityType.WANDERING_TRADER.spawn(serverLevel, blockposition2, EnumMobSpawn.EVENT, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit
+
+ if (wanderingtrader != null) {
+ for (int i = 0; i < 2; ++i) {
+ this.tryToSpawnLlamaFor(serverlevel, wanderingtrader, 4);
+ }
+
+- this.serverLevelData.setWanderingTraderId(wanderingtrader.getUUID());
+- wanderingtrader.setDespawnDelay(48000);
+- wanderingtrader.setWanderTarget(blockpos1);
+- wanderingtrader.restrictTo(blockpos1, 16);
++ this.serverLevelData.setWanderingTraderId(entityvillagertrader.getUUID());
++ // entityvillagertrader.setDespawnDelay(48000); // CraftBukkit - moved to EntityVillagerTrader constructor. This lets the value be modified by plugins on CreatureSpawnEvent
++ entityvillagertrader.setWanderTarget(blockposition1);
++ entityvillagertrader.restrictTo(blockposition1, 16);
+ return true;
+ }
+ }
+@@ -133,8 +132,8 @@
+ private void tryToSpawnLlamaFor(ServerLevel serverlevel, WanderingTrader wanderingtrader, int i) {
+ BlockPos blockpos = this.findSpawnPositionNear(serverlevel, wanderingtrader.blockPosition(), i);
+
+- if (blockpos != null) {
+- TraderLlama traderllama = (TraderLlama) EntityType.TRADER_LLAMA.spawn(serverlevel, blockpos, MobSpawnType.EVENT);
++ if (blockposition != null) {
++ TraderLlama entityllamatrader = (TraderLlama) EntityType.TRADER_LLAMA.spawn(serverLevel, blockposition, EnumMobSpawn.EVENT, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit
+
+ if (traderllama != null) {
+ traderllama.setLeashedTo(wanderingtrader, true);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/player/Inventory.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/player/Inventory.java.patch
new file mode 100644
index 0000000000..c8952ba42c
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/player/Inventory.java.patch
@@ -0,0 +1,100 @@
+--- a/net/minecraft/world/entity/player/Inventory.java
++++ b/net/minecraft/world/entity/player/Inventory.java
+@@ -26,7 +26,12 @@
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.level.block.state.BlockState;
+
+-public class Inventory implements Container, Nameable {
++// CraftBukkit start
++import java.util.ArrayList;
++import org.bukkit.Location;
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++// CraftBukkit end
+
+ public static final int POP_TIME_DURATION = 5;
+ public static final int INVENTORY_SIZE = 36;
+@@ -43,6 +50,54 @@
+ public final Player player;
+ private int timesChanged;
+
++ // CraftBukkit start - add fields and methods
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++ private int maxStack = MAX_STACK;
++
++ public List<ItemStack> getContents() {
++ List<ItemStack> combined = new ArrayList<ItemStack>(items.size() + armor.size() + offhand.size());
++ for (List<net.minecraft.world.item.ItemStack> sub : this.compartments) {
++ combined.addAll(sub);
++ }
++
++ return combined;
++ }
++
++ public List<ItemStack> getArmorContents() {
++ return this.armor;
++ }
++
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ public org.bukkit.inventory.InventoryHolder getOwner() {
++ return this.player.getBukkitEntity();
++ }
++
++ @Override
++ public int getMaxStackSize() {
++ return maxStack;
++ }
++
++ public void setMaxStackSize(int size) {
++ maxStack = size;
++ }
++
++ @Override
++ public Location getLocation() {
++ return player.getBukkitEntity().getLocation();
++ }
++ // CraftBukkit end
++
+ public Inventory(Player player) {
+ this.items = NonNullList.withSize(36, ItemStack.EMPTY);
+ this.armor = NonNullList.withSize(4, ItemStack.EMPTY);
+@@ -63,6 +118,28 @@
+ return !itemstack.isEmpty() && ItemStack.isSameItemSameTags(itemstack, itemstack1) && itemstack.isStackable() && itemstack.getCount() < itemstack.getMaxStackSize() && itemstack.getCount() < this.getMaxStackSize();
+ }
+
++ // CraftBukkit start - Watch method above! :D
++ public int canHold(ItemStack itemstack) {
++ int remains = itemstack.getCount();
++ for (int i = 0; i < this.items.size(); ++i) {
++ ItemStack itemstack1 = this.getItem(i);
++ if (itemstack1.isEmpty()) return itemstack.getCount();
++
++ if (this.hasRemainingSpaceForItem(itemstack1, itemstack)) {
++ remains -= (itemstack1.getMaxStackSize() < this.getMaxStackSize() ? itemstack1.getMaxStackSize() : this.getMaxStackSize()) - itemstack1.getCount();
++ }
++ if (remains <= 0) return itemstack.getCount();
++ }
++ ItemStack offhandItemStack = this.getItem(this.items.size() + this.armor.size());
++ if (this.hasRemainingSpaceForItem(offhandItemStack, itemstack)) {
++ remains -= (offhandItemStack.getMaxStackSize() < this.getMaxStackSize() ? offhandItemStack.getMaxStackSize() : this.getMaxStackSize()) - offhandItemStack.getCount();
++ }
++ if (remains <= 0) return itemstack.getCount();
++
++ return itemstack.getCount() - remains;
++ }
++ // CraftBukkit end
++
+ public int getFreeSlot() {
+ for (int i = 0; i < this.items.size(); ++i) {
+ if (((ItemStack) this.items.get(i)).isEmpty()) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/player/Player.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/player/Player.java.patch
new file mode 100644
index 0000000000..8eb24b7af0
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/player/Player.java.patch
@@ -0,0 +1,519 @@
+--- a/net/minecraft/world/entity/player/Player.java
++++ b/net/minecraft/world/entity/player/Player.java
+@@ -111,6 +112,16 @@
+ import net.minecraft.world.scores.PlayerTeam;
+ import net.minecraft.world.scores.Scoreboard;
+ import org.slf4j.Logger;
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.craftbukkit.util.CraftVector;
++import org.bukkit.entity.Item;
++import org.bukkit.event.entity.CreatureSpawnEvent;
++import org.bukkit.event.entity.EntityCombustByEntityEvent;
++import org.bukkit.event.entity.EntityExhaustionEvent;
++import org.bukkit.event.player.PlayerDropItemEvent;
++import org.bukkit.event.player.PlayerVelocityEvent;
++// CraftBukkit end
+
+ public abstract class Player extends LivingEntity {
+
+@@ -136,10 +148,10 @@
+ protected static final EntityDataAccessor<CompoundTag> DATA_SHOULDER_RIGHT = SynchedEntityData.defineId(Player.class, EntityDataSerializers.COMPOUND_TAG);
+ private long timeEntitySatOnShoulder;
+ private final Inventory inventory = new Inventory(this);
+- protected PlayerEnderChestContainer enderChestInventory = new PlayerEnderChestContainer();
++ protected PlayerEnderChestContainer enderChestInventory = new PlayerEnderChestContainer(this); // CraftBukkit - add "this" to constructor
+ public final InventoryMenu inventoryMenu;
+ public AbstractContainerMenu containerMenu;
+- protected FoodData foodData = new FoodData();
++ protected FoodData foodData = new FoodData(this); // CraftBukkit - add "this" to constructor
+ protected int jumpTriggerTime;
+ public float oBob;
+ public float bob;
+@@ -168,7 +180,17 @@
+ public FishingHook fishing;
+ protected float hurtDir;
+
+- public Player(Level level, BlockPos blockpos, float f, GameProfile gameprofile) {
++ // CraftBukkit start
++ public boolean fauxSleeping;
++ public int oldLevel = -1;
++
++ @Override
++ public CraftHumanEntity getBukkitEntity() {
++ return (CraftHumanEntity) super.getBukkitEntity();
++ }
++ // CraftBukkit end
++
++ public Player(Level level, BlockPos pos, float yRot, GameProfile gameProfile) {
+ super(EntityType.PLAYER, level);
+ this.lastItemInMainHand = ItemStack.EMPTY;
+ this.cooldowns = this.createItemCooldowns();
+@@ -315,7 +334,7 @@
+ ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD);
+
+ if (itemstack.is(Items.TURTLE_HELMET) && !this.isEyeInFluid(FluidTags.WATER)) {
+- this.addEffect(new MobEffectInstance(MobEffects.WATER_BREATHING, 200, 0, false, false, true));
++ this.addEffect(new MobEffectInstance(MobEffects.WATER_BREATHING, 200, 0, false, false, true), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.TURTLE_HELMET); // CraftBukkit
+ }
+
+ }
+@@ -495,8 +504,14 @@
+ public void rideTick() {
+ if (!this.level().isClientSide && this.wantsToStopRiding() && this.isPassenger()) {
+ this.stopRiding();
+- this.setShiftKeyDown(false);
+- } else {
++ // CraftBukkit start - SPIGOT-7316: no longer passenger, dismount and return
++ if (!this.isPassenger()) {
++ this.setShiftKeyDown(false);
++ return;
++ }
++ }
++ {
++ // CraftBukkit end
+ super.rideTick();
+ this.oBob = this.bob;
+ this.bob = 0.0F;
+@@ -520,7 +533,8 @@
+
+ if (this.level().getDifficulty() == Difficulty.PEACEFUL && this.level().getGameRules().getBoolean(GameRules.RULE_NATURAL_REGENERATION)) {
+ if (this.getHealth() < this.getMaxHealth() && this.tickCount % 20 == 0) {
+- this.heal(1.0F);
++ // CraftBukkit - added regain reason of "REGEN" for filtering purposes.
++ this.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.REGEN);
+ }
+
+ if (this.foodData.needsFood() && this.tickCount % 10 == 0) {
+@@ -683,7 +693,14 @@
+ }
+
+ @Nullable
+- public ItemEntity drop(ItemStack itemstack, boolean flag, boolean flag1) {
++ public ItemEntity drop(ItemStack droppedItem, boolean dropAround, boolean includeThrowerName) {
++ // CraftBukkit start - SPIGOT-2942: Add boolean to call event
++ return drop(droppedItem, dropAround, includeThrowerName, true);
++ }
++
++ @Nullable
++ public ItemEntity drop(ItemStack itemstack, boolean flag, boolean flag1, boolean callEvent) {
++ // CraftBukkit end
+ if (itemstack.isEmpty()) {
+ return null;
+ } else {
+@@ -718,7 +735,34 @@
+ itementity.setDeltaMovement((double) (-f3 * f2 * 0.3F) + Math.cos((double) f5) * (double) f6, (double) (-f1 * 0.3F + 0.1F + (this.random.nextFloat() - this.random.nextFloat()) * 0.1F), (double) (f4 * f2 * 0.3F) + Math.sin((double) f5) * (double) f6);
+ }
+
+- return itementity;
++ // CraftBukkit start - fire PlayerDropItemEvent
++ if (!callEvent) { // SPIGOT-2942: Add boolean to call event
++ return entityitem;
++ }
++ org.bukkit.entity.Player player = (org.bukkit.entity.Player) this.getBukkitEntity();
++ Item drop = (Item) entityitem.getBukkitEntity();
++
++ PlayerDropItemEvent event = new PlayerDropItemEvent(player, drop);
++ this.level().getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ org.bukkit.inventory.ItemStack cur = player.getInventory().getItemInHand();
++ if (flag1 && (cur == null || cur.getAmount() == 0)) {
++ // The complete stack was dropped
++ player.getInventory().setItemInHand(drop.getItemStack());
++ } else if (flag1 && cur.isSimilar(drop.getItemStack()) && cur.getAmount() < cur.getMaxStackSize() && drop.getItemStack().getAmount() == 1) {
++ // Only one item is dropped
++ cur.setAmount(cur.getAmount() + 1);
++ player.getInventory().setItemInHand(cur);
++ } else {
++ // Fallback
++ player.getInventory().addItem(drop.getItemStack());
++ }
++ return null;
++ }
++ // CraftBukkit end
++
++ return entityitem;
+ }
+ }
+
+@@ -872,12 +912,12 @@
+ return false;
+ } else {
+ if (!this.level().isClientSide) {
+- this.removeEntitiesOnShoulder();
++ // this.removeEntitiesOnShoulder(); // CraftBukkit - moved down
+ }
+
+ if (damagesource.scalesWithDifficulty()) {
+ if (this.level().getDifficulty() == Difficulty.PEACEFUL) {
+- f = 0.0F;
++ return false; // CraftBukkit - f = 0.0f -> return false
+ }
+
+ if (this.level().getDifficulty() == Difficulty.EASY) {
+@@ -889,7 +929,13 @@
+ }
+ }
+
+- return f == 0.0F ? false : super.hurt(damagesource, f);
++ // CraftBukkit start - Don't filter out 0 damage
++ boolean damaged = super.hurt(source, amount);
++ if (damaged) {
++ this.removeEntitiesOnShoulder();
++ }
++ return damaged;
++ // CraftBukkit end
+ }
+ }
+ }
+@@ -910,11 +954,30 @@
+ return !this.getAbilities().invulnerable && super.canBeSeenAsEnemy();
+ }
+
+- public boolean canHarmPlayer(Player player) {
+- PlayerTeam playerteam = this.getTeam();
+- PlayerTeam playerteam1 = player.getTeam();
++ public boolean canHarmPlayer(Player other) {
++ // CraftBukkit start - Change to check OTHER player's scoreboard team according to API
++ // To summarize this method's logic, it's "Can parameter hurt this"
++ org.bukkit.scoreboard.Team team;
++ if (other instanceof ServerPlayer) {
++ ServerPlayer thatPlayer = (ServerPlayer) other;
++ team = thatPlayer.getBukkitEntity().getScoreboard().getPlayerTeam(thatPlayer.getBukkitEntity());
++ if (team == null || team.allowFriendlyFire()) {
++ return true;
++ }
++ } else {
++ // This should never be called, but is implemented anyway
++ org.bukkit.OfflinePlayer thisPlayer = other.level().getCraftServer().getOfflinePlayer(other.getScoreboardName());
++ team = other.level().getCraftServer().getScoreboardManager().getMainScoreboard().getPlayerTeam(thisPlayer);
++ if (team == null || team.allowFriendlyFire()) {
++ return true;
++ }
++ }
+
+- return playerteam == null ? true : (!playerteam.isAlliedTo(playerteam1) ? true : playerteam.isAllowFriendlyFire());
++ if (this instanceof ServerPlayer) {
++ return !team.hasPlayer(((ServerPlayer) this).getBukkitEntity());
++ }
++ return !team.hasPlayer(this.level().getCraftServer().getOfflinePlayer(this.getScoreboardName()));
++ // CraftBukkit end
+ }
+
+ @Override
+@@ -959,9 +1019,13 @@
+ }
+ }
+
++ // CraftBukkit start
+ @Override
+- @Override
+- protected void actuallyHurt(DamageSource damagesource, float f) {
++ protected boolean damageEntity0(DamageSource damagesource, float f) { // void -> boolean
++ if (true) {
++ return super.damageEntity0(damagesource, f);
++ }
++ // CraftBukkit end
+ if (!this.isInvulnerableTo(damagesource)) {
+ f = this.getDamageAfterArmorAbsorb(damagesource, f);
+ f = this.getDamageAfterMagicAbsorb(damagesource, f);
+@@ -976,7 +1040,7 @@
+ }
+
+ if (f != 0.0F) {
+- this.causeFoodExhaustion(damagesource.getFoodExhaustion());
++ this.causeFoodExhaustion(damagesource.getFoodExhaustion(), EntityExhaustionEvent.ExhaustionReason.DAMAGED); // CraftBukkit - EntityExhaustionEvent
+ this.getCombatTracker().recordDamage(damagesource, f);
+ this.setHealth(this.getHealth() - f);
+ if (f < 3.4028235E37F) {
+@@ -986,6 +1050,7 @@
+ this.gameEvent(GameEvent.ENTITY_DAMAGE);
+ }
+ }
++ return false; // CraftBukkit
+ }
+
+ @Override
+@@ -1156,7 +1215,7 @@
+
+ f *= 0.2F + f2 * f2 * 0.8F;
+ f1 *= f2;
+- this.resetAttackStrengthTicker();
++ // this.resetAttackCooldown(); // CraftBukkit - Moved to EntityLiving to reset the cooldown after the damage is dealt
+ if (f > 0.0F || f1 > 0.0F) {
+ boolean flag = f2 > 0.9F;
+ boolean flag1 = false;
+@@ -1192,11 +1251,18 @@
+ boolean flag4 = false;
+ int j = EnchantmentHelper.getFireAspect(this);
+
+- if (entity instanceof LivingEntity) {
+- f3 = ((LivingEntity) entity).getHealth();
+- if (j > 0 && !entity.isOnFire()) {
+- flag4 = true;
+- entity.setSecondsOnFire(1);
++ if (target instanceof LivingEntity) {
++ f3 = ((LivingEntity) target).getHealth();
++ if (j > 0 && !target.isOnFire()) {
++ // CraftBukkit start - Call a combust event when somebody hits with a fire enchanted item
++ EntityCombustByEntityEvent combustEvent = new EntityCombustByEntityEvent(this.getBukkitEntity(), target.getBukkitEntity(), 1);
++ org.bukkit.Bukkit.getPluginManager().callEvent(combustEvent);
++
++ if (!combustEvent.isCancelled()) {
++ flag4 = true;
++ target.setSecondsOnFire(combustEvent.getDuration(), false);
++ }
++ // CraftBukkit end
+ }
+ }
+
+@@ -1223,9 +1289,12 @@
+ while (iterator.hasNext()) {
+ LivingEntity livingentity = (LivingEntity) iterator.next();
+
+- if (livingentity != this && livingentity != entity && !this.isAlliedTo((Entity) livingentity) && (!(livingentity instanceof ArmorStand) || !((ArmorStand) livingentity).isMarker()) && this.distanceToSqr((Entity) livingentity) < 9.0D) {
+- livingentity.knockback(0.4000000059604645D, (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)));
+- livingentity.hurt(this.damageSources().playerAttack(this), f4);
++ if (entityliving != this && entityliving != target && !this.isAlliedTo((Entity) entityliving) && (!(entityliving instanceof ArmorStand) || !((ArmorStand) entityliving).isMarker()) && this.distanceToSqr((Entity) entityliving) < 9.0D) {
++ // CraftBukkit start - Only apply knockback if the damage hits
++ if (entityliving.hurt(this.damageSources().playerAttack(this).sweep(), f4)) {
++ entityliving.knockback(0.4000000059604645D, (double) Mth.sin(this.getYRot() * 0.017453292F), (double) (-Mth.cos(this.getYRot() * 0.017453292F)));
++ }
++ // CraftBukkit end
+ }
+ }
+
+@@ -1233,10 +1302,27 @@
+ this.sweepAttack();
+ }
+
+- if (entity instanceof ServerPlayer && entity.hurtMarked) {
+- ((ServerPlayer) entity).connection.send(new ClientboundSetEntityMotionPacket(entity));
+- entity.hurtMarked = false;
+- entity.setDeltaMovement(vec3);
++ if (target instanceof ServerPlayer && target.hurtMarked) {
++ // CraftBukkit start - Add Velocity Event
++ boolean cancelled = false;
++ org.bukkit.entity.Player player = (org.bukkit.entity.Player) target.getBukkitEntity();
++ org.bukkit.util.Vector velocity = CraftVector.toBukkit(vec3d);
++
++ PlayerVelocityEvent event = new PlayerVelocityEvent(player, velocity.clone());
++ this.level().getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ cancelled = true;
++ } else if (!velocity.equals(event.getVelocity())) {
++ player.setVelocity(event.getVelocity());
++ }
++
++ if (!cancelled) {
++ ((ServerPlayer) target).connection.send(new ClientboundSetEntityMotionPacket(target));
++ target.hurtMarked = false;
++ target.setDeltaMovement(vec3d);
++ }
++ // CraftBukkit end
+ }
+
+ if (flag2) {
+@@ -1281,7 +1367,14 @@
+
+ this.awardStat(Stats.DAMAGE_DEALT, Math.round(f5 * 10.0F));
+ if (j > 0) {
+- entity.setSecondsOnFire(j * 4);
++ // CraftBukkit start - Call a combust event when somebody hits with a fire enchanted item
++ EntityCombustByEntityEvent combustEvent = new EntityCombustByEntityEvent(this.getBukkitEntity(), target.getBukkitEntity(), j * 4);
++ org.bukkit.Bukkit.getPluginManager().callEvent(combustEvent);
++
++ if (!combustEvent.isCancelled()) {
++ target.setSecondsOnFire(combustEvent.getDuration(), false);
++ }
++ // CraftBukkit end
+ }
+
+ if (this.level() instanceof ServerLevel && f5 > 2.0F) {
+@@ -1291,12 +1384,17 @@
+ }
+ }
+
+- this.causeFoodExhaustion(0.1F);
++ this.causeFoodExhaustion(0.1F, EntityExhaustionEvent.ExhaustionReason.ATTACK); // CraftBukkit - EntityExhaustionEvent
+ } else {
+ this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_NODAMAGE, this.getSoundSource(), 1.0F, 1.0F);
+ if (flag4) {
+ entity.clearFire();
+ }
++ // CraftBukkit start - resync on cancelled event
++ if (this instanceof ServerPlayer) {
++ ((ServerPlayer) this).getBukkitEntity().updateInventory();
++ }
++ // CraftBukkit end
+ }
+ }
+
+@@ -1374,8 +1470,14 @@
+ return this.containerMenu != this.inventoryMenu;
+ }
+
+- public Either<Player.BedSleepingProblem, Unit> startSleepInBed(BlockPos blockpos) {
+- this.startSleeping(blockpos);
++ public Either<Player.BedSleepingProblem, Unit> startSleepInBed(BlockPos bedPos) {
++ // CraftBukkit start
++ return this.startSleepInBed(bedPos, false);
++ }
++
++ public Either<Player.BedSleepingProblem, Unit> startSleepInBed(BlockPos blockposition, boolean force) {
++ // CraftBukkit end
++ this.startSleeping(blockposition);
+ this.sleepCounter = 0;
+ return Either.right(Unit.INSTANCE);
+ }
+@@ -1464,9 +1564,9 @@
+ super.jumpFromGround();
+ this.awardStat(Stats.JUMP);
+ if (this.isSprinting()) {
+- this.causeFoodExhaustion(0.2F);
++ this.causeFoodExhaustion(0.2F, EntityExhaustionEvent.ExhaustionReason.JUMP_SPRINT); // CraftBukkit - EntityExhaustionEvent
+ } else {
+- this.causeFoodExhaustion(0.05F);
++ this.causeFoodExhaustion(0.05F, EntityExhaustionEvent.ExhaustionReason.JUMP); // CraftBukkit - EntityExhaustionEvent
+ }
+
+ }
+@@ -1494,7 +1593,11 @@
+
+ this.setDeltaMovement(vec32.x, d0 * 0.6D, vec32.z);
+ this.resetFallDistance();
+- this.setSharedFlag(7, false);
++ // CraftBukkit start
++ if (getSharedFlag(7) && !org.bukkit.craftbukkit.event.CraftEventFactory.callToggleGlideEvent(this, false).isCancelled()) {
++ this.setSharedFlag(7, false);
++ }
++ // CraftBukkit end
+ } else {
+ super.travel(vec3);
+ }
+@@ -1550,12 +1650,24 @@
+ }
+
+ public void startFallFlying() {
+- this.setSharedFlag(7, true);
++ // CraftBukkit start
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callToggleGlideEvent(this, true).isCancelled()) {
++ this.setSharedFlag(7, true);
++ } else {
++ // SPIGOT-5542: must toggle like below
++ this.setSharedFlag(7, true);
++ this.setSharedFlag(7, false);
++ }
++ // CraftBukkit end
+ }
+
+ public void stopFallFlying() {
++ // CraftBukkit start
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callToggleGlideEvent(this, false).isCancelled()) {
+ this.setSharedFlag(7, true);
+ this.setSharedFlag(7, false);
++ }
++ // CraftBukkit end
+ }
+
+ @Override
+@@ -1674,10 +1781,21 @@
+ return this.experienceLevel >= 30 ? 112 + (this.experienceLevel - 30) * 9 : (this.experienceLevel >= 15 ? 37 + (this.experienceLevel - 15) * 5 : 7 + this.experienceLevel * 2);
+ }
+
+- public void causeFoodExhaustion(float f) {
++ // CraftBukkit start
++ public void causeFoodExhaustion(float exhaustion) {
++ this.causeFoodExhaustion(exhaustion, EntityExhaustionEvent.ExhaustionReason.UNKNOWN);
++ }
++
++ public void causeFoodExhaustion(float f, EntityExhaustionEvent.ExhaustionReason reason) {
++ // CraftBukkit end
+ if (!this.abilities.invulnerable) {
+ if (!this.level().isClientSide) {
+- this.foodData.addExhaustion(f);
++ // CraftBukkit start
++ EntityExhaustionEvent event = CraftEventFactory.callPlayerExhaustionEvent(this, reason, f);
++ if (!event.isCancelled()) {
++ this.foodData.addExhaustion(event.getExhaustion());
++ }
++ // CraftBukkit end
+ }
+
+ }
+@@ -1763,21 +1880,21 @@
+ }
+
+ @Override
+- @Override
+- protected boolean doesEmitEquipEvent(EquipmentSlot equipmentslot) {
+- return equipmentslot.getType() == EquipmentSlot.Type.ARMOR;
++ public void setItemSlot(EquipmentSlot slot, ItemStack stack) {
++ // CraftBukkit start
++ setItemSlot(slot, stack, false);
+ }
+
+ @Override
+- @Override
+- public void setItemSlot(EquipmentSlot equipmentslot, ItemStack itemstack) {
++ public void setItemSlot(EquipmentSlot enumitemslot, ItemStack itemstack, boolean silent) {
++ // CraftBukkit end
+ this.verifyEquippedItem(itemstack);
+- if (equipmentslot == EquipmentSlot.MAINHAND) {
+- this.onEquipItem(equipmentslot, (ItemStack) this.inventory.items.set(this.inventory.selected, itemstack), itemstack);
+- } else if (equipmentslot == EquipmentSlot.OFFHAND) {
+- this.onEquipItem(equipmentslot, (ItemStack) this.inventory.offhand.set(0, itemstack), itemstack);
+- } else if (equipmentslot.getType() == EquipmentSlot.Type.ARMOR) {
+- this.onEquipItem(equipmentslot, (ItemStack) this.inventory.armor.set(equipmentslot.getIndex(), itemstack), itemstack);
++ if (enumitemslot == EquipmentSlot.MAINHAND) {
++ this.onEquipItem(enumitemslot, (ItemStack) this.inventory.items.set(this.inventory.selected, itemstack), itemstack, silent); // CraftBukkit
++ } else if (enumitemslot == EquipmentSlot.OFFHAND) {
++ this.onEquipItem(enumitemslot, (ItemStack) this.inventory.offhand.set(0, itemstack), itemstack, silent); // CraftBukkit
++ } else if (enumitemslot.getType() == EquipmentSlot.Function.ARMOR) {
++ this.onEquipItem(enumitemslot, (ItemStack) this.inventory.armor.set(enumitemslot.getIndex(), itemstack), itemstack, silent); // CraftBukkit
+ }
+
+ }
+@@ -1818,26 +1933,31 @@
+
+ protected void removeEntitiesOnShoulder() {
+ if (this.timeEntitySatOnShoulder + 20L < this.level().getGameTime()) {
+- this.respawnEntityOnShoulder(this.getShoulderEntityLeft());
+- this.setShoulderEntityLeft(new CompoundTag());
+- this.respawnEntityOnShoulder(this.getShoulderEntityRight());
+- this.setShoulderEntityRight(new CompoundTag());
++ // CraftBukkit start
++ if (this.respawnEntityOnShoulder(this.getShoulderEntityLeft())) {
++ this.setShoulderEntityLeft(new CompoundTag());
++ }
++ if (this.respawnEntityOnShoulder(this.getShoulderEntityRight())) {
++ this.setShoulderEntityRight(new CompoundTag());
++ }
++ // CraftBukkit end
+ }
+
+ }
+
+- private void respawnEntityOnShoulder(CompoundTag compoundtag) {
+- if (!this.level().isClientSide && !compoundtag.isEmpty()) {
+- EntityType.create(compoundtag, this.level()).ifPresent((entity) -> {
++ private boolean respawnEntityOnShoulder(CompoundTag nbttagcompound) { // CraftBukkit void->boolean
++ if (!this.level().isClientSide && !nbttagcompound.isEmpty()) {
++ return EntityType.create(nbttagcompound, this.level()).map((entity) -> { // CraftBukkit
+ if (entity instanceof TamableAnimal) {
+ ((TamableAnimal) entity).setOwnerUUID(this.uuid);
+ }
+
+ entity.setPos(this.getX(), this.getY() + 0.699999988079071D, this.getZ());
+- ((ServerLevel) this.level()).addWithUUID(entity);
+- });
++ return ((ServerLevel) this.level()).addWithUUID(entity, CreatureSpawnEvent.SpawnReason.SHOULDER_ENTITY); // CraftBukkit
++ }).orElse(true); // CraftBukkit
+ }
+
++ return true; // CraftBukkit
+ }
+
+ @Override
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/AbstractArrow.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/AbstractArrow.java.patch
new file mode 100644
index 0000000000..59d167b863
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/AbstractArrow.java.patch
@@ -0,0 +1,62 @@
+--- a/net/minecraft/world/entity/projectile/AbstractArrow.java
++++ b/net/minecraft/world/entity/projectile/AbstractArrow.java
+@@ -46,6 +47,9 @@
+ import net.minecraft.world.phys.HitResult;
+ import net.minecraft.world.phys.Vec3;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++import org.bukkit.event.entity.EntityCombustByEntityEvent;
++import org.bukkit.event.player.PlayerPickupArrowEvent;
++// CraftBukkit end
+
+ public abstract class AbstractArrow extends Projectile {
+
+@@ -225,7 +223,7 @@
+ }
+
+ if (object != null && !flag) {
+- this.onHit((HitResult) object);
++ this.preOnHit((HitResult) object); // CraftBukkit - projectile hit event
+ this.hasImpulse = true;
+ }
+
+@@ -375,7 +371,13 @@
+ boolean flag1 = entity.getType().is(EntityTypeTags.DEFLECTS_ARROWS);
+
+ if (this.isOnFire() && !flag && !flag1) {
+- entity.setSecondsOnFire(5);
++ // CraftBukkit start
++ EntityCombustByEntityEvent combustEvent = new EntityCombustByEntityEvent(this.getBukkitEntity(), entity.getBukkitEntity(), 5);
++ org.bukkit.Bukkit.getPluginManager().callEvent(combustEvent);
++ if (!combustEvent.isCancelled()) {
++ entity.setSecondsOnFire(combustEvent.getDuration(), false);
++ }
++ // CraftBukkit end
+ }
+
+ if (entity.hurt(damagesource, (float) i)) {
+@@ -559,8 +555,23 @@
+ @Override
+ public void playerTouch(Player player) {
+ if (!this.level().isClientSide && (this.inGround || this.isNoPhysics()) && this.shakeTime <= 0) {
+- if (this.tryPickup(player)) {
+- player.take(this, 1);
++ // CraftBukkit start
++ ItemStack itemstack = this.getPickupItem();
++ if (this.pickup == Pickup.ALLOWED && !itemstack.isEmpty() && entity.getInventory().canHold(itemstack) > 0) {
++ ItemEntity item = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), itemstack);
++ PlayerPickupArrowEvent event = new PlayerPickupArrowEvent((org.bukkit.entity.Player) entity.getBukkitEntity(), new org.bukkit.craftbukkit.entity.CraftItem(this.level().getCraftServer(), item), (org.bukkit.entity.AbstractArrow) this.getBukkitEntity());
++ // event.setCancelled(!entityhuman.canPickUpLoot); TODO
++ this.level().getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return;
++ }
++ itemstack = item.getItem();
++ }
++
++ if ((this.pickup == AbstractArrow.Pickup.ALLOWED && entity.getInventory().add(itemstack)) || (this.pickup == AbstractArrow.Pickup.CREATIVE_ONLY && entity.getAbilities().instabuild)) {
++ // CraftBukkit end
++ entity.take(this, 1);
+ this.discard();
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java.patch
new file mode 100644
index 0000000000..837230eaf2
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java.patch
@@ -0,0 +1,62 @@
+--- a/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java
++++ b/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java
+@@ -16,12 +16,15 @@
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.phys.HitResult;
+ import net.minecraft.world.phys.Vec3;
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
+
+ public abstract class AbstractHurtingProjectile extends Projectile {
+
+ public double xPower;
+ public double yPower;
+ public double zPower;
++ public float bukkitYield = 1; // CraftBukkit
++ public boolean isIncendiary = true; // CraftBukkit
+
+ protected AbstractHurtingProjectile(EntityType<? extends AbstractHurtingProjectile> entitytype, Level level) {
+ super(entitytype, level);
+@@ -36,6 +39,12 @@
+ this(entitytype, level);
+ this.moveTo(d0, d1, d2, this.getYRot(), this.getXRot());
+ this.reapplyPosition();
++ // CraftBukkit start - Added setDirection method
++ this.setDirection(d3, z, d5);
++ }
++
++ public void setDirection(double d3, double d4, double d5) {
++ // CraftBukkit end
+ double d6 = Math.sqrt(d3 * d3 + d4 * d4 + d5 * d5);
+
+ if (d6 != 0.0D) {
+@@ -88,8 +94,14 @@
+
+ HitResult hitresult = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity, this.getClipType());
+
+- if (hitresult.getType() != HitResult.Type.MISS) {
+- this.onHit(hitresult);
++ if (movingobjectposition.getType() != HitResult.EnumMovingObjectType.MISS) {
++ this.preOnHit(movingobjectposition); // CraftBukkit - projectile hit event
++
++ // CraftBukkit start - Fire ProjectileHitEvent
++ if (this.isRemoved()) {
++ CraftEventFactory.callProjectileHitEvent(this, movingobjectposition);
++ }
++ // CraftBukkit end
+ }
+
+ this.checkInsideBlocks();
+@@ -193,7 +199,12 @@
+
+ if (entity != null) {
+ if (!this.level().isClientSide) {
+- Vec3 vec3 = entity.getLookAngle();
++ // CraftBukkit start
++ if (CraftEventFactory.handleNonLivingEntityDamageEvent(this, source, amount, false)) {
++ return false;
++ }
++ // CraftBukkit end
++ Vec3 vec3d = entity.getLookAngle();
+
+ this.setDeltaMovement(vec3);
+ this.xPower = vec3.x * 0.1D;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/Arrow.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/Arrow.java.patch
new file mode 100644
index 0000000000..19236dddac
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/Arrow.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/entity/projectile/Arrow.java
++++ b/net/minecraft/world/entity/projectile/Arrow.java
+@@ -218,15 +213,15 @@
+ mobeffectinstance = (MobEffectInstance) iterator.next();
+ livingentity.addEffect(new MobEffectInstance(mobeffectinstance.getEffect(), Math.max(mobeffectinstance.mapDuration((i) -> {
+ return i / 8;
+- }), 1), mobeffectinstance.getAmplifier(), mobeffectinstance.isAmbient(), mobeffectinstance.isVisible()), entity);
++ }), 1), mobeffect.getAmplifier(), mobeffect.isAmbient(), mobeffect.isVisible()), entity, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ARROW); // CraftBukkit
+ }
+
+ if (!this.effects.isEmpty()) {
+ iterator = this.effects.iterator();
+
+ while (iterator.hasNext()) {
+- mobeffectinstance = (MobEffectInstance) iterator.next();
+- livingentity.addEffect(mobeffectinstance, entity);
++ mobeffect = (MobEffectInstance) iterator.next();
++ living.addEffect(mobeffect, entity, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ARROW); // CraftBukkit
+ }
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/EvokerFangs.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/EvokerFangs.java.patch
new file mode 100644
index 0000000000..e5712ae16e
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/EvokerFangs.java.patch
@@ -0,0 +1,17 @@
+--- a/net/minecraft/world/entity/projectile/EvokerFangs.java
++++ b/net/minecraft/world/entity/projectile/EvokerFangs.java
+@@ -132,9 +127,11 @@
+ private void dealDamageTo(LivingEntity livingentity) {
+ LivingEntity livingentity1 = this.getOwner();
+
+- if (livingentity.isAlive() && !livingentity.isInvulnerable() && livingentity != livingentity1) {
+- if (livingentity1 == null) {
+- livingentity.hurt(this.damageSources().magic(), 6.0F);
++ if (target.isAlive() && !target.isInvulnerable() && target != entityliving1) {
++ if (entityliving1 == null) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.entityDamage = this; // CraftBukkit
++ target.hurt(this.damageSources().magic(), 6.0F);
++ org.bukkit.craftbukkit.event.CraftEventFactory.entityDamage = null; // CraftBukkit
+ } else {
+ if (livingentity1.isAlliedTo((Entity) livingentity)) {
+ return;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/EyeOfEnder.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/EyeOfEnder.java.patch
new file mode 100644
index 0000000000..f10ba9607b
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/EyeOfEnder.java.patch
@@ -0,0 +1,24 @@
+--- a/net/minecraft/world/entity/projectile/EyeOfEnder.java
++++ b/net/minecraft/world/entity/projectile/EyeOfEnder.java
+@@ -34,9 +34,9 @@
+ this.setPos(d0, d1, d2);
+ }
+
+- public void setItem(ItemStack itemstack) {
+- if (!itemstack.is(Items.ENDER_EYE) || itemstack.hasTag()) {
+- this.getEntityData().set(EyeOfEnder.DATA_ITEM_STACK, itemstack.copyWithCount(1));
++ public void setItem(ItemStack stack) {
++ if (true || !stack.is(Items.ENDER_EYE) || stack.hasTag()) { // CraftBukkit - always allow item changing
++ this.getEntityData().set(EyeOfEnder.DATA_ITEM_STACK, stack.copyWithCount(1));
+ }
+
+ }
+@@ -184,7 +177,7 @@
+ public void readAdditionalSaveData(CompoundTag compoundtag) {
+ ItemStack itemstack = ItemStack.of(compoundtag.getCompound("Item"));
+
+- this.setItem(itemstack);
++ if (!itemstack.isEmpty()) this.setItem(itemstack); // CraftBukkit - SPIGOT-6103 summon, see also SPIGOT-5474
+ }
+
+ @Override
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/Fireball.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/Fireball.java.patch
new file mode 100644
index 0000000000..418d8315b3
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/Fireball.java.patch
@@ -0,0 +1,10 @@
+--- a/net/minecraft/world/entity/projectile/Fireball.java
++++ b/net/minecraft/world/entity/projectile/Fireball.java
+@@ -69,6 +65,6 @@
+ super.readAdditionalSaveData(compoundtag);
+ ItemStack itemstack = ItemStack.of(compoundtag.getCompound("Item"));
+
+- this.setItem(itemstack);
++ if (!itemstack.isEmpty()) this.setItem(itemstack); // CraftBukkit - SPIGOT-5474 probably came from bugged earlier versions
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch
new file mode 100644
index 0000000000..4a7a183754
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch
@@ -0,0 +1,80 @@
+--- a/net/minecraft/world/entity/projectile/FireworkRocketEntity.java
++++ b/net/minecraft/world/entity/projectile/FireworkRocketEntity.java
+@@ -27,6 +27,7 @@
+ import net.minecraft.world.phys.EntityHitResult;
+ import net.minecraft.world.phys.HitResult;
+ import net.minecraft.world.phys.Vec3;
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
+
+ public class FireworkRocketEntity extends Projectile implements ItemSupplier {
+
+@@ -147,7 +144,7 @@
+ HitResult hitresult = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity);
+
+ if (!this.noPhysics) {
+- this.onHit(hitresult);
++ this.preOnHit(movingobjectposition); // CraftBukkit - projectile hit event
+ this.hasImpulse = true;
+ }
+
+@@ -162,7 +159,11 @@
+ }
+
+ if (!this.level().isClientSide && this.life > this.lifetime) {
+- this.explode();
++ // CraftBukkit start
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callFireworkExplodeEvent(this).isCancelled()) {
++ this.explode();
++ }
++ // CraftBukkit end
+ }
+
+ }
+@@ -179,7 +179,11 @@
+ protected void onHitEntity(EntityHitResult entityhitresult) {
+ super.onHitEntity(entityhitresult);
+ if (!this.level().isClientSide) {
+- this.explode();
++ // CraftBukkit start
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callFireworkExplodeEvent(this).isCancelled()) {
++ this.explode();
++ }
++ // CraftBukkit end
+ }
+ }
+
+@@ -190,7 +193,11 @@
+
+ this.level().getBlockState(blockpos).entityInside(this.level(), blockpos, this);
+ if (!this.level().isClientSide() && this.hasExplosion()) {
+- this.explode();
++ // CraftBukkit start
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callFireworkExplodeEvent(this).isCancelled()) {
++ this.explode();
++ }
++ // CraftBukkit end
+ }
+
+ super.onHitBlock(blockhitresult);
+@@ -216,7 +223,9 @@
+
+ if (f > 0.0F) {
+ if (this.attachedToEntity != null) {
+- this.attachedToEntity.hurt(this.damageSources().fireworks(this, this.getOwner()), 5.0F + (float) (listtag.size() * 2));
++ CraftEventFactory.entityDamage = this; // CraftBukkit
++ this.attachedToEntity.hurt(this.damageSources().fireworks(this, this.getOwner()), 5.0F + (float) (nbttaglist.size() * 2));
++ CraftEventFactory.entityDamage = null; // CraftBukkit
+ }
+
+ double d0 = 5.0D;
+@@ -243,7 +252,9 @@
+ if (flag) {
+ float f1 = f * (float) Math.sqrt((5.0D - (double) this.distanceTo(livingentity)) / 5.0D);
+
+- livingentity.hurt(this.damageSources().fireworks(this, this.getOwner()), f1);
++ CraftEventFactory.entityDamage = this; // CraftBukkit
++ entityliving.hurt(this.damageSources().fireworks(this, this.getOwner()), f1);
++ CraftEventFactory.entityDamage = null; // CraftBukkit
+ }
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/FishingHook.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/FishingHook.java.patch
new file mode 100644
index 0000000000..b595ce9ab1
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/FishingHook.java.patch
@@ -0,0 +1,184 @@
+--- a/net/minecraft/world/entity/projectile/FishingHook.java
++++ b/net/minecraft/world/entity/projectile/FishingHook.java
+@@ -46,6 +45,12 @@
+ import net.minecraft.world.phys.Vec3;
+ import org.slf4j.Logger;
+
++// CraftBukkit start
++import org.bukkit.entity.Player;
++import org.bukkit.entity.FishHook;
++import org.bukkit.event.player.PlayerFishEvent;
++// CraftBukkit end
++
+ public class FishingHook extends Projectile {
+
+ private static final Logger LOGGER = LogUtils.getLogger();
+@@ -67,8 +72,20 @@
+ private final int luck;
+ private final int lureSpeed;
+
+- private FishingHook(EntityType<? extends FishingHook> entitytype, Level level, int i, int j) {
+- super(entitytype, level);
++ // CraftBukkit start - Extra variables to enable modification of fishing wait time, values are minecraft defaults
++ public int minWaitTime = 100;
++ public int maxWaitTime = 600;
++ public int minLureTime = 20;
++ public int maxLureTime = 80;
++ public float minLureAngle = 0.0F;
++ public float maxLureAngle = 360.0F;
++ public boolean applyLure = true;
++ public boolean rainInfluenced = true;
++ public boolean skyInfluenced = true;
++ // CraftBukkit end
++
++ private FishingHook(EntityType<? extends FishingHook> entityType, Level level, int luck, int lureSpeed) {
++ super(entityType, level);
+ this.syncronizedRandom = RandomSource.create();
+ this.openWater = true;
+ this.currentState = FishingHook.FishHookState.FLYING;
+@@ -266,7 +278,7 @@
+ private void checkCollision() {
+ HitResult hitresult = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity);
+
+- this.onHit(hitresult);
++ this.preOnHit(movingobjectposition); // CraftBukkit - projectile hit event
+ }
+
+ @Override
+@@ -302,11 +311,11 @@
+ int i = 1;
+ BlockPos blockpos1 = blockpos.above();
+
+- if (this.random.nextFloat() < 0.25F && this.level().isRainingAt(blockpos1)) {
++ if (this.rainInfluenced && this.random.nextFloat() < 0.25F && this.level().isRainingAt(blockposition1)) { // CraftBukkit
+ ++i;
+ }
+
+- if (this.random.nextFloat() < 0.5F && !this.level().canSeeSky(blockpos1)) {
++ if (this.skyInfluenced && this.random.nextFloat() < 0.5F && !this.level().canSeeSky(blockposition1)) { // CraftBukkit
+ --i;
+ }
+
+@@ -316,6 +325,10 @@
+ this.timeUntilLured = 0;
+ this.timeUntilHooked = 0;
+ this.getEntityData().set(FishingHook.DATA_BITING, false);
++ // CraftBukkit start
++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) this.getPlayerOwner().getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.FAILED_ATTEMPT);
++ this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent);
++ // CraftBukkit end
+ }
+ } else {
+ float f;
+@@ -349,6 +362,13 @@
+ serverlevel.sendParticles(ParticleTypes.FISHING, d0, d1, d2, 0, (double) (-f4), 0.01D, (double) f3, 1.0D);
+ }
+ } else {
++ // CraftBukkit start
++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) this.getPlayerOwner().getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.BITE);
++ this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent);
++ if (playerFishEvent.isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
+ this.playSound(SoundEvents.FISHING_BOBBER_SPLASH, 0.25F, 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.4F);
+ double d3 = this.getY() + 0.5D;
+
+@@ -381,12 +401,16 @@
+ }
+
+ if (this.timeUntilLured <= 0) {
+- this.fishAngle = Mth.nextFloat(this.random, 0.0F, 360.0F);
+- this.timeUntilHooked = Mth.nextInt(this.random, 20, 80);
++ // CraftBukkit start - logic to modify fishing wait time, lure time, and lure angle
++ this.fishAngle = Mth.nextFloat(this.random, this.minLureAngle, this.maxLureAngle);
++ this.timeUntilHooked = Mth.nextInt(this.random, this.minLureTime, this.maxLureTime);
++ // CraftBukkit end
+ }
+ } else {
+- this.timeUntilLured = Mth.nextInt(this.random, 100, 600);
+- this.timeUntilLured -= this.lureSpeed * 20 * 5;
++ // CraftBukkit start - logic to modify fishing wait time
++ this.timeUntilLured = Mth.nextInt(this.random, this.minWaitTime, this.maxWaitTime);
++ this.timeUntilLured -= (this.applyLure) ? this.lureSpeed * 20 * 5 : 0;
++ // CraftBukkit end
+ }
+ }
+
+@@ -455,6 +477,14 @@
+ int i = 0;
+
+ if (this.hookedIn != null) {
++ // CraftBukkit start
++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), this.hookedIn.getBukkitEntity(), (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.CAUGHT_ENTITY);
++ this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent);
++
++ if (playerFishEvent.isCancelled()) {
++ return 0;
++ }
++ // CraftBukkit end
+ this.pullEntity(this.hookedIn);
+ CriteriaTriggers.FISHING_ROD_HOOKED.trigger((ServerPlayer) player, itemstack, this, Collections.emptyList());
+ this.level().broadcastEntityEvent(this, (byte) 31);
+@@ -469,15 +499,28 @@
+
+ while (iterator.hasNext()) {
+ ItemStack itemstack1 = (ItemStack) iterator.next();
+- ItemEntity itementity = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), itemstack1);
+- double d0 = player.getX() - this.getX();
+- double d1 = player.getY() - this.getY();
+- double d2 = player.getZ() - this.getZ();
++ ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), itemstack1);
++ // CraftBukkit start
++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), entityitem.getBukkitEntity(), (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.CAUGHT_FISH);
++ playerFishEvent.setExpToDrop(this.random.nextInt(6) + 1);
++ this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent);
++
++ if (playerFishEvent.isCancelled()) {
++ return 0;
++ }
++ // CraftBukkit end
++ double d0 = entityhuman.getX() - this.getX();
++ double d1 = entityhuman.getY() - this.getY();
++ double d2 = entityhuman.getZ() - this.getZ();
+ double d3 = 0.1D;
+
+- itementity.setDeltaMovement(d0 * 0.1D, d1 * 0.1D + Math.sqrt(Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2)) * 0.08D, d2 * 0.1D);
+- this.level().addFreshEntity(itementity);
+- player.level().addFreshEntity(new ExperienceOrb(player.level(), player.getX(), player.getY() + 0.5D, player.getZ() + 0.5D, this.random.nextInt(6) + 1));
++ entityitem.setDeltaMovement(d0 * 0.1D, d1 * 0.1D + Math.sqrt(Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2)) * 0.08D, d2 * 0.1D);
++ this.level().addFreshEntity(entityitem);
++ // CraftBukkit start - this.random.nextInt(6) + 1 -> playerFishEvent.getExpToDrop()
++ if (playerFishEvent.getExpToDrop() > 0) {
++ entityhuman.level().addFreshEntity(new ExperienceOrb(entityhuman.level(), entityhuman.getX(), entityhuman.getY() + 0.5D, entityhuman.getZ() + 0.5D, playerFishEvent.getExpToDrop()));
++ }
++ // CraftBukkit end
+ if (itemstack1.is(ItemTags.FISHES)) {
+ player.awardStat(Stats.FISH_CAUGHT, 1);
+ }
+@@ -487,8 +530,25 @@
+ }
+
+ if (this.onGround()) {
++ // CraftBukkit start
++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.IN_GROUND);
++ this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent);
++
++ if (playerFishEvent.isCancelled()) {
++ return 0;
++ }
++ // CraftBukkit end
+ i = 2;
+ }
++ // CraftBukkit start
++ if (i == 0) {
++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((Player) entityhuman.getBukkitEntity(), null, (FishHook) this.getBukkitEntity(), PlayerFishEvent.State.REEL_IN);
++ this.level().getCraftServer().getPluginManager().callEvent(playerFishEvent);
++ if (playerFishEvent.isCancelled()) {
++ return 0;
++ }
++ }
++ // CraftBukkit end
+
+ this.discard();
+ return i;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/LargeFireball.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/LargeFireball.java.patch
new file mode 100644
index 0000000000..99a6249186
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/LargeFireball.java.patch
@@ -0,0 +1,60 @@
+--- a/net/minecraft/world/entity/projectile/LargeFireball.java
++++ b/net/minecraft/world/entity/projectile/LargeFireball.java
+@@ -8,18 +8,21 @@
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.phys.EntityHitResult;
+ import net.minecraft.world.phys.HitResult;
++import org.bukkit.event.entity.ExplosionPrimeEvent; // CraftBukkit
+
+ public class LargeFireball extends Fireball {
+
+ private int explosionPower = 1;
+
+- public LargeFireball(EntityType<? extends LargeFireball> entitytype, Level level) {
+- super(entitytype, level);
++ public LargeFireball(EntityType<? extends LargeFireball> entityType, Level level) {
++ super(entityType, level);
++ isIncendiary = this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit
+ }
+
+ public LargeFireball(Level level, LivingEntity livingentity, double d0, double d1, double d2, int i) {
+ super(EntityType.FIREBALL, livingentity, d0, d1, d2, level);
+ this.explosionPower = i;
++ isIncendiary = this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit
+ }
+
+ @Override
+@@ -29,7 +31,15 @@
+ if (!this.level().isClientSide) {
+ boolean flag = this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING);
+
+- this.level().explode(this, this.getX(), this.getY(), this.getZ(), (float) this.explosionPower, flag, Level.ExplosionInteraction.MOB);
++ // CraftBukkit start - fire ExplosionPrimeEvent
++ ExplosionPrimeEvent event = new ExplosionPrimeEvent((org.bukkit.entity.Explosive) this.getBukkitEntity());
++ this.level().getCraftServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ // give 'this' instead of (Entity) null so we know what causes the damage
++ this.level().explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.a.MOB);
++ }
++ // CraftBukkit end
+ this.discard();
+ }
+
+@@ -59,11 +67,11 @@
+ }
+
+ @Override
+- @Override
+- public void readAdditionalSaveData(CompoundTag compoundtag) {
+- super.readAdditionalSaveData(compoundtag);
+- if (compoundtag.contains("ExplosionPower", 99)) {
+- this.explosionPower = compoundtag.getByte("ExplosionPower");
++ public void readAdditionalSaveData(CompoundTag compound) {
++ super.readAdditionalSaveData(compound);
++ if (compound.contains("ExplosionPower", 99)) {
++ // CraftBukkit - set bukkitYield when setting explosionpower
++ bukkitYield = this.explosionPower = compound.getByte("ExplosionPower");
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/LlamaSpit.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/LlamaSpit.java.patch
new file mode 100644
index 0000000000..3a0f211e17
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/LlamaSpit.java.patch
@@ -0,0 +1,17 @@
+--- a/net/minecraft/world/entity/projectile/LlamaSpit.java
++++ b/net/minecraft/world/entity/projectile/LlamaSpit.java
+@@ -33,10 +32,10 @@
+ Vec3 vec3 = this.getDeltaMovement();
+ HitResult hitresult = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity);
+
+- this.onHit(hitresult);
+- double d0 = this.getX() + vec3.x;
+- double d1 = this.getY() + vec3.y;
+- double d2 = this.getZ() + vec3.z;
++ this.preOnHit(movingobjectposition); // CraftBukkit - projectile hit event
++ double d0 = this.getX() + vec3d.x;
++ double d1 = this.getY() + vec3d.y;
++ double d2 = this.getZ() + vec3d.z;
+
+ this.updateRotation();
+ float f = 0.99F;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/Projectile.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/Projectile.java.patch
new file mode 100644
index 0000000000..7389e32773
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/Projectile.java.patch
@@ -0,0 +1,69 @@
+--- a/net/minecraft/world/entity/projectile/Projectile.java
++++ b/net/minecraft/world/entity/projectile/Projectile.java
+@@ -24,6 +24,9 @@
+ import net.minecraft.world.phys.EntityHitResult;
+ import net.minecraft.world.phys.HitResult;
+ import net.minecraft.world.phys.Vec3;
++// CraftBukkit start
++import org.bukkit.projectiles.ProjectileSource;
++// CraftBukkit end
+
+ public abstract class Projectile extends Entity implements TraceableEntity {
+
+@@ -34,8 +37,12 @@
+ private boolean leftOwner;
+ private boolean hasBeenShot;
+
+- Projectile(EntityType<? extends Projectile> entitytype, Level level) {
+- super(entitytype, level);
++ // CraftBukkit start
++ private boolean hitCancelled = false;
++ // CraftBukkit end
++
++ Projectile(EntityType<? extends Projectile> entityType, Level level) {
++ super(entityType, level);
+ }
+
+ public void setOwner(@Nullable Entity entity) {
+@@ -43,6 +50,7 @@
+ this.ownerUUID = entity.getUUID();
+ this.cachedOwner = entity;
+ }
++ this.projectileSource = (owner != null && owner.getBukkitEntity() instanceof ProjectileSource) ? (ProjectileSource) owner.getBukkitEntity() : null; // CraftBukkit
+
+ }
+
+@@ -172,8 +175,15 @@
+ this.setDeltaMovement(this.getDeltaMovement().add(vec3.x, entity.onGround() ? 0.0D : vec3.y, vec3.z));
+ }
+
+- protected void onHit(HitResult hitresult) {
+- HitResult.Type hitresult_type = hitresult.getType();
++ // CraftBukkit start - call projectile hit event
++ protected void preOnHit(HitResult movingobjectposition) {
++ org.bukkit.event.entity.ProjectileHitEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileHitEvent(this, movingobjectposition);
++ this.hitCancelled = event != null && event.isCancelled();
++ if (movingobjectposition.getType() == HitResult.EnumMovingObjectType.BLOCK || !this.hitCancelled) {
++ this.onHit(movingobjectposition);
++ }
++ }
++ // CraftBukkit end
+
+ if (hitresult_type == HitResult.Type.ENTITY) {
+ this.onHitEntity((EntityHitResult) hitresult);
+@@ -191,8 +204,13 @@
+
+ protected void onHitEntity(EntityHitResult entityhitresult) {}
+
+- protected void onHitBlock(BlockHitResult blockhitresult) {
+- BlockState blockstate = this.level().getBlockState(blockhitresult.getBlockPos());
++ protected void onHitBlock(BlockHitResult result) {
++ // CraftBukkit start - cancellable hit event
++ if (hitCancelled) {
++ return;
++ }
++ // CraftBukkit end
++ IBlockData iblockdata = this.level().getBlockState(result.getBlockPos());
+
+ blockstate.onProjectileHit(this.level(), blockstate, blockhitresult, this);
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ShulkerBullet.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ShulkerBullet.java.patch
new file mode 100644
index 0000000000..4512f7190d
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ShulkerBullet.java.patch
@@ -0,0 +1,61 @@
+--- a/net/minecraft/world/entity/projectile/ShulkerBullet.java
++++ b/net/minecraft/world/entity/projectile/ShulkerBullet.java
+@@ -59,9 +59,22 @@
+ this.moveTo(d0, d1, d2, this.getYRot(), this.getXRot());
+ this.finalTarget = entity;
+ this.currentMoveDirection = Direction.UP;
+- this.selectNextMoveDirection(direction_axis);
++ this.selectNextMoveDirection(axis);
++ projectileSource = (org.bukkit.entity.LivingEntity) shooter.getBukkitEntity(); // CraftBukkit
+ }
+
++ // CraftBukkit start
++ public Entity getTarget() {
++ return this.finalTarget;
++ }
++
++ public void setTarget(Entity e) {
++ this.finalTarget = e;
++ this.currentMoveDirection = Direction.UP;
++ this.selectNextMoveDirection(Direction.Axis.X);
++ }
++ // CraftBukkit end
++
+ @Override
+ @Override
+ public SoundSource getSoundSource() {
+@@ -230,8 +237,8 @@
+
+ HitResult hitresult = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity);
+
+- if (hitresult.getType() != HitResult.Type.MISS) {
+- this.onHit(hitresult);
++ if (movingobjectposition.getType() != HitResult.EnumMovingObjectType.MISS) {
++ this.preOnHit(movingobjectposition); // CraftBukkit - projectile hit event
+ }
+ }
+
+@@ -305,7 +307,7 @@
+ if (entity instanceof LivingEntity) {
+ LivingEntity livingentity1 = (LivingEntity) entity;
+
+- livingentity1.addEffect(new MobEffectInstance(MobEffects.LEVITATION, 200), (Entity) MoreObjects.firstNonNull(entity1, this));
++ entityliving1.addEffect(new MobEffectInstance(MobEffects.LEVITATION, 200), (Entity) MoreObjects.firstNonNull(entity1, this), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+ }
+ }
+
+@@ -338,8 +337,12 @@
+ }
+
+ @Override
+- @Override
+- public boolean hurt(DamageSource damagesource, float f) {
++ public boolean hurt(DamageSource source, float amount) {
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, source, amount, false)) {
++ return false;
++ }
++ // CraftBukkit end
+ if (!this.level().isClientSide) {
+ this.playSound(SoundEvents.SHULKER_BULLET_HURT, 1.0F, 1.0F);
+ ((ServerLevel) this.level()).sendParticles(ParticleTypes.CRIT, this.getX(), this.getY(), this.getZ(), 15, 0.2D, 0.2D, 0.2D, 0.0D);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/SmallFireball.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/SmallFireball.java.patch
new file mode 100644
index 0000000000..9ce39e4476
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/SmallFireball.java.patch
@@ -0,0 +1,58 @@
+--- a/net/minecraft/world/entity/projectile/SmallFireball.java
++++ b/net/minecraft/world/entity/projectile/SmallFireball.java
+@@ -12,6 +12,7 @@
+ import net.minecraft.world.phys.BlockHitResult;
+ import net.minecraft.world.phys.EntityHitResult;
+ import net.minecraft.world.phys.HitResult;
++import org.bukkit.event.entity.EntityCombustByEntityEvent; // CraftBukkit
+
+ public class SmallFireball extends Fireball {
+
+@@ -19,8 +20,13 @@
+ super(entitytype, level);
+ }
+
+- public SmallFireball(Level level, LivingEntity livingentity, double d0, double d1, double d2) {
+- super(EntityType.SMALL_FIREBALL, livingentity, d0, d1, d2, level);
++ public SmallFireball(Level level, LivingEntity shooter, double offsetX, double d1, double offsetY) {
++ super(EntityType.SMALL_FIREBALL, shooter, offsetX, d1, offsetY, level);
++ // CraftBukkit start
++ if (this.getOwner() != null && this.getOwner() instanceof Mob) {
++ isIncendiary = this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING);
++ }
++ // CraftBukkit end
+ }
+
+ public SmallFireball(Level level, double d0, double d1, double d2, double d3, double d4, double d5) {
+@@ -36,7 +41,14 @@
+ Entity entity1 = this.getOwner();
+ int i = entity.getRemainingFireTicks();
+
+- entity.setSecondsOnFire(5);
++ // CraftBukkit start - Entity damage by entity event + combust event
++ EntityCombustByEntityEvent event = new EntityCombustByEntityEvent((org.bukkit.entity.Projectile) this.getBukkitEntity(), entity.getBukkitEntity(), 5);
++ entity.level().getCraftServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ entity.setSecondsOnFire(event.getDuration(), false);
++ }
++ // CraftBukkit end
+ if (!entity.hurt(this.damageSources().fireball(this, entity1), 5.0F)) {
+ entity.setRemainingFireTicks(i);
+ } else if (entity1 instanceof LivingEntity) {
+@@ -53,11 +64,11 @@
+ if (!this.level().isClientSide) {
+ Entity entity = this.getOwner();
+
+- if (!(entity instanceof Mob) || this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+- BlockPos blockpos = blockhitresult.getBlockPos().relative(blockhitresult.getDirection());
++ if (isIncendiary) { // CraftBukkit
++ BlockPos blockposition = result.getBlockPos().relative(result.getDirection());
+
+- if (this.level().isEmptyBlock(blockpos)) {
+- this.level().setBlockAndUpdate(blockpos, BaseFireBlock.getState(this.level(), blockpos));
++ if (this.level().isEmptyBlock(blockposition) && !org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(this.level(), blockposition, this).isCancelled()) { // CraftBukkit
++ this.level().setBlockAndUpdate(blockposition, BaseFireBlock.getState(this.level(), blockposition));
+ }
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/SpectralArrow.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/SpectralArrow.java.patch
new file mode 100644
index 0000000000..7797849d52
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/SpectralArrow.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/entity/projectile/SpectralArrow.java
++++ b/net/minecraft/world/entity/projectile/SpectralArrow.java
+@@ -43,7 +41,7 @@
+ super.doPostHurtEffects(livingentity);
+ MobEffectInstance mobeffectinstance = new MobEffectInstance(MobEffects.GLOWING, this.duration, 0);
+
+- livingentity.addEffect(mobeffectinstance, this.getEffectSource());
++ living.addEffect(mobeffect, this.getEffectSource(), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ARROW); // CraftBukkit
+ }
+
+ @Override
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ThrowableItemProjectile.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ThrowableItemProjectile.java.patch
new file mode 100644
index 0000000000..34ac2052d7
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ThrowableItemProjectile.java.patch
@@ -0,0 +1,17 @@
+--- a/net/minecraft/world/entity/projectile/ThrowableItemProjectile.java
++++ b/net/minecraft/world/entity/projectile/ThrowableItemProjectile.java
+@@ -35,7 +35,13 @@
+
+ protected abstract Item getDefaultItem();
+
+- protected ItemStack getItemRaw() {
++ // CraftBukkit start
++ public Item getDefaultItemPublic() {
++ return getDefaultItem();
++ }
++ // CraftBukkit end
++
++ public ItemStack getItemRaw() {
+ return (ItemStack) this.getEntityData().get(ThrowableItemProjectile.DATA_ITEM_STACK);
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ThrowableProjectile.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ThrowableProjectile.java.patch
new file mode 100644
index 0000000000..1b03fbb817
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ThrowableProjectile.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/world/entity/projectile/ThrowableProjectile.java
++++ b/net/minecraft/world/entity/projectile/ThrowableProjectile.java
+@@ -67,8 +65,8 @@
+ }
+ }
+
+- if (hitresult.getType() != HitResult.Type.MISS && !flag) {
+- this.onHit(hitresult);
++ if (movingobjectposition.getType() != HitResult.EnumMovingObjectType.MISS && !flag) {
++ this.preOnHit(movingobjectposition); // CraftBukkit - projectile hit event
+ }
+
+ this.checkInsideBlocks();
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ThrownEgg.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ThrownEgg.java.patch
new file mode 100644
index 0000000000..e6eaff52bb
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ThrownEgg.java.patch
@@ -0,0 +1,73 @@
+--- a/net/minecraft/world/entity/projectile/ThrownEgg.java
++++ b/net/minecraft/world/entity/projectile/ThrownEgg.java
+@@ -10,6 +5,16 @@
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.phys.EntityHitResult;
+ import net.minecraft.world.phys.HitResult;
++import net.minecraft.core.particles.ItemParticleOption;
++import net.minecraft.core.particles.ParticleTypes;
++import net.minecraft.server.level.ServerPlayer;
++import net.minecraft.world.entity.Entity;
++import net.minecraft.world.entity.LivingEntity;
++import org.bukkit.entity.Ageable;
++import org.bukkit.entity.EntityType;
++import org.bukkit.entity.Player;
++import org.bukkit.event.player.PlayerEggThrowEvent;
++// CraftBukkit end
+
+ public class ThrownEgg extends ThrowableItemProjectile {
+
+@@ -50,20 +52,47 @@
+ protected void onHit(HitResult hitresult) {
+ super.onHit(hitresult);
+ if (!this.level().isClientSide) {
+- if (this.random.nextInt(8) == 0) {
++ // CraftBukkit start
++ boolean hatching = this.random.nextInt(8) == 0;
++ if (true) {
++ // CraftBukkit end
+ byte b0 = 1;
+
+ if (this.random.nextInt(32) == 0) {
+ b0 = 4;
+ }
+
++ // CraftBukkit start
++ EntityType hatchingType = EntityType.CHICKEN;
++
++ Entity shooter = this.getOwner();
++ if (!hatching) {
++ b0 = 0;
++ }
++ if (shooter instanceof ServerPlayer) {
++ PlayerEggThrowEvent event = new PlayerEggThrowEvent((Player) shooter.getBukkitEntity(), (org.bukkit.entity.Egg) this.getBukkitEntity(), hatching, b0, hatchingType);
++ this.level().getCraftServer().getPluginManager().callEvent(event);
++
++ b0 = event.getNumHatches();
++ hatching = event.isHatching();
++ hatchingType = event.getHatchingType();
++ // If hatching is set to false, ensure child count is 0
++ if (!hatching) {
++ b0 = 0;
++ }
++ }
++ // CraftBukkit end
++
+ for (int i = 0; i < b0; ++i) {
+- Chicken chicken = (Chicken) EntityType.CHICKEN.create(this.level());
++ Entity entitychicken = this.level().getWorld().makeEntity(new org.bukkit.Location(this.level().getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), 0.0F), hatchingType.getEntityClass()); // CraftBukkit
+
+- if (chicken != null) {
+- chicken.setAge(-24000);
+- chicken.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), 0.0F);
+- this.level().addFreshEntity(chicken);
++ if (entitychicken != null) {
++ // CraftBukkit start
++ if (entitychicken.getBukkitEntity() instanceof Ageable) {
++ ((Ageable) entitychicken.getBukkitEntity()).setBaby();
++ }
++ this.level().addFreshEntity(entitychicken, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EGG);
++ // CraftBukkit end
+ }
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch
new file mode 100644
index 0000000000..5bf2ed6971
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch
@@ -0,0 +1,56 @@
+--- a/net/minecraft/world/entity/projectile/ThrownEnderpearl.java
++++ b/net/minecraft/world/entity/projectile/ThrownEnderpearl.java
+@@ -17,6 +17,12 @@
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.phys.EntityHitResult;
+ import net.minecraft.world.phys.HitResult;
++// CraftBukkit start
++import org.bukkit.Bukkit;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.CreatureSpawnEvent;
++import org.bukkit.event.player.PlayerTeleportEvent;
++// CraftBukkit end
+
+ public class ThrownEnderpearl extends ThrowableItemProjectile {
+
+@@ -56,9 +59,12 @@
+ if (entity instanceof ServerPlayer) {
+ ServerPlayer serverplayer = (ServerPlayer) entity;
+
+- if (serverplayer.connection.isAcceptingMessages() && serverplayer.level() == this.level() && !serverplayer.isSleeping()) {
+- if (this.random.nextFloat() < 0.05F && this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) {
+- Endermite endermite = (Endermite) EntityType.ENDERMITE.create(this.level());
++ if (entityplayer.connection.isAcceptingMessages() && entityplayer.level() == this.level() && !entityplayer.isSleeping()) {
++ // CraftBukkit start - Fire PlayerTeleportEvent
++ org.bukkit.craftbukkit.entity.CraftPlayer player = entityplayer.getBukkitEntity();
++ org.bukkit.Location location = getBukkitEntity().getLocation();
++ location.setPitch(player.getLocation().getPitch());
++ location.setYaw(player.getLocation().getYaw());
+
+ if (endermite != null) {
+ endermite.moveTo(entity.getX(), entity.getY(), entity.getZ(), entity.getYRot(), entity.getXRot());
+@@ -72,8 +83,13 @@
+ entity.teleportTo(this.getX(), this.getY(), this.getZ());
+ }
+
+- entity.resetFallDistance();
+- entity.hurt(this.damageSources().fall(), 5.0F);
++ entityplayer.connection.teleport(teleEvent.getTo());
++ entity.resetFallDistance();
++ CraftEventFactory.entityDamage = this;
++ entity.hurt(this.damageSources().fall(), 5.0F);
++ CraftEventFactory.entityDamage = null;
++ }
++ // CraftBukkit end
+ this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_TELEPORT, SoundSource.PLAYERS);
+ }
+ } else if (entity != null) {
+@@ -105,7 +119,7 @@
+ public Entity changeDimension(ServerLevel serverlevel) {
+ Entity entity = this.getOwner();
+
+- if (entity != null && entity.level().dimension() != serverlevel.dimension()) {
++ if (entity != null && server != null && entity.level().dimension() != server.dimension()) { // CraftBukkit - SPIGOT-6113
+ this.setOwner((Entity) null);
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java.patch
new file mode 100644
index 0000000000..8ba925a0b9
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java.patch
@@ -0,0 +1,22 @@
+--- a/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java
++++ b/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java
+@@ -42,9 +39,18 @@
+ protected void onHit(HitResult hitresult) {
+ super.onHit(hitresult);
+ if (this.level() instanceof ServerLevel) {
+- this.level().levelEvent(2002, this.blockPosition(), PotionUtils.getColor(Potions.WATER));
++ // CraftBukkit - moved to after event
++ // this.level().levelEvent(2002, this.blockPosition(), PotionUtil.getColor(Potions.WATER));
+ int i = 3 + this.level().random.nextInt(5) + this.level().random.nextInt(5);
+
++ // CraftBukkit start
++ org.bukkit.event.entity.ExpBottleEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callExpBottleEvent(this, result, i);
++ i = event.getExperience();
++ if (event.getShowEffect()) {
++ this.level().levelEvent(2002, this.blockPosition(), PotionUtils.getColor(Potions.WATER));
++ }
++ // CraftBukkit end
++
+ ExperienceOrb.award((ServerLevel) this.level(), this.position(), i);
+ this.discard();
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ThrownPotion.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ThrownPotion.java.patch
new file mode 100644
index 0000000000..290501b436
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ThrownPotion.java.patch
@@ -0,0 +1,169 @@
+--- a/net/minecraft/world/entity/projectile/ThrownPotion.java
++++ b/net/minecraft/world/entity/projectile/ThrownPotion.java
+@@ -30,6 +29,16 @@
+ import net.minecraft.world.phys.BlockHitResult;
+ import net.minecraft.world.phys.EntityHitResult;
+ import net.minecraft.world.phys.HitResult;
++// CraftBukkit start
++import java.util.HashMap;
++import java.util.Map;
++import net.minecraft.world.effect.MobEffects;
++import net.minecraft.world.level.block.Blocks;
++import net.minecraft.world.level.block.CampfireBlock;
++import org.bukkit.craftbukkit.entity.CraftLivingEntity;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.entity.LivingEntity;
++// CraftBukkit end
+
+ public class ThrownPotion extends ThrowableItemProjectile implements ItemSupplier {
+
+@@ -103,11 +108,11 @@
+
+ if (flag) {
+ this.applyWater();
+- } else if (!list.isEmpty()) {
++ } else if (true || !list.isEmpty()) { // CraftBukkit - Call event even if no effects to apply
+ if (this.isLingering()) {
+- this.makeAreaOfEffectCloud(itemstack, potion);
++ this.makeAreaOfEffectCloud(itemstack, potionregistry, result); // CraftBukkit - Pass MovingObjectPosition
+ } else {
+- this.applySplash(list, hitresult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitresult).getEntity() : null);
++ this.applySplash(list, result.getType() == HitResult.EnumMovingObjectType.ENTITY ? ((EntityHitResult) result).getEntity() : null, result); // CraftBukkit - Pass MovingObjectPosition
+ }
+ }
+
+@@ -149,9 +154,10 @@
+
+ }
+
+- private void applySplash(List<MobEffectInstance> list, @Nullable Entity entity) {
+- AABB aabb = this.getBoundingBox().inflate(4.0D, 2.0D, 4.0D);
+- List<LivingEntity> list1 = this.level().getEntitiesOfClass(LivingEntity.class, aabb);
++ private void applySplash(List<MobEffectInstance> list, @Nullable Entity entity, HitResult position) { // CraftBukkit - Pass MovingObjectPosition
++ AABB axisalignedbb = this.getBoundingBox().inflate(4.0D, 2.0D, 4.0D);
++ List<net.minecraft.world.entity.LivingEntity> list1 = this.level().getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb);
++ Map<LivingEntity, Double> affected = new HashMap<LivingEntity, Double>(); // CraftBukkit
+
+ if (!list1.isEmpty()) {
+ Entity entity1 = this.getEffectSource();
+@@ -172,34 +178,55 @@
+ d1 = 1.0D - Math.sqrt(d0) / 4.0D;
+ }
+
+- Iterator iterator1 = list.iterator();
++ // CraftBukkit start
++ affected.put((LivingEntity) entityliving.getBukkitEntity(), d1);
++ }
++ }
++ }
++ }
+
+ while (iterator1.hasNext()) {
+ MobEffectInstance mobeffectinstance = (MobEffectInstance) iterator1.next();
+ MobEffect mobeffect = mobeffectinstance.getEffect();
+
+- if (mobeffect.isInstantenous()) {
+- mobeffect.applyInstantenousEffect(this, this.getOwner(), livingentity, mobeffectinstance.getAmplifier(), d1);
+- } else {
+- int i = mobeffectinstance.mapDuration((j) -> {
+- return (int) (d1 * (double) j + 0.5D);
+- });
+- MobEffectInstance mobeffectinstance1 = new MobEffectInstance(mobeffect, i, mobeffectinstance.getAmplifier(), mobeffectinstance.isAmbient(), mobeffectinstance.isVisible());
++ net.minecraft.world.entity.LivingEntity entityliving = ((CraftLivingEntity) victim).getHandle();
++ double d1 = event.getIntensity(victim);
++ // CraftBukkit end
+
+- if (!mobeffectinstance1.endsWithin(20)) {
+- livingentity.addEffect(mobeffectinstance1, entity1);
+- }
+- }
++ Iterator iterator1 = list.iterator();
++
++ while (iterator1.hasNext()) {
++ MobEffectInstance mobeffect = (MobEffectInstance) iterator1.next();
++ MobEffect mobeffectlist = mobeffect.getEffect();
++ // CraftBukkit start - Abide by PVP settings - for players only!
++ if (!this.level().pvpMode && this.getOwner() instanceof ServerPlayer && entityliving instanceof ServerPlayer && entityliving != this.getOwner()) {
++ if (mobeffectlist == MobEffects.MOVEMENT_SLOWDOWN || mobeffectlist == MobEffects.DIG_SLOWDOWN || mobeffectlist == MobEffects.HARM || mobeffectlist == MobEffects.BLINDNESS
++ || mobeffectlist == MobEffects.HUNGER || mobeffectlist == MobEffects.WEAKNESS || mobeffectlist == MobEffects.POISON) {
++ continue;
+ }
+ }
++ // CraftBukkit end
++
++ if (mobeffectlist.isInstantenous()) {
++ mobeffectlist.applyInstantenousEffect(this, this.getOwner(), entityliving, mobeffect.getAmplifier(), d1);
++ } else {
++ int i = mobeffect.mapDuration((j) -> {
++ return (int) (d1 * (double) j + 0.5D);
++ });
++ MobEffectInstance mobeffect1 = new MobEffectInstance(mobeffectlist, i, mobeffect.getAmplifier(), mobeffect.isAmbient(), mobeffect.isVisible());
++
++ if (!mobeffect1.endsWithin(20)) {
++ entityliving.addEffect(mobeffect1, entity1, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.POTION_SPLASH); // CraftBukkit
++ }
++ }
+ }
+ }
+ }
+
+ }
+
+- private void makeAreaOfEffectCloud(ItemStack itemstack, Potion potion) {
+- AreaEffectCloud areaeffectcloud = new AreaEffectCloud(this.level(), this.getX(), this.getY(), this.getZ());
++ private void makeAreaOfEffectCloud(ItemStack itemstack, Potion potionregistry, HitResult position) { // CraftBukkit - Pass MovingObjectPosition
++ AreaEffectCloud entityareaeffectcloud = new AreaEffectCloud(this.level(), this.getX(), this.getY(), this.getZ());
+ Entity entity = this.getOwner();
+
+ if (entity instanceof LivingEntity) {
+@@ -225,7 +256,14 @@
+ areaeffectcloud.setFixedColor(compoundtag.getInt("CustomPotionColor"));
+ }
+
+- this.level().addFreshEntity(areaeffectcloud);
++ // CraftBukkit start
++ org.bukkit.event.entity.LingeringPotionSplashEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callLingeringPotionSplashEvent(this, position, entityareaeffectcloud);
++ if (!(event.isCancelled() || entityareaeffectcloud.isRemoved())) {
++ this.level().addFreshEntity(entityareaeffectcloud);
++ } else {
++ entityareaeffectcloud.discard();
++ }
++ // CraftBukkit end
+ }
+
+ private boolean isLingering() {
+@@ -235,14 +273,26 @@
+ private void dowseFire(BlockPos blockpos) {
+ BlockState blockstate = this.level().getBlockState(blockpos);
+
+- if (blockstate.is(BlockTags.FIRE)) {
+- this.level().destroyBlock(blockpos, false, this);
+- } else if (AbstractCandleBlock.isLit(blockstate)) {
+- AbstractCandleBlock.extinguish((Player) null, blockstate, this.level(), blockpos);
+- } else if (CampfireBlock.isLitCampfire(blockstate)) {
+- this.level().levelEvent((Player) null, 1009, blockpos, 0);
+- CampfireBlock.dowse(this.getOwner(), this.level(), blockpos, blockstate);
+- this.level().setBlockAndUpdate(blockpos, (BlockState) blockstate.setValue(CampfireBlock.LIT, false));
++ if (iblockdata.is(BlockTags.FIRE)) {
++ // CraftBukkit start
++ if (CraftEventFactory.callEntityChangeBlockEvent(this, pos, Blocks.AIR.defaultBlockState())) {
++ this.level().destroyBlock(pos, false, this);
++ }
++ // CraftBukkit end
++ } else if (AbstractCandleBlock.isLit(iblockdata)) {
++ // CraftBukkit start
++ if (CraftEventFactory.callEntityChangeBlockEvent(this, pos, iblockdata.setValue(AbstractCandleBlock.LIT, false))) {
++ AbstractCandleBlock.extinguish((Player) null, iblockdata, this.level(), pos);
++ }
++ // CraftBukkit end
++ } else if (CampfireBlock.isLitCampfire(iblockdata)) {
++ // CraftBukkit start
++ if (CraftEventFactory.callEntityChangeBlockEvent(this, pos, iblockdata.setValue(CampfireBlock.LIT, false))) {
++ this.level().levelEvent((Player) null, 1009, pos, 0);
++ CampfireBlock.dowse(this.getOwner(), this.level(), pos, iblockdata);
++ this.level().setBlockAndUpdate(pos, (IBlockData) iblockdata.setValue(CampfireBlock.LIT, false));
++ }
++ // CraftBukkit end
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ThrownTrident.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ThrownTrident.java.patch
new file mode 100644
index 0000000000..e222409598
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/ThrownTrident.java.patch
@@ -0,0 +1,19 @@
+--- a/net/minecraft/world/entity/projectile/ThrownTrident.java
++++ b/net/minecraft/world/entity/projectile/ThrownTrident.java
+@@ -154,11 +150,11 @@
+ if (this.level().canSeeSky(blockpos)) {
+ LightningBolt lightningbolt = (LightningBolt) EntityType.LIGHTNING_BOLT.create(this.level());
+
+- if (lightningbolt != null) {
+- lightningbolt.moveTo(Vec3.atBottomCenterOf(blockpos));
+- lightningbolt.setCause(entity1 instanceof ServerPlayer ? (ServerPlayer) entity1 : null);
+- this.level().addFreshEntity(lightningbolt);
+- soundevent = SoundEvents.TRIDENT_THUNDER;
++ if (entitylightning != null) {
++ entitylightning.moveTo(Vec3.atBottomCenterOf(blockposition));
++ entitylightning.setCause(entity1 instanceof ServerPlayer ? (ServerPlayer) entity1 : null);
++ ((ServerLevel) this.level()).strikeLightning(entitylightning, org.bukkit.event.weather.LightningStrikeEvent.Cause.TRIDENT); // CraftBukkit
++ soundeffect = SoundEvents.TRIDENT_THUNDER;
+ f1 = 5.0F;
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/WindCharge.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/WindCharge.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/WindCharge.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/WitherSkull.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/WitherSkull.java.patch
new file mode 100644
index 0000000000..9fad777d0e
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/projectile/WitherSkull.java.patch
@@ -0,0 +1,47 @@
+--- a/net/minecraft/world/entity/projectile/WitherSkull.java
++++ b/net/minecraft/world/entity/projectile/WitherSkull.java
+@@ -20,6 +20,9 @@
+ import net.minecraft.world.level.material.FluidState;
+ import net.minecraft.world.phys.EntityHitResult;
+ import net.minecraft.world.phys.HitResult;
++// CraftBukkit start
++import org.bukkit.event.entity.ExplosionPrimeEvent;
++// CraftBukkit end
+
+ public class WitherSkull extends AbstractHurtingProjectile {
+
+@@ -68,7 +67,7 @@
+ if (entity.isAlive()) {
+ this.doEnchantDamageEffects(livingentity, entity);
+ } else {
+- livingentity.heal(5.0F);
++ entityliving.heal(5.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.WITHER); // CraftBukkit
+ }
+ }
+ } else {
+@@ -86,7 +85,7 @@
+ }
+
+ if (b0 > 0) {
+- livingentity.addEffect(new MobEffectInstance(MobEffects.WITHER, 20 * b0, 1), this.getEffectSource());
++ entityliving.addEffect(new MobEffectInstance(MobEffects.WITHER, 20 * b0, 1), this.getEffectSource(), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+ }
+ }
+
+@@ -98,7 +96,15 @@
+ protected void onHit(HitResult hitresult) {
+ super.onHit(hitresult);
+ if (!this.level().isClientSide) {
+- this.level().explode(this, this.getX(), this.getY(), this.getZ(), 1.0F, false, Level.ExplosionInteraction.MOB);
++ // CraftBukkit start
++ // this.level().explode(this, this.getX(), this.getY(), this.getZ(), 1.0F, false, World.a.MOB);
++ ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), 1.0F, false);
++ this.level().getCraftServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ this.level().explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.a.MOB);
++ }
++ // CraftBukkit end
+ this.discard();
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/raid/Raid.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/raid/Raid.java.patch
new file mode 100644
index 0000000000..8db1d9c988
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/raid/Raid.java.patch
@@ -0,0 +1,155 @@
+--- a/net/minecraft/world/entity/raid/Raid.java
++++ b/net/minecraft/world/entity/raid/Raid.java
+@@ -176,6 +176,12 @@
+ return this.status == Raid.RaidStatus.LOSS;
+ }
+
++ // CraftBukkit start
++ public boolean isInProgress() {
++ return this.status == RaidStatus.ONGOING;
++ }
++ // CraftBukkit end
++
+ public float getTotalHealth() {
+ return this.totalHealth;
+ }
+@@ -272,6 +278,7 @@
+
+ this.active = this.level.hasChunkAt(this.center);
+ if (this.level.getDifficulty() == Difficulty.PEACEFUL) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.callRaidStopEvent(this, org.bukkit.event.raid.RaidStopEvent.Reason.PEACE); // CraftBukkit
+ this.stop();
+ return;
+ }
+@@ -291,13 +298,16 @@
+ if (!this.level.isVillage(this.center)) {
+ if (this.groupsSpawned > 0) {
+ this.status = Raid.RaidStatus.LOSS;
++ org.bukkit.craftbukkit.event.CraftEventFactory.callRaidFinishEvent(this, new java.util.ArrayList<>()); // CraftBukkit
+ } else {
++ org.bukkit.craftbukkit.event.CraftEventFactory.callRaidStopEvent(this, org.bukkit.event.raid.RaidStopEvent.Reason.NOT_IN_VILLAGE); // CraftBukkit
+ this.stop();
+ }
+ }
+
+ ++this.ticksActive;
+ if (this.ticksActive >= 48000L) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.callRaidStopEvent(this, org.bukkit.event.raid.RaidStopEvent.Reason.TIMEOUT); // CraftBukkit
+ this.stop();
+ return;
+ }
+@@ -371,6 +381,7 @@
+ }
+
+ if (j > 3) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.callRaidStopEvent(this, org.bukkit.event.raid.RaidStopEvent.Reason.UNSPAWNABLE); // CraftBukkit
+ this.stop();
+ break;
+ }
+@@ -383,6 +394,7 @@
+ this.status = Raid.RaidStatus.VICTORY;
+ Iterator iterator = this.heroesOfTheVillage.iterator();
+
++ List<org.bukkit.entity.Player> winners = new java.util.ArrayList<>(); // CraftBukkit
+ while (iterator.hasNext()) {
+ UUID uuid = (UUID) iterator.next();
+ Entity entity = this.level.getEntity(uuid);
+@@ -395,12 +407,14 @@
+ if (livingentity instanceof ServerPlayer) {
+ ServerPlayer serverplayer = (ServerPlayer) livingentity;
+
+- serverplayer.awardStat(Stats.RAID_WIN);
+- CriteriaTriggers.RAID_WIN.trigger(serverplayer);
++ entityplayer.awardStat(Stats.RAID_WIN);
++ CriteriaTriggers.RAID_WIN.trigger(entityplayer);
++ winners.add(entityplayer.getBukkitEntity()); // CraftBukkit
+ }
+ }
+ }
+ }
++ org.bukkit.craftbukkit.event.CraftEventFactory.callRaidFinishEvent(this, winners); // CraftBukkit
+ }
+ }
+
+@@ -408,6 +422,7 @@
+ } else if (this.isOver()) {
+ ++this.celebrationTicks;
+ if (this.celebrationTicks >= 600) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.callRaidStopEvent(this, org.bukkit.event.raid.RaidStopEvent.Reason.FINISHED); // CraftBukkit
+ this.stop();
+ return;
+ }
+@@ -544,6 +559,10 @@
+ int j = araid_raidertype.length;
+ int k = 0;
+
++ // CraftBukkit start
++ Raider leader = null;
++ List<Raider> raiders = new java.util.ArrayList<>();
++ // CraftBukkit end
+ while (k < j) {
+ Raid.RaiderType raid_raidertype = araid_raidertype[k];
+ int l = this.getDefaultNumSpawns(raid_raidertype, i, flag1) + this.getPotentialBonusSpawns(raid_raidertype, this.random, i, difficultyinstance, flag1);
+@@ -559,11 +578,13 @@
+ raider.setPatrolLeader(true);
+ this.setLeader(i, raider);
+ flag = true;
++ leader = entityraider; // CraftBukkit
+ }
+
+- this.joinRaid(i, raider, blockpos, false);
+- if (raid_raidertype.entityType == EntityType.RAVAGER) {
+- Raider raider1 = null;
++ this.joinRaid(i, entityraider, pos, false);
++ raiders.add(entityraider); // CraftBukkit
++ if (raid_wave.entityType == EntityType.RAVAGER) {
++ Raider entityraider1 = null;
+
+ if (i == this.getNumGroups(Difficulty.NORMAL)) {
+ raider1 = (Raider) EntityType.PILLAGER.create(this.level);
+@@ -576,10 +597,11 @@
+ }
+
+ ++i1;
+- if (raider1 != null) {
+- this.joinRaid(i, raider1, blockpos, false);
+- raider1.moveTo(blockpos, 0.0F, 0.0F);
+- raider1.startRiding(raider);
++ if (entityraider1 != null) {
++ this.joinRaid(i, entityraider1, pos, false);
++ entityraider1.moveTo(pos, 0.0F, 0.0F);
++ entityraider1.startRiding(entityraider);
++ raiders.add(entityraider); // CraftBukkit
+ }
+ }
+
+@@ -597,6 +619,7 @@
+ ++this.groupsSpawned;
+ this.updateBossbar();
+ this.setDirty();
++ org.bukkit.craftbukkit.event.CraftEventFactory.callRaidSpawnWaveEvent(this, leader, raiders); // CraftBukkit
+ }
+
+ public void joinRaid(int i, Raider raider, @Nullable BlockPos blockpos, boolean flag) {
+@@ -612,7 +635,7 @@
+ raider.finalizeSpawn(this.level, this.level.getCurrentDifficultyAt(blockpos), MobSpawnType.EVENT, (SpawnGroupData) null, (CompoundTag) null);
+ raider.applyRaidBuffs(i, false);
+ raider.setOnGround(true);
+- this.level.addFreshEntityWithPassengers(raider);
++ this.level.addFreshEntityWithPassengers(raider, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.RAID); // CraftBukkit
+ }
+ }
+
+@@ -862,6 +885,12 @@
+ this.heroesOfTheVillage.add(entity.getUUID());
+ }
+
++ // CraftBukkit start - a method to get all raiders
++ public java.util.Collection<Raider> getRaiders() {
++ return this.groupRaiderMap.values().stream().flatMap(Set::stream).collect(java.util.stream.Collectors.toSet());
++ }
++ // CraftBukkit end
++
+ private static enum RaidStatus {
+
+ ONGOING, VICTORY, LOSS, STOPPED;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/raid/Raider.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/raid/Raider.java.patch
new file mode 100644
index 0000000000..17f7705f6e
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/raid/Raider.java.patch
@@ -0,0 +1,31 @@
+--- a/net/minecraft/world/entity/raid/Raider.java
++++ b/net/minecraft/world/entity/raid/Raider.java
+@@ -170,7 +165,7 @@
+ MobEffectInstance mobeffectinstance1 = new MobEffectInstance(MobEffects.BAD_OMEN, 120000, i, false, false, true);
+
+ if (!this.level().getGameRules().getBoolean(GameRules.RULE_DISABLE_RAIDS)) {
+- player.addEffect(mobeffectinstance1);
++ entityhuman.addEffect(mobeffect1, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.PATROL_CAPTAIN); // CraftBukkit
+ }
+ }
+ }
+@@ -547,7 +521,7 @@
+ while (iterator.hasNext()) {
+ Raider raider = (Raider) iterator.next();
+
+- raider.setTarget(this.mob.getTarget());
++ entityraider.setTarget(this.mob.getTarget(), org.bukkit.event.entity.EntityTargetEvent.TargetReason.FOLLOW_LEADER, true); // CraftBukkit
+ }
+
+ }
+@@ -565,8 +538,8 @@
+ while (iterator.hasNext()) {
+ Raider raider = (Raider) iterator.next();
+
+- raider.setTarget(livingentity);
+- raider.setAggressive(true);
++ entityraider.setTarget(this.mob.getTarget(), org.bukkit.event.entity.EntityTargetEvent.TargetReason.FOLLOW_LEADER, true); // CraftBukkit
++ entityraider.setAggressive(true);
+ }
+
+ this.mob.setAggressive(true);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/raid/Raids.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/raid/Raids.java.patch
new file mode 100644
index 0000000000..e1975b1818
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/raid/Raids.java.patch
@@ -0,0 +1,40 @@
+--- a/net/minecraft/world/entity/raid/Raids.java
++++ b/net/minecraft/world/entity/raid/Raids.java
+@@ -121,21 +121,34 @@
+ boolean flag = false;
+
+ if (!raid.isStarted()) {
++ /* CraftBukkit - moved down
+ if (!this.raidMap.containsKey(raid.getId())) {
+ this.raidMap.put(raid.getId(), raid);
+ }
+
+ flag = true;
+- } else if (raid.getBadOmenLevel() < raid.getMaxBadOmenLevel()) {
++ // CraftBukkit start - fixed a bug with raid: players could add up Bad Omen level even when the raid had finished
++ } else if (raid.isInProgress() && raid.getBadOmenLevel() < raid.getMaxBadOmenLevel()) {
+ flag = true;
++ // CraftBukkit end
+ } else {
+ serverplayer.removeEffect(MobEffects.BAD_OMEN);
+ serverplayer.connection.send(new ClientboundEntityEventPacket(serverplayer, (byte) 43));
+ }
+
+ if (flag) {
+- raid.absorbBadOmen(serverplayer);
+- serverplayer.connection.send(new ClientboundEntityEventPacket(serverplayer, (byte) 43));
++ // CraftBukkit start
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callRaidTriggerEvent(raid, serverPlayer)) {
++ serverPlayer.removeEffect(MobEffects.BAD_OMEN);
++ return null;
++ }
++
++ if (!this.raidMap.containsKey(raid.getId())) {
++ this.raidMap.put(raid.getId(), raid);
++ }
++ // CraftBukkit end
++ raid.absorbBadOmen(serverPlayer);
++ serverPlayer.connection.send(new ClientboundEntityEventPacket(serverPlayer, (byte) 43));
+ if (!raid.hasFirstWaveSpawned()) {
+ serverplayer.awardStat(Stats.RAID_TRIGGER);
+ CriteriaTriggers.BAD_OMEN.trigger(serverplayer);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/vehicle/AbstractMinecart.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/vehicle/AbstractMinecart.java.patch
new file mode 100644
index 0000000000..89898296d1
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/vehicle/AbstractMinecart.java.patch
@@ -0,0 +1,207 @@
+--- a/net/minecraft/world/entity/vehicle/AbstractMinecart.java
++++ b/net/minecraft/world/entity/vehicle/AbstractMinecart.java
+@@ -48,6 +48,14 @@
+ import net.minecraft.world.phys.Vec3;
+ import org.joml.Vector3f;
+
++// CraftBukkit start
++import org.bukkit.Location;
++import org.bukkit.craftbukkit.util.CraftLocation;
++import org.bukkit.entity.Vehicle;
++import org.bukkit.event.vehicle.VehicleEntityCollisionEvent;
++import org.bukkit.util.Vector;
++// CraftBukkit end
++
+ public abstract class AbstractMinecart extends VehicleEntity {
+
+ private static final float LOWERED_PASSENGER_ATTACHMENT_Y = 0.0F;
+@@ -88,8 +96,19 @@
+ enummap.put(RailShape.NORTH_EAST, Pair.of(vec3i2, vec3i1));
+ });
+
+- protected AbstractMinecart(EntityType<?> entitytype, Level level) {
+- super(entitytype, level);
++ // CraftBukkit start
++ public boolean slowWhenEmpty = true;
++ private double derailedX = 0.5;
++ private double derailedY = 0.5;
++ private double derailedZ = 0.5;
++ private double flyingX = 0.95;
++ private double flyingY = 0.95;
++ private double flyingZ = 0.95;
++ public double maxSpeed = 0.4D;
++ // CraftBukkit end
++
++ protected AbstractMinecart(EntityType<?> entityType, Level level) {
++ super(entityType, level);
+ this.targetDeltaMovement = Vec3.ZERO;
+ this.blocksBuilding = true;
+ }
+@@ -279,6 +286,14 @@
+ @Override
+ @Override
+ public void tick() {
++ // CraftBukkit start
++ double prevX = this.getX();
++ double prevY = this.getY();
++ double prevZ = this.getZ();
++ float prevYaw = this.getYRot();
++ float prevPitch = this.getXRot();
++ // CraftBukkit end
++
+ if (this.getHurtTime() > 0) {
+ this.setHurtTime(this.getHurtTime() - 1);
+ }
+@@ -288,7 +303,7 @@
+ }
+
+ this.checkBelowWorld();
+- this.handleNetherPortal();
++ // this.handleNetherPortal(); // CraftBukkit - handled in postTick
+ if (this.level().isClientSide) {
+ if (this.lerpSteps > 0) {
+ this.lerpPositionAndRotationStep(this.lerpSteps, this.lerpX, this.lerpY, this.lerpZ, this.lerpYRot, this.lerpXRot);
+@@ -346,7 +361,19 @@
+ }
+
+ this.setRot(this.getYRot(), this.getXRot());
+- if (this.getMinecartType() == AbstractMinecart.Type.RIDEABLE && this.getDeltaMovement().horizontalDistanceSqr() > 0.01D) {
++ // CraftBukkit start
++ org.bukkit.World bworld = this.level().getWorld();
++ Location from = new Location(bworld, prevX, prevY, prevZ, prevYaw, prevPitch);
++ Location to = CraftLocation.toBukkit(this.position(), bworld, this.getYRot(), this.getXRot());
++ Vehicle vehicle = (Vehicle) this.getBukkitEntity();
++
++ this.level().getCraftServer().getPluginManager().callEvent(new org.bukkit.event.vehicle.VehicleUpdateEvent(vehicle));
++
++ if (!from.equals(to)) {
++ this.level().getCraftServer().getPluginManager().callEvent(new org.bukkit.event.vehicle.VehicleMoveEvent(vehicle, from, to));
++ }
++ // CraftBukkit end
++ if (this.getMinecartType() == AbstractMinecart.EnumMinecartType.RIDEABLE && this.getDeltaMovement().horizontalDistanceSqr() > 0.01D) {
+ List<Entity> list = this.level().getEntities((Entity) this, this.getBoundingBox().inflate(0.20000000298023224D, 0.0D, 0.20000000298023224D), EntitySelector.pushableBy(this));
+
+ if (!list.isEmpty()) {
+@@ -356,8 +383,26 @@
+ Entity entity = (Entity) iterator.next();
+
+ if (!(entity instanceof Player) && !(entity instanceof IronGolem) && !(entity instanceof AbstractMinecart) && !this.isVehicle() && !entity.isPassenger()) {
++ // CraftBukkit start
++ VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent(vehicle, entity.getBukkitEntity());
++ this.level().getCraftServer().getPluginManager().callEvent(collisionEvent);
++
++ if (collisionEvent.isCancelled()) {
++ continue;
++ }
++ // CraftBukkit end
+ entity.startRiding(this);
+ } else {
++ // CraftBukkit start
++ if (!this.isPassengerOfSameVehicle(entity)) {
++ VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent(vehicle, entity.getBukkitEntity());
++ this.level().getCraftServer().getPluginManager().callEvent(collisionEvent);
++
++ if (collisionEvent.isCancelled()) {
++ continue;
++ }
++ }
++ // CraftBukkit end
+ entity.push(this);
+ }
+ }
+@@ -369,6 +414,14 @@
+ Entity entity1 = (Entity) iterator1.next();
+
+ if (!this.hasPassenger(entity1) && entity1.isPushable() && entity1 instanceof AbstractMinecart) {
++ // CraftBukkit start
++ VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent(vehicle, entity1.getBukkitEntity());
++ this.level().getCraftServer().getPluginManager().callEvent(collisionEvent);
++
++ if (collisionEvent.isCancelled()) {
++ continue;
++ }
++ // CraftBukkit end
+ entity1.push(this);
+ }
+ }
+@@ -385,7 +438,7 @@
+ }
+
+ protected double getMaxSpeed() {
+- return (this.isInWater() ? 4.0D : 8.0D) / 20.0D;
++ return (this.isInWater() ? this.maxSpeed / 2.0D: this.maxSpeed); // CraftBukkit
+ }
+
+ public void activateMinecart(int i, int j, int k, boolean flag) {}
+@@ -396,12 +449,16 @@
+
+ this.setDeltaMovement(Mth.clamp(vec3.x, -d0, d0), vec3.y, Mth.clamp(vec3.z, -d0, d0));
+ if (this.onGround()) {
+- this.setDeltaMovement(this.getDeltaMovement().scale(0.5D));
++ // CraftBukkit start - replace magic numbers with our variables
++ this.setDeltaMovement(new Vec3(this.getDeltaMovement().x * this.derailedX, this.getDeltaMovement().y * this.derailedY, this.getDeltaMovement().z * this.derailedZ));
++ // CraftBukkit end
+ }
+
+ this.move(MoverType.SELF, this.getDeltaMovement());
+ if (!this.onGround()) {
+- this.setDeltaMovement(this.getDeltaMovement().scale(0.95D));
++ // CraftBukkit start - replace magic numbers with our variables
++ this.setDeltaMovement(new Vec3(this.getDeltaMovement().x * this.flyingX, this.getDeltaMovement().y * this.flyingY, this.getDeltaMovement().z * this.flyingZ));
++ // CraftBukkit end
+ }
+
+ }
+@@ -598,8 +654,8 @@
+ }
+
+ protected void applyNaturalSlowdown() {
+- double d0 = this.isVehicle() ? 0.997D : 0.96D;
+- Vec3 vec3 = this.getDeltaMovement();
++ double d0 = this.isVehicle() || !this.slowWhenEmpty ? 0.997D : 0.96D; // CraftBukkit - add !this.slowWhenEmpty
++ Vec3 vec3d = this.getDeltaMovement();
+
+ vec3 = vec3.multiply(d0, 0.0D, d0);
+ if (this.isInWater()) {
+@@ -741,6 +793,14 @@
+ if (!this.level().isClientSide) {
+ if (!entity.noPhysics && !this.noPhysics) {
+ if (!this.hasPassenger(entity)) {
++ // CraftBukkit start
++ VehicleEntityCollisionEvent collisionEvent = new VehicleEntityCollisionEvent((Vehicle) this.getBukkitEntity(), entity.getBukkitEntity());
++ this.level().getCraftServer().getPluginManager().callEvent(collisionEvent);
++
++ if (collisionEvent.isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
+ double d0 = entity.getX() - this.getX();
+ double d1 = entity.getZ() - this.getZ();
+ double d2 = d0 * d0 + d1 * d1;
+@@ -923,4 +975,26 @@
+
+ private Type() {}
+ }
++
++ // CraftBukkit start - Methods for getting and setting flying and derailed velocity modifiers
++ public Vector getFlyingVelocityMod() {
++ return new Vector(flyingX, flyingY, flyingZ);
++ }
++
++ public void setFlyingVelocityMod(Vector flying) {
++ flyingX = flying.getX();
++ flyingY = flying.getY();
++ flyingZ = flying.getZ();
++ }
++
++ public Vector getDerailedVelocityMod() {
++ return new Vector(derailedX, derailedY, derailedZ);
++ }
++
++ public void setDerailedVelocityMod(Vector derailed) {
++ derailedX = derailed.getX();
++ derailedY = derailed.getY();
++ derailedZ = derailed.getZ();
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java.patch
new file mode 100644
index 0000000000..513d659d35
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java.patch
@@ -0,0 +1,51 @@
+--- a/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java
++++ b/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java
+@@ -17,6 +17,13 @@
+ import net.minecraft.world.inventory.AbstractContainerMenu;
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.level.Level;
++// CraftBukkit start
++import java.util.List;
++import org.bukkit.Location;
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++import org.bukkit.inventory.InventoryHolder;
++// CraftBukkit end
+
+ public abstract class AbstractMinecartContainer extends AbstractMinecart implements ContainerEntity {
+
+@@ -25,9 +32,12 @@
+ private ResourceLocation lootTable;
+ private long lootTableSeed;
+
+- protected AbstractMinecartContainer(EntityType<?> entitytype, Level level) {
+- super(entitytype, level);
+- this.itemStacks = NonNullList.withSize(36, ItemStack.EMPTY);
++ // CraftBukkit start
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++ private int maxStack = MAX_STACK;
++
++ public List<ItemStack> getContents() {
++ return this.itemStacks;
+ }
+
+ protected AbstractMinecartContainer(EntityType<?> entitytype, double d0, double d1, double d2, Level level) {
+@@ -41,7 +71,18 @@
+ super.destroy(damagesource);
+ this.chestVehicleDestroyed(damagesource, this.level(), this);
+ }
++ // CraftBukkit end
+
++ protected AbstractMinecartContainer(EntityType<?> entityType, Level level) {
++ super(entityType, level);
++ this.itemStacks = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY); // CraftBukkit - SPIGOT-3513
++ }
++
++ protected AbstractMinecartContainer(EntityType<?> entityType, double x, double d1, double y, Level world) {
++ super(entityType, world, x, d1, y);
++ this.itemStacks = NonNullList.withSize(this.getContainerSize(), ItemStack.EMPTY); // CraftBukkit - SPIGOT-3513
++ }
++
+ @Override
+ @Override
+ public ItemStack getItem(int i) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/vehicle/Boat.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/vehicle/Boat.java.patch
new file mode 100644
index 0000000000..341637cd7d
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/vehicle/Boat.java.patch
@@ -0,0 +1,118 @@
+--- a/net/minecraft/world/entity/vehicle/Boat.java
++++ b/net/minecraft/world/entity/vehicle/Boat.java
+@@ -55,6 +55,15 @@
+ import net.minecraft.world.phys.shapes.VoxelShape;
+ import org.joml.Vector3f;
+
++// CraftBukkit start
++import org.bukkit.Location;
++import org.bukkit.craftbukkit.util.CraftLocation;
++import org.bukkit.entity.Vehicle;
++import org.bukkit.event.vehicle.VehicleDestroyEvent;
++import org.bukkit.event.vehicle.VehicleEntityCollisionEvent;
++import org.bukkit.event.vehicle.VehicleMoveEvent;
++// CraftBukkit end
++
+ public class Boat extends VehicleEntity implements VariantHolder<Boat.Type> {
+
+ private static final EntityDataAccessor<Integer> DATA_ID_TYPE = SynchedEntityData.defineId(Boat.class, EntityDataSerializers.INT);
+@@ -92,8 +101,16 @@
+ private float bubbleAngle;
+ private float bubbleAngleO;
+
+- public Boat(EntityType<? extends Boat> entitytype, Level level) {
+- super(entitytype, level);
++ // CraftBukkit start
++ // PAIL: Some of these haven't worked since a few updates, and since 1.9 they are less and less applicable.
++ public double maxSpeed = 0.4D;
++ public double occupiedDeceleration = 0.2D;
++ public double unoccupiedDeceleration = -1;
++ public boolean landBoats = false;
++ // CraftBukkit end
++
++ public Boat(EntityType<? extends Boat> entityType, Level level) {
++ super(entityType, level);
+ this.paddlePositions = new float[2];
+ this.blocksBuilding = true;
+ }
+@@ -202,9 +209,29 @@
+ public void push(Entity entity) {
+ if (entity instanceof Boat) {
+ if (entity.getBoundingBox().minY < this.getBoundingBox().maxY) {
++ // CraftBukkit start
++ if (!this.isPassengerOfSameVehicle(entity)) {
++ VehicleEntityCollisionEvent event = new VehicleEntityCollisionEvent((Vehicle) this.getBukkitEntity(), entity.getBukkitEntity());
++ this.level().getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return;
++ }
++ }
++ // CraftBukkit end
+ super.push(entity);
+ }
+ } else if (entity.getBoundingBox().minY <= this.getBoundingBox().minY) {
++ // CraftBukkit start
++ if (!this.isPassengerOfSameVehicle(entity)) {
++ VehicleEntityCollisionEvent event = new VehicleEntityCollisionEvent((Vehicle) this.getBukkitEntity(), entity.getBukkitEntity());
++ this.level().getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return;
++ }
++ }
++ // CraftBukkit end
+ super.push(entity);
+ }
+
+@@ -308,6 +325,7 @@
+ return this.getDirection().getClockWise();
+ }
+
++ private Location lastLocation; // CraftBukkit
+ @Override
+ @Override
+ public void tick() {
+@@ -349,6 +366,22 @@
+ this.setDeltaMovement(Vec3.ZERO);
+ }
+
++ // CraftBukkit start
++ org.bukkit.Server server = this.level().getCraftServer();
++ org.bukkit.World bworld = this.level().getWorld();
++
++ Location to = CraftLocation.toBukkit(this.position(), bworld, this.getYRot(), this.getXRot());
++ Vehicle vehicle = (Vehicle) this.getBukkitEntity();
++
++ server.getPluginManager().callEvent(new org.bukkit.event.vehicle.VehicleUpdateEvent(vehicle));
++
++ if (lastLocation != null && !lastLocation.equals(to)) {
++ VehicleMoveEvent event = new VehicleMoveEvent(vehicle, lastLocation, to);
++ server.getPluginManager().callEvent(event);
++ }
++ lastLocation = vehicle.getLocation();
++ // CraftBukkit end
++
+ this.tickBubbleColumn();
+
+ for (int i = 0; i <= 1; ++i) {
+@@ -841,6 +867,11 @@
+
+ this.causeFallDamage(this.fallDistance, 1.0F, this.damageSources().fall());
+ if (!this.level().isClientSide && !this.isRemoved()) {
++ // CraftBukkit start
++ Vehicle vehicle = (Vehicle) this.getBukkitEntity();
++ VehicleDestroyEvent destroyEvent = new VehicleDestroyEvent(vehicle, null);
++ this.level().getCraftServer().getPluginManager().callEvent(destroyEvent);
++ if (!destroyEvent.isCancelled()) {
+ this.kill();
+ if (this.level().getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
+ int i;
+@@ -854,6 +885,7 @@
+ }
+ }
+ }
++ } // CraftBukkit end
+ }
+
+ this.resetFallDistance();
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/vehicle/ChestBoat.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/vehicle/ChestBoat.java.patch
new file mode 100644
index 0000000000..f287bad03c
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/vehicle/ChestBoat.java.patch
@@ -0,0 +1,68 @@
+--- a/net/minecraft/world/entity/vehicle/ChestBoat.java
++++ b/net/minecraft/world/entity/vehicle/ChestBoat.java
+@@ -23,6 +23,13 @@
+ import net.minecraft.world.item.Items;
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.gameevent.GameEvent;
++// CraftBukkit start
++import java.util.List;
++import org.bukkit.Location;
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++import org.bukkit.inventory.InventoryHolder;
++// CraftBukkit end
+
+ public class ChestBoat extends Boat implements HasCustomInventoryScreen, ContainerEntity {
+
+@@ -264,4 +245,51 @@
+ public void stopOpen(Player player) {
+ this.level().gameEvent(GameEvent.CONTAINER_CLOSE, this.position(), GameEvent.Context.of((Entity) player));
+ }
++
++ // CraftBukkit start
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++ private int maxStack = MAX_STACK;
++
++ @Override
++ public List<ItemStack> getContents() {
++ return this.itemStacks;
++ }
++
++ @Override
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ @Override
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ @Override
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ @Override
++ public InventoryHolder getOwner() {
++ org.bukkit.entity.Entity entity = getBukkitEntity();
++ if (entity instanceof InventoryHolder) return (InventoryHolder) entity;
++ return null;
++ }
++
++ @Override
++ public int getMaxStackSize() {
++ return maxStack;
++ }
++
++ @Override
++ public void setMaxStackSize(int size) {
++ maxStack = size;
++ }
++
++ @Override
++ public Location getLocation() {
++ return getBukkitEntity().getLocation();
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java.patch
new file mode 100644
index 0000000000..6027706fe1
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java.patch
@@ -0,0 +1,15 @@
+--- a/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java
++++ b/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java
+@@ -158,5 +143,12 @@
+ public boolean isValid() {
+ return !MinecartCommandBlock.this.isRemoved();
+ }
++
++ // CraftBukkit start
++ @Override
++ public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) {
++ return (org.bukkit.craftbukkit.entity.CraftMinecartCommand) MinecartCommandBlock.this.getBukkitEntity();
++ }
++ // CraftBukkit end
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/vehicle/MinecartTNT.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/vehicle/MinecartTNT.java.patch
new file mode 100644
index 0000000000..1ad988dd1c
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/vehicle/MinecartTNT.java.patch
@@ -0,0 +1,29 @@
+--- a/net/minecraft/world/entity/vehicle/MinecartTNT.java
++++ b/net/minecraft/world/entity/vehicle/MinecartTNT.java
+@@ -22,6 +22,9 @@
+ import net.minecraft.world.level.block.Blocks;
+ import net.minecraft.world.level.block.state.BlockState;
+ import net.minecraft.world.level.material.FluidState;
++// CraftBukkit start
++import org.bukkit.event.entity.ExplosionPrimeEvent;
++// CraftBukkit end
+
+ public class MinecartTNT extends AbstractMinecart {
+
+@@ -121,7 +118,15 @@
+ d1 = 5.0D;
+ }
+
+- this.level().explode(this, damagesource, (ExplosionDamageCalculator) null, this.getX(), this.getY(), this.getZ(), (float) (4.0D + this.random.nextDouble() * 1.5D * d1), false, Level.ExplosionInteraction.TNT);
++ // CraftBukkit start
++ ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), (float) (4.0D + this.random.nextDouble() * 1.5D * d1), false);
++ this.level().getCraftServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ fuse = -1;
++ return;
++ }
++ this.level().explode(this, damageSource, (ExplosionDamageCalculator) null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.a.TNT);
++ // CraftBukkit end
+ this.discard();
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/vehicle/VehicleEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/vehicle/VehicleEntity.java.patch
new file mode 100644
index 0000000000..36804ea421
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/entity/vehicle/VehicleEntity.java.patch
@@ -0,0 +1,64 @@
+--- a/net/minecraft/world/entity/vehicle/VehicleEntity.java
++++ b/net/minecraft/world/entity/vehicle/VehicleEntity.java
+@@ -13,6 +13,12 @@
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.gameevent.GameEvent;
+
++// CraftBukkit start
++import org.bukkit.entity.Vehicle;
++import org.bukkit.event.vehicle.VehicleDamageEvent;
++import org.bukkit.event.vehicle.VehicleDestroyEvent;
++// CraftBukkit end
++
+ public abstract class VehicleEntity extends Entity {
+
+ protected static final EntityDataAccessor<Integer> DATA_ID_HURT = SynchedEntityData.defineId(VehicleEntity.class, EntityDataSerializers.INT);
+@@ -30,6 +35,18 @@
+ if (this.isInvulnerableTo(damagesource)) {
+ return false;
+ } else {
++ // CraftBukkit start
++ Vehicle vehicle = (Vehicle) this.getBukkitEntity();
++ org.bukkit.entity.Entity attacker = (source.getEntity() == null) ? null : source.getEntity().getBukkitEntity();
++
++ VehicleDamageEvent event = new VehicleDamageEvent(vehicle, attacker, (double) amount);
++ this.level().getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return false;
++ }
++ amount = (float) event.getDamage();
++ // CraftBukkit end
+ this.setHurtDir(-this.getHurtDir());
+ this.setHurtTime(10);
+ this.markHurt();
+@@ -39,10 +56,28 @@
+
+ if ((flag || this.getDamage() <= 40.0F) && !this.shouldSourceDestroy(damagesource)) {
+ if (flag) {
++ // CraftBukkit start
++ VehicleDestroyEvent destroyEvent = new VehicleDestroyEvent(vehicle, attacker);
++ this.level().getCraftServer().getPluginManager().callEvent(destroyEvent);
++
++ if (destroyEvent.isCancelled()) {
++ this.setDamage(40.0F); // Maximize damage so this doesn't get triggered again right away
++ return true;
++ }
++ // CraftBukkit end
+ this.discard();
+ }
+ } else {
+- this.destroy(damagesource);
++ // CraftBukkit start
++ VehicleDestroyEvent destroyEvent = new VehicleDestroyEvent(vehicle, attacker);
++ this.level().getCraftServer().getPluginManager().callEvent(destroyEvent);
++
++ if (destroyEvent.isCancelled()) {
++ this.setDamage(40.0F); // Maximize damage so this doesn't get triggered again right away
++ return true;
++ }
++ // CraftBukkit end
++ this.destroy(source);
+ }
+
+ return true;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/food/FoodData.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/food/FoodData.java.patch
new file mode 100644
index 0000000000..601d6a3963
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/food/FoodData.java.patch
@@ -0,0 +1,100 @@
+--- a/net/minecraft/world/food/FoodData.java
++++ b/net/minecraft/world/food/FoodData.java
+@@ -13,20 +15,37 @@
+ private float saturationLevel = 5.0F;
+ private float exhaustionLevel;
+ private int tickTimer;
++ // CraftBukkit start
++ private Player entityhuman;
++ public int saturatedRegenRate = 10;
++ public int unsaturatedRegenRate = 80;
++ public int starvationRate = 80;
++ // CraftBukkit end
+ private int lastFoodLevel = 20;
+
+- public FoodData() {}
++ public FoodData() { throw new AssertionError("Whoopsie, we missed the bukkit."); } // CraftBukkit start - throw an error
+
+- public void eat(int i, float f) {
+- this.foodLevel = Math.min(i + this.foodLevel, 20);
+- this.saturationLevel = Math.min(this.saturationLevel + (float) i * f * 2.0F, (float) this.foodLevel);
++ // CraftBukkit start - added EntityHuman constructor
++ public FoodData(Player entityhuman) {
++ org.apache.commons.lang.Validate.notNull(entityhuman);
++ this.entityhuman = entityhuman;
+ }
++ // CraftBukkit end
+
+ public void eat(Item item, ItemStack itemstack) {
+ if (item.isEdible()) {
+- FoodProperties foodproperties = item.getFoodProperties();
++ FoodProperties foodinfo = item.getFoodProperties();
++ // CraftBukkit start
++ int oldFoodLevel = foodLevel;
+
+- this.eat(foodproperties.getNutrition(), foodproperties.getSaturationModifier());
++ org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(entityhuman, foodinfo.getNutrition() + oldFoodLevel, stack);
++
++ if (!event.isCancelled()) {
++ this.eat(event.getFoodLevel() - oldFoodLevel, foodinfo.getSaturationModifier());
++ }
++
++ ((ServerPlayer) entityhuman).getBukkitEntity().sendHealthUpdate();
++ // CraftBukkit end
+ }
+
+ }
+@@ -39,8 +63,16 @@
+ this.exhaustionLevel -= 4.0F;
+ if (this.saturationLevel > 0.0F) {
+ this.saturationLevel = Math.max(this.saturationLevel - 1.0F, 0.0F);
+- } else if (difficulty != Difficulty.PEACEFUL) {
+- this.foodLevel = Math.max(this.foodLevel - 1, 0);
++ } else if (enumdifficulty != Difficulty.PEACEFUL) {
++ // CraftBukkit start
++ org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(player, Math.max(this.foodLevel - 1, 0));
++
++ if (!event.isCancelled()) {
++ this.foodLevel = event.getFoodLevel();
++ }
++
++ ((ServerPlayer) player).connection.send(new ClientboundSetHealthPacket(((ServerPlayer) player).getBukkitEntity().getScaledHealth(), this.foodLevel, this.saturationLevel));
++ // CraftBukkit end
+ }
+ }
+
+@@ -48,24 +80,26 @@
+
+ if (flag && this.saturationLevel > 0.0F && player.isHurt() && this.foodLevel >= 20) {
+ ++this.tickTimer;
+- if (this.tickTimer >= 10) {
++ if (this.tickTimer >= this.saturatedRegenRate) { // CraftBukkit
+ float f = Math.min(this.saturationLevel, 6.0F);
+
+- player.heal(f / 6.0F);
+- this.addExhaustion(f);
++ player.heal(f / 6.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.SATIATED); // CraftBukkit - added RegainReason
++ // this.addExhaustion(f); CraftBukkit - EntityExhaustionEvent
++ player.causeFoodExhaustion(f, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.REGEN); // CraftBukkit - EntityExhaustionEvent
+ this.tickTimer = 0;
+ }
+ } else if (flag && this.foodLevel >= 18 && player.isHurt()) {
+ ++this.tickTimer;
+- if (this.tickTimer >= 80) {
+- player.heal(1.0F);
+- this.addExhaustion(6.0F);
++ if (this.tickTimer >= this.unsaturatedRegenRate) { // CraftBukkit - add regen rate manipulation
++ player.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.SATIATED); // CraftBukkit - added RegainReason
++ // this.a(6.0F); CraftBukkit - EntityExhaustionEvent
++ player.causeFoodExhaustion(6.0f, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.REGEN); // CraftBukkit - EntityExhaustionEvent
+ this.tickTimer = 0;
+ }
+ } else if (this.foodLevel <= 0) {
+ ++this.tickTimer;
+- if (this.tickTimer >= 80) {
+- if (player.getHealth() > 10.0F || difficulty == Difficulty.HARD || player.getHealth() > 1.0F && difficulty == Difficulty.NORMAL) {
++ if (this.tickTimer >= this.starvationRate) { // CraftBukkit - add regen rate manipulation
++ if (player.getHealth() > 10.0F || enumdifficulty == Difficulty.HARD || player.getHealth() > 1.0F && enumdifficulty == Difficulty.NORMAL) {
+ player.hurt(player.damageSources().starve(), 1.0F);
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/AbstractContainerMenu.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/AbstractContainerMenu.java.patch
new file mode 100644
index 0000000000..45f274d0b5
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/AbstractContainerMenu.java.patch
@@ -0,0 +1,194 @@
+--- a/net/minecraft/world/inventory/AbstractContainerMenu.java
++++ b/net/minecraft/world/inventory/AbstractContainerMenu.java
+@@ -33,6 +35,18 @@
+ import net.minecraft.world.level.block.entity.BlockEntity;
+ import org.slf4j.Logger;
+
++// CraftBukkit start
++import com.google.common.base.Preconditions;
++import java.util.HashMap;
++import java.util.Map;
++import org.bukkit.craftbukkit.inventory.CraftInventory;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.Event.Result;
++import org.bukkit.event.inventory.InventoryDragEvent;
++import org.bukkit.event.inventory.InventoryType;
++import org.bukkit.inventory.InventoryView;
++// CraftBukkit end
++
+ public abstract class AbstractContainerMenu {
+
+ private static final Logger LOGGER = LogUtils.getLogger();
+@@ -63,7 +77,28 @@
+ private ContainerSynchronizer synchronizer;
+ private boolean suppressRemoteUpdates;
+
+- protected AbstractContainerMenu(@Nullable MenuType<?> menutype, int i) {
++ // CraftBukkit start
++ public boolean checkReachable = true;
++ public abstract InventoryView getBukkitView();
++ public void transferTo(AbstractContainerMenu other, org.bukkit.craftbukkit.entity.CraftHumanEntity player) {
++ InventoryView source = this.getBukkitView(), destination = other.getBukkitView();
++ ((CraftInventory) source.getTopInventory()).getInventory().onClose(player);
++ ((CraftInventory) source.getBottomInventory()).getInventory().onClose(player);
++ ((CraftInventory) destination.getTopInventory()).getInventory().onOpen(player);
++ ((CraftInventory) destination.getBottomInventory()).getInventory().onOpen(player);
++ }
++ private Component title;
++ public final Component getTitle() {
++ Preconditions.checkState(this.title != null, "Title not set");
++ return this.title;
++ }
++ public final void setTitle(Component title) {
++ Preconditions.checkState(this.title == null, "Title already set");
++ this.title = title;
++ }
++ // CraftBukkit end
++
++ protected AbstractContainerMenu(@Nullable MenuType<?> menuType, int containerId) {
+ this.carried = ItemStack.EMPTY;
+ this.remoteSlots = NonNullList.create();
+ this.remoteDataSlots = new IntArrayList();
+@@ -164,9 +199,14 @@
+
+ }
+
+- public void removeSlotListener(ContainerListener containerlistener) {
+- this.containerListeners.remove(containerlistener);
++ // CraftBukkit start
++ public void broadcastCarriedItem() {
++ this.remoteCarried = this.getCarried().copy();
++ if (this.synchronizer != null) {
++ this.synchronizer.sendCarriedChange(this, this.remoteCarried);
++ }
+ }
++ // CraftBukkit end
+
+ public NonNullList<ItemStack> getItems() {
+ NonNullList<ItemStack> nonnulllist = NonNullList.create();
+@@ -380,7 +424,7 @@
+ }
+ } else if (this.quickcraftStatus == 2) {
+ if (!this.quickcraftSlots.isEmpty()) {
+- if (this.quickcraftSlots.size() == 1) {
++ if (false && this.quickcraftSlots.size() == 1) { // CraftBukkit - treat everything as a drag since we are unable to easily call InventoryClickEvent instead
+ k = ((Slot) this.quickcraftSlots.iterator().next()).index;
+ this.resetQuickCraft();
+ this.doClick(k, this.quickcraftType, ClickType.PICKUP, player);
+@@ -396,6 +440,7 @@
+ l = this.getCarried().getCount();
+ Iterator iterator = this.quickcraftSlots.iterator();
+
++ Map<Integer, ItemStack> draggedSlots = new HashMap<Integer, ItemStack>(); // CraftBukkit - Store slots from drag in map (raw slot id -> new stack)
+ while (iterator.hasNext()) {
+ Slot slot1 = (Slot) iterator.next();
+ ItemStack itemstack2 = this.getCarried();
+@@ -406,12 +451,48 @@
+ int l1 = Math.min(getQuickCraftPlaceCount(this.quickcraftSlots, this.quickcraftType, itemstack1) + j1, k1);
+
+ l -= l1 - j1;
+- slot1.setByPlayer(itemstack1.copyWithCount(l1));
++ // slot1.setByPlayer(itemstack1.copyWithCount(l1));
++ draggedSlots.put(slot1.index, itemstack1.copyWithCount(l1)); // CraftBukkit - Put in map instead of setting
+ }
+ }
+
+- itemstack1.setCount(l);
+- this.setCarried(itemstack1);
++ // CraftBukkit start - InventoryDragEvent
++ InventoryView view = getBukkitView();
++ org.bukkit.inventory.ItemStack newcursor = CraftItemStack.asCraftMirror(itemstack1);
++ newcursor.setAmount(l);
++ Map<Integer, org.bukkit.inventory.ItemStack> eventmap = new HashMap<Integer, org.bukkit.inventory.ItemStack>();
++ for (Map.Entry<Integer, ItemStack> ditem : draggedSlots.entrySet()) {
++ eventmap.put(ditem.getKey(), CraftItemStack.asBukkitCopy(ditem.getValue()));
++ }
++
++ // It's essential that we set the cursor to the new value here to prevent item duplication if a plugin closes the inventory.
++ ItemStack oldCursor = this.getCarried();
++ this.setCarried(CraftItemStack.asNMSCopy(newcursor));
++
++ InventoryDragEvent event = new InventoryDragEvent(view, (newcursor.getType() != org.bukkit.Material.AIR ? newcursor : null), CraftItemStack.asBukkitCopy(oldCursor), this.quickcraftType == 1, eventmap);
++ player.level().getCraftServer().getPluginManager().callEvent(event);
++
++ // Whether or not a change was made to the inventory that requires an update.
++ boolean needsUpdate = event.getResult() != Result.DEFAULT;
++
++ if (event.getResult() != Result.DENY) {
++ for (Map.Entry<Integer, ItemStack> dslot : draggedSlots.entrySet()) {
++ view.setItem(dslot.getKey(), CraftItemStack.asBukkitCopy(dslot.getValue()));
++ }
++ // The only time the carried item will be set to null is if the inventory is closed by the server.
++ // If the inventory is closed by the server, then the cursor items are dropped. This is why we change the cursor early.
++ if (this.getCarried() != null) {
++ this.setCarried(CraftItemStack.asNMSCopy(event.getCursor()));
++ needsUpdate = true;
++ }
++ } else {
++ this.setCarried(oldCursor);
++ }
++
++ if (needsUpdate && player instanceof ServerPlayer) {
++ this.sendAllDataToRemote();
++ }
++ // CraftBukkit end
+ }
+
+ this.resetQuickCraft();
+@@ -429,8 +510,11 @@
+ if (i == -999) {
+ if (!this.getCarried().isEmpty()) {
+ if (clickaction == ClickAction.PRIMARY) {
+- player.drop(this.getCarried(), true);
++ // CraftBukkit start
++ ItemStack carried = this.getCarried();
+ this.setCarried(ItemStack.EMPTY);
++ player.drop(carried, true);
++ // CraftBukkit start
+ } else {
+ player.drop(this.getCarried().split(1), true);
+ }
+@@ -493,6 +577,15 @@
+ }
+
+ slot.setChanged();
++ // CraftBukkit start - Make sure the client has the right slot contents
++ if (player instanceof ServerPlayer && slot.getMaxStackSize() != 64) {
++ ((ServerPlayer) player).connection.send(new ClientboundContainerSetSlotPacket(this.containerId, this.incrementStateId(), slot.index, slot.getItem()));
++ // Updating a crafting inventory makes the client reset the result slot, have to send it again
++ if (this.getBukkitView().getType() == InventoryType.WORKBENCH || this.getBukkitView().getType() == InventoryType.CRAFTING) {
++ ((ServerPlayer) player).connection.send(new ClientboundContainerSetSlotPacket(this.containerId, this.incrementStateId(), 0, this.getSlot(0).getItem()));
++ }
++ }
++ // CraftBukkit end
+ }
+ } else {
+ int j2;
+@@ -611,13 +702,14 @@
+ ItemStack itemstack = this.getCarried();
+
+ if (!itemstack.isEmpty()) {
++ this.setCarried(ItemStack.EMPTY); // CraftBukkit - SPIGOT-4556 - from below
+ if (player.isAlive() && !((ServerPlayer) player).hasDisconnected()) {
+ player.getInventory().placeItemBackInInventory(itemstack);
+ } else {
+ player.drop(itemstack, false);
+ }
+
+- this.setCarried(ItemStack.EMPTY);
++ // this.setCarried(ItemStack.EMPTY); // CraftBukkit - moved up
+ }
+ }
+
+@@ -834,6 +926,11 @@
+ }
+
+ public ItemStack getCarried() {
++ // CraftBukkit start
++ if (this.carried.isEmpty()) {
++ this.setCarried(ItemStack.EMPTY);
++ }
++ // CraftBukkit end
+ return this.carried;
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/AbstractFurnaceMenu.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/AbstractFurnaceMenu.java.patch
new file mode 100644
index 0000000000..aaf5b6de89
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/AbstractFurnaceMenu.java.patch
@@ -0,0 +1,55 @@
+--- a/net/minecraft/world/inventory/AbstractFurnaceMenu.java
++++ b/net/minecraft/world/inventory/AbstractFurnaceMenu.java
+@@ -13,6 +13,10 @@
+ import net.minecraft.world.item.crafting.RecipeType;
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity;
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftInventoryFurnace;
++import org.bukkit.craftbukkit.inventory.CraftInventoryView;
++// CraftBukkit end
+
+ public abstract class AbstractFurnaceMenu extends RecipeBookMenu<Container> {
+
+@@ -31,9 +35,21 @@
+ private final RecipeType<? extends AbstractCookingRecipe> recipeType;
+ private final RecipeBookType recipeBookType;
+
+- protected AbstractFurnaceMenu(MenuType<?> menutype, RecipeType<? extends AbstractCookingRecipe> recipetype, RecipeBookType recipebooktype, int i, Inventory inventory) {
+- this(menutype, recipetype, recipebooktype, i, inventory, new SimpleContainer(3), new SimpleContainerData(4));
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity = null;
++ private Inventory player;
++
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ CraftInventoryFurnace inventory = new CraftInventoryFurnace((AbstractFurnaceBlockEntity) this.container);
++ bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), inventory, this);
++ return bukkitEntity;
+ }
++ // CraftBukkit end
+
+ protected AbstractFurnaceMenu(MenuType<?> menutype, RecipeType<? extends AbstractCookingRecipe> recipetype, RecipeBookType recipebooktype, int i, Inventory inventory, Container container, ContainerData containerdata) {
+ super(menutype, i);
+@@ -46,7 +66,8 @@
+ this.level = inventory.player.level();
+ this.addSlot(new Slot(container, 0, 56, 17));
+ this.addSlot(new FurnaceFuelSlot(this, container, 1, 56, 53));
+- this.addSlot(new FurnaceResultSlot(inventory.player, container, 2, 116, 35));
++ this.addSlot(new FurnaceResultSlot(playerInventory.player, container, 2, 116, 35));
++ this.player = playerInventory; // CraftBukkit - save player
+
+ int j;
+
+@@ -112,6 +125,7 @@
+ @Override
+ @Override
+ public boolean stillValid(Player player) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return this.container.stillValid(player);
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/AnvilMenu.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/AnvilMenu.java.patch
new file mode 100644
index 0000000000..421c6d7e48
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/AnvilMenu.java.patch
@@ -0,0 +1,138 @@
+--- a/net/minecraft/world/inventory/AnvilMenu.java
++++ b/net/minecraft/world/inventory/AnvilMenu.java
+@@ -19,6 +19,10 @@
+ import net.minecraft.world.level.block.state.BlockState;
+ import org.slf4j.Logger;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftInventoryView;
++// CraftBukkit end
++
+ public class AnvilMenu extends ItemCombinerMenu {
+
+ public static final int INPUT_SLOT = 0;
+@@ -42,6 +46,11 @@
+ private static final int ADDITIONAL_SLOT_X_PLACEMENT = 76;
+ private static final int RESULT_SLOT_X_PLACEMENT = 134;
+ private static final int SLOT_Y_PLACEMENT = 47;
++ // CraftBukkit start
++ public static final int DEFAULT_DENIED_COST = -1;
++ public int maximumRepairCost = 40;
++ private CraftInventoryView bukkitEntity;
++ // CraftBukkit end
+
+ public AnvilMenu(int i, Inventory inventory) {
+ this(i, inventory, ContainerLevelAccess.NULL);
+@@ -70,9 +77,8 @@
+ }
+
+ @Override
+- @Override
+- protected boolean mayPickup(Player player, boolean flag) {
+- return (player.getAbilities().instabuild || player.experienceLevel >= this.cost.get()) && this.cost.get() > 0;
++ protected boolean mayPickup(Player player, boolean hasStack) {
++ return (player.getAbilities().instabuild || player.experienceLevel >= this.cost.get()) && this.cost.get() > AnvilMenu.DEFAULT_DENIED_COST && hasStack; // CraftBukkit - allow cost 0 like a free item
+ }
+
+ @Override
+@@ -96,9 +101,9 @@
+ this.inputSlots.setItem(1, ItemStack.EMPTY);
+ }
+
+- this.cost.set(0);
+- this.access.execute((level, blockpos) -> {
+- BlockState blockstate = level.getBlockState(blockpos);
++ this.cost.set(DEFAULT_DENIED_COST); // CraftBukkit - use a variable for set a cost for denied item
++ this.access.execute((world, blockposition) -> {
++ IBlockData iblockdata = world.getBlockState(blockposition);
+
+ if (!player.getAbilities().instabuild && blockstate.is(BlockTags.ANVIL) && player.getRandom().nextFloat() < 0.12F) {
+ BlockState blockstate1 = AnvilBlock.damage(blockstate);
+@@ -128,8 +132,8 @@
+ byte b1 = 0;
+
+ if (itemstack.isEmpty()) {
+- this.resultSlots.setItem(0, ItemStack.EMPTY);
+- this.cost.set(0);
++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(getBukkitView(), ItemStack.EMPTY); // CraftBukkit
++ this.cost.set(DEFAULT_DENIED_COST); // CraftBukkit - use a variable for set a cost for denied item
+ } else {
+ ItemStack itemstack1 = itemstack.copy();
+ ItemStack itemstack2 = this.inputSlots.getItem(1);
+@@ -146,8 +150,8 @@
+ if (itemstack1.isDamageableItem() && itemstack1.getItem().isValidRepairItem(itemstack, itemstack2)) {
+ k = Math.min(itemstack1.getDamageValue(), itemstack1.getMaxDamage() / 4);
+ if (k <= 0) {
+- this.resultSlots.setItem(0, ItemStack.EMPTY);
+- this.cost.set(0);
++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(getBukkitView(), ItemStack.EMPTY); // CraftBukkit
++ this.cost.set(DEFAULT_DENIED_COST); // CraftBukkit - use a variable for set a cost for denied item
+ return;
+ }
+
+@@ -161,8 +165,8 @@
+ this.repairItemCountCost = i1;
+ } else {
+ if (!flag && (!itemstack1.is(itemstack2.getItem()) || !itemstack1.isDamageableItem())) {
+- this.resultSlots.setItem(0, ItemStack.EMPTY);
+- this.cost.set(0);
++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(getBukkitView(), ItemStack.EMPTY); // CraftBukkit
++ this.cost.set(DEFAULT_DENIED_COST); // CraftBukkit - use a variable for set a cost for denied item
+ return;
+ }
+
+@@ -251,8 +255,8 @@
+ }
+
+ if (flag2 && !flag1) {
+- this.resultSlots.setItem(0, ItemStack.EMPTY);
+- this.cost.set(0);
++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(getBukkitView(), ItemStack.EMPTY); // CraftBukkit
++ this.cost.set(DEFAULT_DENIED_COST); // CraftBukkit - use a variable for set a cost for denied item
+ return;
+ }
+ }
+@@ -275,11 +279,11 @@
+ itemstack1 = ItemStack.EMPTY;
+ }
+
+- if (b1 == i && b1 > 0 && this.cost.get() >= 40) {
+- this.cost.set(39);
++ if (b1 == i && b1 > 0 && this.cost.get() >= maximumRepairCost) { // CraftBukkit
++ this.cost.set(maximumRepairCost - 1); // CraftBukkit
+ }
+
+- if (this.cost.get() >= 40 && !this.player.getAbilities().instabuild) {
++ if (this.cost.get() >= maximumRepairCost && !this.player.getAbilities().instabuild) { // CraftBukkit
+ itemstack1 = ItemStack.EMPTY;
+ }
+
+@@ -298,7 +302,8 @@
+ EnchantmentHelper.setEnchantments(map, itemstack1);
+ }
+
+- this.resultSlots.setItem(0, itemstack1);
++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(getBukkitView(), itemstack1); // CraftBukkit
++ sendAllDataToRemote(); // CraftBukkit - SPIGOT-6686: Always send completed inventory to stay in sync with client
+ this.broadcastChanges();
+ }
+ }
+@@ -339,4 +344,18 @@
+ public int getCost() {
+ return this.cost.get();
+ }
++
++ // CraftBukkit start
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ org.bukkit.craftbukkit.inventory.CraftInventory inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryAnvil(
++ access.getLocation(), this.inputSlots, this.resultSlots, this);
++ bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this);
++ return bukkitEntity;
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/BeaconMenu.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/BeaconMenu.java.patch
new file mode 100644
index 0000000000..8e5ec5d3c2
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/BeaconMenu.java.patch
@@ -0,0 +1,58 @@
+--- a/net/minecraft/world/inventory/BeaconMenu.java
++++ b/net/minecraft/world/inventory/BeaconMenu.java
+@@ -11,6 +12,8 @@
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.block.Blocks;
++import org.bukkit.craftbukkit.inventory.CraftInventoryView;
++// CraftBukkit end
+
+ public class BeaconMenu extends AbstractContainerMenu {
+
+@@ -26,13 +29,18 @@
+ private final BeaconMenu.PaymentSlot paymentSlot;
+ private final ContainerLevelAccess access;
+ private final ContainerData beaconData;
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity = null;
++ private Inventory player;
++ // CraftBukkit end
+
+ public BeaconMenu(int i, Container container) {
+ this(i, container, new SimpleContainerData(3), ContainerLevelAccess.NULL);
+ }
+
+- public BeaconMenu(int i, Container container, ContainerData containerdata, ContainerLevelAccess containerlevelaccess) {
+- super(MenuType.BEACON, i);
++ public BeaconMenu(int containerId, Container container, ContainerData beaconData, ContainerLevelAccess access) {
++ super(MenuType.BEACON, containerId);
++ player = (Inventory) container; // CraftBukkit - TODO: check this
+ this.beacon = new SimpleContainer(1) {
+ @Override
+ @Override
+@@ -86,6 +90,7 @@
+ @Override
+ @Override
+ public boolean stillValid(Player player) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return stillValid(this.access, player, Blocks.BEACON);
+ }
+
+@@ -199,4 +200,17 @@
+ return 1;
+ }
+ }
++
++ // CraftBukkit start
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ org.bukkit.craftbukkit.inventory.CraftInventory inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryBeacon(this.beacon);
++ bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), inventory, this);
++ return bukkitEntity;
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/BrewingStandMenu.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/BrewingStandMenu.java.patch
new file mode 100644
index 0000000000..4e23e36b65
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/BrewingStandMenu.java.patch
@@ -0,0 +1,82 @@
+--- a/net/minecraft/world/inventory/BrewingStandMenu.java
++++ b/net/minecraft/world/inventory/BrewingStandMenu.java
+@@ -12,6 +12,10 @@
+ import net.minecraft.world.item.alchemy.Potion;
+ import net.minecraft.world.item.alchemy.PotionBrewing;
+ import net.minecraft.world.item.alchemy.PotionUtils;
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftInventoryBrewer;
++import org.bukkit.craftbukkit.inventory.CraftInventoryView;
++// CraftBukkit end
+
+ public class BrewingStandMenu extends AbstractContainerMenu {
+
+@@ -29,22 +33,28 @@
+ private final ContainerData brewingStandData;
+ private final Slot ingredientSlot;
+
+- public BrewingStandMenu(int i, Inventory inventory) {
+- this(i, inventory, new SimpleContainer(5), new SimpleContainerData(2));
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity = null;
++ private Inventory player;
++ // CraftBukkit end
++
++ public BrewingStandMenu(int containerId, Inventory playerInventory) {
++ this(containerId, playerInventory, new SimpleContainer(5), new SimpleContainerData(2));
+ }
+
+- public BrewingStandMenu(int i, Inventory inventory, Container container, ContainerData containerdata) {
+- super(MenuType.BREWING_STAND, i);
+- checkContainerSize(container, 5);
+- checkContainerDataCount(containerdata, 2);
+- this.brewingStand = container;
+- this.brewingStandData = containerdata;
+- this.addSlot(new BrewingStandMenu.PotionSlot(container, 0, 56, 51));
+- this.addSlot(new BrewingStandMenu.PotionSlot(container, 1, 79, 58));
+- this.addSlot(new BrewingStandMenu.PotionSlot(container, 2, 102, 51));
+- this.ingredientSlot = this.addSlot(new BrewingStandMenu.IngredientsSlot(container, 3, 79, 17));
+- this.addSlot(new BrewingStandMenu.FuelSlot(container, 4, 17, 17));
+- this.addDataSlots(containerdata);
++ public BrewingStandMenu(int containerId, Inventory playerInventory, Container brewingStandContainer, ContainerData brewingStandData) {
++ super(MenuType.BREWING_STAND, containerId);
++ player = playerInventory; // CraftBukkit
++ checkContainerSize(brewingStandContainer, 5);
++ checkContainerDataCount(brewingStandData, 2);
++ this.brewingStand = brewingStandContainer;
++ this.brewingStandData = brewingStandData;
++ this.addSlot(new BrewingStandMenu.PotionSlot(brewingStandContainer, 0, 56, 51));
++ this.addSlot(new BrewingStandMenu.PotionSlot(brewingStandContainer, 1, 79, 58));
++ this.addSlot(new BrewingStandMenu.PotionSlot(brewingStandContainer, 2, 102, 51));
++ this.ingredientSlot = this.addSlot(new BrewingStandMenu.IngredientsSlot(brewingStandContainer, 3, 79, 17));
++ this.addSlot(new BrewingStandMenu.FuelSlot(brewingStandContainer, 4, 17, 17));
++ this.addDataSlots(brewingStandData);
+
+ int j;
+
+@@ -63,6 +72,7 @@
+ @Override
+ @Override
+ public boolean stillValid(Player player) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return this.brewingStand.stillValid(player);
+ }
+
+@@ -208,4 +210,17 @@
+ return 64;
+ }
+ }
++
++ // CraftBukkit start
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ CraftInventoryBrewer inventory = new CraftInventoryBrewer(this.brewingStand);
++ bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), inventory, this);
++ return bukkitEntity;
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/CartographyTableMenu.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/CartographyTableMenu.java.patch
new file mode 100644
index 0000000000..13a6e37f71
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/CartographyTableMenu.java.patch
@@ -0,0 +1,76 @@
+--- a/net/minecraft/world/inventory/CartographyTableMenu.java
++++ b/net/minecraft/world/inventory/CartographyTableMenu.java
+@@ -11,9 +10,30 @@
+ import net.minecraft.world.item.MapItem;
+ import net.minecraft.world.level.block.Blocks;
+ import net.minecraft.world.level.saveddata.maps.MapItemSavedData;
++// CraftBukkit start
++import org.bukkit.Location;
++import org.bukkit.craftbukkit.inventory.CraftInventoryCartography;
++import org.bukkit.craftbukkit.inventory.CraftInventoryView;
++import org.bukkit.entity.Player;
++// CraftBukkit end
+
+ public class CartographyTableMenu extends AbstractContainerMenu {
+
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity = null;
++ private Player player;
++
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ CraftInventoryCartography inventory = new CraftInventoryCartography(this.container, this.resultContainer);
++ bukkitEntity = new CraftInventoryView(this.player, inventory, this);
++ return bukkitEntity;
++ }
++ // CraftBukkit end
+ public static final int MAP_SLOT = 0;
+ public static final int ADDITIONAL_SLOT = 1;
+ public static final int RESULT_SLOT = 2;
+@@ -39,6 +58,13 @@
+ CartographyTableMenu.this.slotsChanged(this);
+ super.setChanged();
+ }
++
++ // CraftBukkit start
++ @Override
++ public Location getLocation() {
++ return access.getLocation();
++ }
++ // CraftBukkit end
+ };
+ this.resultContainer = new ResultContainer() {
+ @Override
+@@ -47,6 +72,13 @@
+ CartographyTableMenu.this.slotsChanged(this);
+ super.setChanged();
+ }
++
++ // CraftBukkit start
++ @Override
++ public Location getLocation() {
++ return access.getLocation();
++ }
++ // CraftBukkit end
+ };
+ this.access = containerlevelaccess;
+ this.addSlot(new Slot(this.container, 0, 15, 15) {
+@@ -101,11 +129,12 @@
+ this.addSlot(new Slot(inventory, j, 8 + j * 18, 142));
+ }
+
++ player = (Player) playerInventory.player.getBukkitEntity(); // CraftBukkit
+ }
+
+ @Override
+- @Override
+- public boolean stillValid(Player player) {
++ public boolean stillValid(net.minecraft.world.entity.player.Player player) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return stillValid(this.access, player, Blocks.CARTOGRAPHY_TABLE);
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/ChestMenu.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/ChestMenu.java.patch
new file mode 100644
index 0000000000..edc63ba678
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/ChestMenu.java.patch
@@ -0,0 +1,45 @@
+--- a/net/minecraft/world/inventory/ChestMenu.java
++++ b/net/minecraft/world/inventory/ChestMenu.java
+@@ -5,16 +6,23 @@
+ import net.minecraft.world.entity.player.Inventory;
+ import net.minecraft.world.entity.player.Player;
+ import net.minecraft.world.item.ItemStack;
++import org.bukkit.craftbukkit.inventory.CraftInventory;
++import org.bukkit.craftbukkit.inventory.CraftInventoryView;
++// CraftBukkit end
+
+ public class ChestMenu extends AbstractContainerMenu {
+
+ private static final int SLOTS_PER_ROW = 9;
+ private final Container container;
+ private final int containerRows;
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity = null;
++ private Inventory player;
+
+ private ChestMenu(MenuType<?> menutype, int i, Inventory inventory, int j) {
+ this(menutype, i, inventory, new SimpleContainer(9 * j), j);
+ }
++ // CraftBukkit end
+
+ public static ChestMenu oneRow(int i, Inventory inventory) {
+ return new ChestMenu(MenuType.GENERIC_9x1, i, inventory, 1);
+@@ -56,6 +83,10 @@
+ container.startOpen(inventory.player);
+ int k = (this.containerRows - 4) * 18;
+
++ // CraftBukkit start - Save player
++ this.player = playerInventory;
++ // CraftBukkit end
++
+ int l;
+ int i1;
+
+@@ -80,6 +110,7 @@
+ @Override
+ @Override
+ public boolean stillValid(Player player) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return this.container.stillValid(player);
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/ContainerLevelAccess.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/ContainerLevelAccess.java.patch
new file mode 100644
index 0000000000..9842d48860
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/ContainerLevelAccess.java.patch
@@ -0,0 +1,42 @@
+--- a/net/minecraft/world/inventory/ContainerLevelAccess.java
++++ b/net/minecraft/world/inventory/ContainerLevelAccess.java
+@@ -8,6 +8,20 @@
+
+ public interface ContainerLevelAccess {
+
++ // CraftBukkit start
++ default Level getWorld() {
++ throw new UnsupportedOperationException("Not supported yet.");
++ }
++
++ default BlockPos getPosition() {
++ throw new UnsupportedOperationException("Not supported yet.");
++ }
++
++ default org.bukkit.Location getLocation() {
++ return new org.bukkit.Location(getWorld().getWorld(), getPosition().getX(), getPosition().getY(), getPosition().getZ());
++ }
++ // CraftBukkit end
++
+ ContainerLevelAccess NULL = new ContainerLevelAccess() {
+ @Override
+ @Override
+@@ -18,11 +31,18 @@
+
+ static ContainerLevelAccess create(final Level level, final BlockPos blockpos) {
+ return new ContainerLevelAccess() {
++ // CraftBukkit start
+ @Override
+ @Override
+ public <T> Optional<T> evaluate(BiFunction<Level, BlockPos, T> bifunction) {
+ return Optional.of(bifunction.apply(level, blockpos));
+ }
++ // CraftBukkit end
++
++ @Override
++ public <T> Optional<T> evaluate(BiFunction<Level, BlockPos, T> levelPosConsumer) {
++ return Optional.of(levelPosConsumer.apply(level, pos));
++ }
+ };
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/CrafterMenu.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/CrafterMenu.java.patch
new file mode 100644
index 0000000000..84de94f9c8
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/CrafterMenu.java.patch
@@ -0,0 +1,38 @@
+--- a/net/minecraft/world/inventory/CrafterMenu.java
++++ b/net/minecraft/world/inventory/CrafterMenu.java
+@@ -8,8 +8,27 @@
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.block.CrafterBlock;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftInventoryCrafter;
++import org.bukkit.craftbukkit.inventory.CraftInventoryView;
++// CraftBukkit end
++
+ public class CrafterMenu extends AbstractContainerMenu implements ContainerListener {
+
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity = null;
++
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ CraftInventoryCrafter inventory = new CraftInventoryCrafter(this.container, this.resultContainer);
++ bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this);
++ return bukkitEntity;
++ }
++ // CraftBukkit end
+ protected static final int SLOT_COUNT = 9;
+ private static final int INV_SLOT_START = 9;
+ private static final int INV_SLOT_END = 36;
+@@ -118,6 +135,7 @@
+ @Override
+ @Override
+ public boolean stillValid(Player player) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return this.container.stillValid(player);
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/CraftingMenu.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/CraftingMenu.java.patch
new file mode 100644
index 0000000000..becbef811b
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/CraftingMenu.java.patch
@@ -0,0 +1,81 @@
+--- a/net/minecraft/world/inventory/CraftingMenu.java
++++ b/net/minecraft/world/inventory/CraftingMenu.java
+@@ -14,6 +15,9 @@
+ import net.minecraft.world.item.crafting.RecipeType;
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.block.Blocks;
++import org.bukkit.craftbukkit.inventory.CraftInventoryCrafting;
++import org.bukkit.craftbukkit.inventory.CraftInventoryView;
++// CraftBukkit end
+
+ public class CraftingMenu extends RecipeBookMenu<CraftingContainer> {
+
+@@ -24,22 +28,28 @@
+ private static final int INV_SLOT_END = 37;
+ private static final int USE_ROW_SLOT_START = 37;
+ private static final int USE_ROW_SLOT_END = 46;
+- private final CraftingContainer craftSlots;
+- private final ResultContainer resultSlots;
+- private final ContainerLevelAccess access;
++ public final TransientCraftingContainer craftSlots; // CraftBukkit
++ public final ResultContainer resultSlots;
++ public final ContainerLevelAccess access;
+ private final Player player;
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity = null;
++ // CraftBukkit end
+
+ public CraftingMenu(int i, Inventory inventory) {
+ this(i, inventory, ContainerLevelAccess.NULL);
+ }
+
+- public CraftingMenu(int i, Inventory inventory, ContainerLevelAccess containerlevelaccess) {
+- super(MenuType.CRAFTING, i);
+- this.craftSlots = new TransientCraftingContainer(this, 3, 3);
++ public CraftingMenu(int containerId, Inventory playerInventory, ContainerLevelAccess access) {
++ super(MenuType.CRAFTING, containerId);
++ // CraftBukkit start - Switched order of IInventory construction and stored player
+ this.resultSlots = new ResultContainer();
+- this.access = containerlevelaccess;
+- this.player = inventory.player;
+- this.addSlot(new ResultSlot(inventory.player, this.craftSlots, this.resultSlots, 0, 124, 35));
++ this.craftSlots = new TransientCraftingContainer(this, 3, 3, playerInventory.player); // CraftBukkit - pass player
++ this.craftSlots.resultInventory = this.resultSlots;
++ // CraftBukkit end
++ this.access = access;
++ this.player = playerInventory.player;
++ this.addSlot(new ResultSlot(playerInventory.player, this.craftSlots, this.resultSlots, 0, 124, 35));
+
+ int j;
+ int k;
+@@ -80,6 +90,7 @@
+ }
+ }
+ }
++ itemstack = org.bukkit.craftbukkit.event.CraftEventFactory.callPreCraftEvent(container, result, itemstack, menu.getBukkitView(), optional.map(RecipeHolder::toBukkitRecipe).orElse(null) instanceof RepairItemRecipe); // CraftBukkit
+
+ resultcontainer.setItem(0, itemstack);
+ abstractcontainermenu.setRemoteSlot(0, itemstack);
+@@ -126,6 +131,7 @@
+ @Override
+ @Override
+ public boolean stillValid(Player player) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return stillValid(this.access, player, Blocks.CRAFTING_TABLE);
+ }
+
+@@ -218,8 +217,14 @@
+ }
+
+ @Override
++ public boolean shouldMoveToInventory(int slotIndex) {
++ return slotIndex != this.getResultSlotIndex();
++ }
++
++ // CraftBukkit start
+ @Override
+ public boolean shouldMoveToInventory(int i) {
+ return i != this.getResultSlotIndex();
+ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/DispenserMenu.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/DispenserMenu.java.patch
new file mode 100644
index 0000000000..725f337033
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/DispenserMenu.java.patch
@@ -0,0 +1,66 @@
+--- a/net/minecraft/world/inventory/DispenserMenu.java
++++ b/net/minecraft/world/inventory/DispenserMenu.java
+@@ -6,6 +6,11 @@
+ import net.minecraft.world.entity.player.Player;
+ import net.minecraft.world.item.ItemStack;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftInventory;
++import org.bukkit.craftbukkit.inventory.CraftInventoryView;
++// CraftBukkit end
++
+ public class DispenserMenu extends AbstractContainerMenu {
+
+ private static final int SLOT_COUNT = 9;
+@@ -13,14 +18,22 @@
+ private static final int INV_SLOT_END = 36;
+ private static final int USE_ROW_SLOT_START = 36;
+ private static final int USE_ROW_SLOT_END = 45;
+- private final Container dispenser;
++ public final Container dispenser;
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity = null;
++ private Inventory player;
++ // CraftBukkit end
+
+ public DispenserMenu(int i, Inventory inventory) {
+ this(i, inventory, new SimpleContainer(9));
+ }
+
+- public DispenserMenu(int i, Inventory inventory, Container container) {
+- super(MenuType.GENERIC_3x3, i);
++ public DispenserMenu(int containerId, Inventory playerInventory, Container container) {
++ super(MenuType.GENERIC_3x3, containerId);
++ // CraftBukkit start - Save player
++ this.player = playerInventory;
++ // CraftBukkit end
++
+ checkContainerSize(container, 9);
+ this.dispenser = container;
+ container.startOpen(inventory.player);
+@@ -49,6 +61,7 @@
+ @Override
+ @Override
+ public boolean stillValid(Player player) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return this.dispenser.stillValid(player);
+ }
+
+@@ -92,4 +103,17 @@
+ super.removed(player);
+ this.dispenser.stopOpen(player);
+ }
++
++ // CraftBukkit start
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ CraftInventory inventory = new CraftInventory(this.dispenser);
++ bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), inventory, this);
++ return bukkitEntity;
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/EnchantmentMenu.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/EnchantmentMenu.java.patch
new file mode 100644
index 0000000000..8e5dc408ee
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/EnchantmentMenu.java.patch
@@ -0,0 +1,198 @@
+--- a/net/minecraft/world/inventory/EnchantmentMenu.java
++++ b/net/minecraft/world/inventory/EnchantmentMenu.java
+@@ -26,6 +23,22 @@
+ import net.minecraft.world.item.enchantment.EnchantmentInstance;
+ import net.minecraft.world.level.block.Blocks;
+ import net.minecraft.world.level.block.EnchantmentTableBlock;
++// CraftBukkit start
++import java.util.Map;
++import net.minecraft.world.item.enchantment.Enchantment;
++import net.minecraft.world.item.enchantment.EnchantmentHelper;
++import net.minecraft.world.item.enchantment.EnchantmentInstance;
++import org.bukkit.Location;
++import org.bukkit.NamespacedKey;
++import org.bukkit.craftbukkit.inventory.CraftInventoryEnchanting;
++import org.bukkit.craftbukkit.inventory.CraftInventoryView;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.craftbukkit.util.CraftNamespacedKey;
++import org.bukkit.enchantments.EnchantmentOffer;
++import org.bukkit.event.enchantment.EnchantItemEvent;
++import org.bukkit.event.enchantment.PrepareItemEnchantEvent;
++import org.bukkit.entity.Player;
++// CraftBukkit end
+
+ public class EnchantmentMenu extends AbstractContainerMenu {
+
+@@ -37,6 +50,10 @@
+ public final int[] costs;
+ public final int[] enchantClue;
+ public final int[] levelClue;
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity = null;
++ private Player player;
++ // CraftBukkit end
+
+ public EnchantmentMenu(int i, Inventory inventory) {
+ this(i, inventory, ContainerLevelAccess.NULL);
+@@ -51,6 +67,13 @@
+ super.setChanged();
+ EnchantmentMenu.this.slotsChanged(this);
+ }
++
++ // CraftBukkit start
++ @Override
++ public Location getLocation() {
++ return access.getLocation();
++ }
++ // CraftBukkit end
+ };
+ this.random = RandomSource.create();
+ this.enchantmentSeed = DataSlot.standalone();
+@@ -101,6 +121,9 @@
+ this.addDataSlot(DataSlot.shared(this.levelClue, 0));
+ this.addDataSlot(DataSlot.shared(this.levelClue, 1));
+ this.addDataSlot(DataSlot.shared(this.levelClue, 2));
++ // CraftBukkit start
++ player = (Player) playerInventory.player.getBukkitEntity();
++ // CraftBukkit end
+ }
+
+ @Override
+@@ -109,8 +131,8 @@
+ if (container == this.enchantSlots) {
+ ItemStack itemstack = container.getItem(0);
+
+- if (!itemstack.isEmpty() && itemstack.isEnchantable()) {
+- this.access.execute((level, blockpos) -> {
++ if (!itemstack.isEmpty()) { // CraftBukkit - relax condition
++ this.access.execute((world, blockposition) -> {
+ int i = 0;
+ Iterator iterator = EnchantmentTableBlock.BOOKSHELF_OFFSETS.iterator();
+
+@@ -148,6 +170,41 @@
+ }
+ }
+
++ // CraftBukkit start
++ CraftItemStack item = CraftItemStack.asCraftMirror(itemstack);
++ org.bukkit.enchantments.EnchantmentOffer[] offers = new EnchantmentOffer[3];
++ for (j = 0; j < 3; ++j) {
++ org.bukkit.enchantments.Enchantment enchantment = (this.enchantClue[j] >= 0) ? org.bukkit.enchantments.Enchantment.getByKey(CraftNamespacedKey.fromMinecraft(BuiltInRegistries.ENCHANTMENT.getKey(BuiltInRegistries.ENCHANTMENT.byId(this.enchantClue[j])))) : null;
++ offers[j] = (enchantment != null) ? new EnchantmentOffer(enchantment, this.levelClue[j], this.costs[j]) : null;
++ }
++
++ PrepareItemEnchantEvent event = new PrepareItemEnchantEvent(player, this.getBukkitView(), access.getLocation().getBlock(), item, offers, i);
++ event.setCancelled(!itemstack.isEnchantable());
++ world.getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ for (j = 0; j < 3; ++j) {
++ this.costs[j] = 0;
++ this.enchantClue[j] = -1;
++ this.levelClue[j] = -1;
++ }
++ return;
++ }
++
++ for (j = 0; j < 3; j++) {
++ EnchantmentOffer offer = event.getOffers()[j];
++ if (offer != null) {
++ this.costs[j] = offer.getCost();
++ this.enchantClue[j] = BuiltInRegistries.ENCHANTMENT.getId(BuiltInRegistries.ENCHANTMENT.get(CraftNamespacedKey.toMinecraft(offer.getEnchantment().getKey())));
++ this.levelClue[j] = offer.getEnchantmentLevel();
++ } else {
++ this.costs[j] = 0;
++ this.enchantClue[j] = -1;
++ this.levelClue[j] = -1;
++ }
++ }
++ // CraftBukkit end
++
+ this.broadcastChanges();
+ });
+ } else {
+@@ -176,8 +232,25 @@
+ ItemStack itemstack2 = itemstack;
+ List<EnchantmentInstance> list = this.getEnchantmentList(itemstack, i, this.costs[i]);
+
+- if (!list.isEmpty()) {
+- player.onEnchantmentPerformed(itemstack, j);
++ // CraftBukkit start
++ if (true || !list.isEmpty()) {
++ // entityhuman.onEnchantmentPerformed(itemstack, j); // Moved down
++ Map<org.bukkit.enchantments.Enchantment, Integer> enchants = new java.util.HashMap<org.bukkit.enchantments.Enchantment, Integer>();
++ for (EnchantmentInstance instance : list) {
++ enchants.put(org.bukkit.enchantments.Enchantment.getByKey(CraftNamespacedKey.fromMinecraft(BuiltInRegistries.ENCHANTMENT.getKey(instance.enchantment))), instance.level);
++ }
++ CraftItemStack item = CraftItemStack.asCraftMirror(itemstack2);
++
++ org.bukkit.enchantments.Enchantment hintedEnchantment = org.bukkit.enchantments.Enchantment.getByKey(CraftNamespacedKey.fromMinecraft(BuiltInRegistries.ENCHANTMENT.getKey(Enchantment.byId(enchantClue[id]))));
++ int hintedEnchantmentLevel = levelClue[id];
++ EnchantItemEvent event = new EnchantItemEvent((Player) player.getBukkitEntity(), this.getBukkitView(), access.getLocation().getBlock(), item, this.costs[id], enchants, hintedEnchantment, hintedEnchantmentLevel, id);
++ world.getCraftServer().getPluginManager().callEvent(event);
++
++ int level = event.getExpLevelCost();
++ if (event.isCancelled() || (level > player.experienceLevel && !player.getAbilities().instabuild) || event.getEnchantsToAdd().isEmpty()) {
++ return;
++ }
++ // CraftBukkit end
+ boolean flag = itemstack.is(Items.BOOK);
+
+ if (flag) {
+@@ -191,7 +264,15 @@
+ this.enchantSlots.setItem(0, itemstack2);
+ }
+
+- Iterator iterator = list.iterator();
++ // CraftBukkit start
++ for (Map.Entry<org.bukkit.enchantments.Enchantment, Integer> entry : event.getEnchantsToAdd().entrySet()) {
++ try {
++ if (flag) {
++ NamespacedKey enchantId = entry.getKey().getKey();
++ Enchantment nms = BuiltInRegistries.ENCHANTMENT.get(CraftNamespacedKey.toMinecraft(enchantId));
++ if (nms == null) {
++ continue;
++ }
+
+ while (iterator.hasNext()) {
+ EnchantmentInstance enchantmentinstance = (EnchantmentInstance) iterator.next();
+@@ -203,6 +284,10 @@
+ }
+ }
+
++ player.onEnchantmentPerformed(itemstack, j);
++ // CraftBukkit end
++
++ // CraftBukkit - TODO: let plugins change this
+ if (!player.getAbilities().instabuild) {
+ itemstack1.shrink(j);
+ if (itemstack1.isEmpty()) {
+@@ -265,8 +349,8 @@
+ }
+
+ @Override
+- @Override
+- public boolean stillValid(Player player) {
++ public boolean stillValid(net.minecraft.world.entity.player.Player player) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return stillValid(this.access, player, Blocks.ENCHANTING_TABLE);
+ }
+
+@@ -318,4 +401,17 @@
+
+ return itemstack;
+ }
++
++ // CraftBukkit start
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ CraftInventoryEnchanting inventory = new CraftInventoryEnchanting(this.enchantSlots);
++ bukkitEntity = new CraftInventoryView(this.player, inventory, this);
++ return bukkitEntity;
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/FurnaceResultSlot.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/FurnaceResultSlot.java.patch
new file mode 100644
index 0000000000..92ab3e6165
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/FurnaceResultSlot.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/inventory/FurnaceResultSlot.java
++++ b/net/minecraft/world/inventory/FurnaceResultSlot.java
+@@ -59,7 +54,7 @@
+ if (container instanceof AbstractFurnaceBlockEntity) {
+ AbstractFurnaceBlockEntity abstractfurnaceblockentity = (AbstractFurnaceBlockEntity) container;
+
+- abstractfurnaceblockentity.awardUsedRecipesAndPopExperience(serverplayer);
++ tileentityfurnace.awardUsedRecipesAndPopExperience(entityplayer, stack, this.removeCount); // CraftBukkit
+ }
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/GrindstoneMenu.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/GrindstoneMenu.java.patch
new file mode 100644
index 0000000000..9729875c6d
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/GrindstoneMenu.java.patch
@@ -0,0 +1,109 @@
+--- a/net/minecraft/world/inventory/GrindstoneMenu.java
++++ b/net/minecraft/world/inventory/GrindstoneMenu.java
+@@ -18,9 +17,30 @@
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.block.Blocks;
+ import net.minecraft.world.phys.Vec3;
++// CraftBukkit start
++import org.bukkit.Location;
++import org.bukkit.craftbukkit.inventory.CraftInventoryGrindstone;
++import org.bukkit.craftbukkit.inventory.CraftInventoryView;
++import org.bukkit.entity.Player;
++// CraftBukkit end
+
+ public class GrindstoneMenu extends AbstractContainerMenu {
+
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity = null;
++ private Player player;
++
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ CraftInventoryGrindstone inventory = new CraftInventoryGrindstone(this.repairSlots, this.resultSlots);
++ bukkitEntity = new CraftInventoryView(this.player, inventory, this);
++ return bukkitEntity;
++ }
++ // CraftBukkit end
+ public static final int MAX_NAME_LENGTH = 35;
+ public static final int INPUT_SLOT = 0;
+ public static final int ADDITIONAL_SLOT = 1;
+@@ -47,6 +66,13 @@
+ super.setChanged();
+ GrindstoneMenu.this.slotsChanged(this);
+ }
++
++ // CraftBukkit start
++ @Override
++ public Location getLocation() {
++ return access.getLocation();
++ }
++ // CraftBukkit end
+ };
+ this.access = containerlevelaccess;
+ this.addSlot(new Slot(this.repairSlots, 0, 49, 19) {
+@@ -129,6 +151,7 @@
+ this.addSlot(new Slot(inventory, j, 8 + j * 18, 142));
+ }
+
++ player = (Player) playerInventory.player.getBukkitEntity(); // CraftBukkit
+ }
+
+ @Override
+@@ -151,7 +173,7 @@
+ boolean flag2 = !itemstack.isEmpty() && !itemstack.is(Items.ENCHANTED_BOOK) && !itemstack.isEnchanted() || !itemstack1.isEmpty() && !itemstack1.is(Items.ENCHANTED_BOOK) && !itemstack1.isEnchanted();
+
+ if (itemstack.getCount() > 1 || itemstack1.getCount() > 1 || !flag1 && flag2) {
+- this.resultSlots.setItem(0, ItemStack.EMPTY);
++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareGrindstoneEvent(getBukkitView(), ItemStack.EMPTY); // CraftBukkit
+ this.broadcastChanges();
+ return;
+ }
+@@ -162,7 +184,7 @@
+
+ if (flag1) {
+ if (!itemstack.is(itemstack1.getItem())) {
+- this.resultSlots.setItem(0, ItemStack.EMPTY);
++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareGrindstoneEvent(getBukkitView(), ItemStack.EMPTY); // CraftBukkit
+ this.broadcastChanges();
+ return;
+ }
+@@ -176,7 +198,7 @@
+ itemstack2 = this.mergeEnchants(itemstack, itemstack1);
+ if (!itemstack2.isDamageableItem()) {
+ if (!ItemStack.matches(itemstack, itemstack1)) {
+- this.resultSlots.setItem(0, ItemStack.EMPTY);
++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareGrindstoneEvent(getBukkitView(), ItemStack.EMPTY); // CraftBukkit
+ this.broadcastChanges();
+ return;
+ }
+@@ -189,12 +211,12 @@
+ i = flag3 ? itemstack.getDamageValue() : itemstack1.getDamageValue();
+ itemstack2 = flag3 ? itemstack : itemstack1;
+ }
+-
+- this.resultSlots.setItem(0, this.removeNonCurses(itemstack2, i, b0));
++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareGrindstoneEvent(getBukkitView(), this.removeNonCurses(itemstack2, i, b0)); // CraftBukkit
+ } else {
+- this.resultSlots.setItem(0, ItemStack.EMPTY);
++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareGrindstoneEvent(getBukkitView(), ItemStack.EMPTY); // CraftBukkit
+ }
+
++ sendAllDataToRemote(); // CraftBukkit - SPIGOT-6686: Always send completed inventory to stay in sync with client
+ this.broadcastChanges();
+ }
+
+@@ -256,8 +277,8 @@
+ }
+
+ @Override
+- @Override
+- public boolean stillValid(Player player) {
++ public boolean stillValid(net.minecraft.world.entity.player.Player player) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return stillValid(this.access, player, Blocks.GRINDSTONE);
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/HopperMenu.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/HopperMenu.java.patch
new file mode 100644
index 0000000000..dd741c4d56
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/HopperMenu.java.patch
@@ -0,0 +1,49 @@
+--- a/net/minecraft/world/inventory/HopperMenu.java
++++ b/net/minecraft/world/inventory/HopperMenu.java
+@@ -6,18 +6,36 @@
+ import net.minecraft.world.entity.player.Player;
+ import net.minecraft.world.item.ItemStack;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftInventory;
++import org.bukkit.craftbukkit.inventory.CraftInventoryView;
++// CraftBukkit end
++
+ public class HopperMenu extends AbstractContainerMenu {
+
+ public static final int CONTAINER_SIZE = 5;
+ private final Container hopper;
+
+- public HopperMenu(int i, Inventory inventory) {
+- this(i, inventory, new SimpleContainer(5));
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity = null;
++ private Inventory player;
++
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ CraftInventory inventory = new CraftInventory(this.hopper);
++ bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), inventory, this);
++ return bukkitEntity;
+ }
++ // CraftBukkit end
+
+ public HopperMenu(int i, Inventory inventory, Container container) {
+ super(MenuType.HOPPER, i);
+ this.hopper = container;
++ this.player = playerInventory; // CraftBukkit - save player
+ checkContainerSize(container, 5);
+ container.startOpen(inventory.player);
+ boolean flag = true;
+@@ -43,6 +64,7 @@
+ @Override
+ @Override
+ public boolean stillValid(Player player) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return this.hopper.stillValid(player);
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/HorseInventoryMenu.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/HorseInventoryMenu.java.patch
new file mode 100644
index 0000000000..a8cda8a126
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/HorseInventoryMenu.java.patch
@@ -0,0 +1,38 @@
+--- a/net/minecraft/world/inventory/HorseInventoryMenu.java
++++ b/net/minecraft/world/inventory/HorseInventoryMenu.java
+@@ -8,13 +8,33 @@
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.item.Items;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftInventoryView;
++import org.bukkit.inventory.InventoryView;
++// CraftBukkit end
++
+ public class HorseInventoryMenu extends AbstractContainerMenu {
+
+ private final Container horseContainer;
+ private final AbstractHorse horse;
+
+- public HorseInventoryMenu(int i, Inventory inventory, Container container, final AbstractHorse abstracthorse) {
+- super((MenuType) null, i);
++ // CraftBukkit start
++ org.bukkit.craftbukkit.inventory.CraftInventoryView bukkitEntity;
++ Inventory player;
++
++ @Override
++ public InventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ return bukkitEntity = new CraftInventoryView(player.player.getBukkitEntity(), horseContainer.getOwner().getInventory(), this);
++ }
++
++ public HorseInventoryMenu(int containerId, Inventory playerInventory, Container container, final AbstractHorse horse) {
++ super((MenuType) null, containerId);
++ player = playerInventory;
++ // CraftBukkit end
+ this.horseContainer = container;
+ this.horse = abstracthorse;
+ boolean flag = true;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/InventoryMenu.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/InventoryMenu.java.patch
new file mode 100644
index 0000000000..519b631f4c
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/InventoryMenu.java.patch
@@ -0,0 +1,62 @@
+--- a/net/minecraft/world/inventory/InventoryMenu.java
++++ b/net/minecraft/world/inventory/InventoryMenu.java
+@@ -12,6 +13,9 @@
+ import net.minecraft.world.item.crafting.Recipe;
+ import net.minecraft.world.item.crafting.RecipeHolder;
+ import net.minecraft.world.item.enchantment.EnchantmentHelper;
++import org.bukkit.craftbukkit.inventory.CraftInventoryCrafting;
++import org.bukkit.craftbukkit.inventory.CraftInventoryView;
++// CraftBukkit end
+
+ public class InventoryMenu extends RecipeBookMenu<CraftingContainer> {
+
+@@ -34,16 +38,29 @@
+ public static final ResourceLocation EMPTY_ARMOR_SLOT_SHIELD = new ResourceLocation("item/empty_armor_slot_shield");
+ static final ResourceLocation[] TEXTURE_EMPTY_SLOTS = new ResourceLocation[]{InventoryMenu.EMPTY_ARMOR_SLOT_BOOTS, InventoryMenu.EMPTY_ARMOR_SLOT_LEGGINGS, InventoryMenu.EMPTY_ARMOR_SLOT_CHESTPLATE, InventoryMenu.EMPTY_ARMOR_SLOT_HELMET};
+ private static final EquipmentSlot[] SLOT_IDS = new EquipmentSlot[]{EquipmentSlot.HEAD, EquipmentSlot.CHEST, EquipmentSlot.LEGS, EquipmentSlot.FEET};
+- private final CraftingContainer craftSlots = new TransientCraftingContainer(this, 2, 2);
+- private final ResultContainer resultSlots = new ResultContainer();
++ // CraftBukkit start
++ private final TransientCraftingContainer craftSlots;
++ private final ResultContainer resultSlots;
++ // CraftBukkit end
+ public final boolean active;
+ private final Player owner;
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity = null;
++ private Inventory player;
++ // CraftBukkit end
+
+ public InventoryMenu(Inventory inventory, boolean flag, final Player player) {
+ super((MenuType) null, 0);
+- this.active = flag;
+- this.owner = player;
+- this.addSlot(new ResultSlot(inventory.player, this.craftSlots, this.resultSlots, 0, 154, 28));
++ this.active = active;
++ this.owner = owner;
++ // CraftBukkit start
++ this.resultSlots = new ResultContainer(); // CraftBukkit - moved to before InventoryCrafting construction
++ this.craftSlots = new TransientCraftingContainer(this, 2, 2, playerInventory.player); // CraftBukkit - pass player
++ this.craftSlots.resultInventory = this.resultSlots; // CraftBukkit - let InventoryCrafting know about its result slot
++ this.player = playerInventory; // CraftBukkit - save player
++ setTitle(Component.translatable("container.crafting")); // SPIGOT-4722: Allocate title for player inventory
++ // CraftBukkit end
++ this.addSlot(new ResultSlot(playerInventory.player, this.craftSlots, this.resultSlots, 0, 154, 28));
+
+ int i;
+ int j;
+@@ -276,8 +273,14 @@
+ }
+
+ @Override
++ public boolean shouldMoveToInventory(int slotIndex) {
++ return slotIndex != this.getResultSlotIndex();
++ }
++
++ // CraftBukkit start
+ @Override
+ public boolean shouldMoveToInventory(int i) {
+ return i != this.getResultSlotIndex();
+ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/ItemCombinerMenu.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/ItemCombinerMenu.java.patch
new file mode 100644
index 0000000000..80316c4c94
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/ItemCombinerMenu.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/world/inventory/ItemCombinerMenu.java
++++ b/net/minecraft/world/inventory/ItemCombinerMenu.java
+@@ -132,8 +124,9 @@
+ @Override
+ @Override
+ public boolean stillValid(Player player) {
+- return (Boolean) this.access.evaluate((level, blockpos) -> {
+- return !this.isValidBlock(level.getBlockState(blockpos)) ? false : player.distanceToSqr((double) blockpos.getX() + 0.5D, (double) blockpos.getY() + 0.5D, (double) blockpos.getZ() + 0.5D) <= 64.0D;
++ if (!this.checkReachable) return true; // CraftBukkit
++ return (Boolean) this.access.evaluate((world, blockposition) -> {
++ return !this.isValidBlock(world.getBlockState(blockposition)) ? false : player.distanceToSqr((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D) <= 64.0D;
+ }, true);
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/LecternMenu.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/LecternMenu.java.patch
new file mode 100644
index 0000000000..fbce988295
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/LecternMenu.java.patch
@@ -0,0 +1,87 @@
+--- a/net/minecraft/world/inventory/LecternMenu.java
++++ b/net/minecraft/world/inventory/LecternMenu.java
+@@ -4,9 +4,31 @@
+ import net.minecraft.world.SimpleContainer;
+ import net.minecraft.world.entity.player.Player;
+ import net.minecraft.world.item.ItemStack;
++import net.minecraft.world.level.block.entity.LecternBlockEntity.LecternInventory;
++import org.bukkit.Bukkit;
++import org.bukkit.craftbukkit.inventory.CraftInventoryLectern;
++import org.bukkit.craftbukkit.inventory.CraftInventoryView;
++import org.bukkit.entity.Player;
++import org.bukkit.event.player.PlayerTakeLecternBookEvent;
++// CraftBukkit end
+
+ public class LecternMenu extends AbstractContainerMenu {
+
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity = null;
++ private Player player;
++
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ CraftInventoryLectern inventory = new CraftInventoryLectern(this.lectern);
++ bukkitEntity = new CraftInventoryView(this.player, inventory, this);
++ return bukkitEntity;
++ }
++ // CraftBukkit end
+ private static final int DATA_COUNT = 1;
+ private static final int SLOT_COUNT = 1;
+ public static final int BUTTON_PREV_PAGE = 1;
+@@ -16,11 +38,13 @@
+ private final Container lectern;
+ private final ContainerData lecternData;
+
+- public LecternMenu(int i) {
+- this(i, new SimpleContainer(1), new SimpleContainerData(1));
++ // CraftBukkit start - add player
++ public LecternMenu(int i, Inventory playerinventory) {
++ this(i, new SimpleContainer(1), new SimpleContainerData(1), playerinventory);
+ }
+
+- public LecternMenu(int i, Container container, ContainerData containerdata) {
++ public LecternMenu(int i, Container iinventory, ContainerData icontainerproperties, Inventory playerinventory) {
++ // CraftBukkit end
+ super(MenuType.LECTERN, i);
+ checkContainerSize(container, 1);
+ checkContainerDataCount(containerdata, 1);
+@@ -34,7 +57,8 @@
+ LecternMenu.this.slotsChanged(this.container);
+ }
+ });
+- this.addDataSlots(containerdata);
++ this.addDataSlots(icontainerproperties);
++ player = (Player) playerinventory.player.getBukkitEntity(); // CraftBukkit
+ }
+
+ @Override
+@@ -61,6 +84,13 @@
+ return false;
+ }
+
++ // CraftBukkit start - Event for taking the book
++ PlayerTakeLecternBookEvent event = new PlayerTakeLecternBookEvent(player, ((CraftInventoryLectern) getBukkitView().getTopInventory()).getHolder());
++ Bukkit.getServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ return false;
++ }
++ // CraftBukkit end
+ ItemStack itemstack = this.lectern.removeItemNoUpdate(0);
+
+ this.lectern.setChanged();
+@@ -89,8 +117,9 @@
+ }
+
+ @Override
+- @Override
+- public boolean stillValid(Player player) {
++ public boolean stillValid(net.minecraft.world.entity.player.Player player) {
++ if (lectern instanceof LecternInventory && !((LecternInventory) lectern).getLectern().hasBook()) return false; // CraftBukkit
++ if (!this.checkReachable) return true; // CraftBukkit
+ return this.lectern.stillValid(player);
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/LoomMenu.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/LoomMenu.java.patch
new file mode 100644
index 0000000000..d748ed1303
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/LoomMenu.java.patch
@@ -0,0 +1,92 @@
+--- a/net/minecraft/world/inventory/LoomMenu.java
++++ b/net/minecraft/world/inventory/LoomMenu.java
+@@ -23,9 +22,30 @@
+ import net.minecraft.world.level.block.Blocks;
+ import net.minecraft.world.level.block.entity.BannerPattern;
+ import net.minecraft.world.level.block.entity.BlockEntityType;
++// CraftBukkit start
++import org.bukkit.Location;
++import org.bukkit.craftbukkit.inventory.CraftInventoryLoom;
++import org.bukkit.craftbukkit.inventory.CraftInventoryView;
++import org.bukkit.entity.Player;
++// CraftBukkit end
+
+ public class LoomMenu extends AbstractContainerMenu {
+
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity = null;
++ private Player player;
++
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ CraftInventoryLoom inventory = new CraftInventoryLoom(this.inputContainer, this.outputContainer);
++ bukkitEntity = new CraftInventoryView(this.player, inventory, this);
++ return bukkitEntity;
++ }
++ // CraftBukkit end
+ private static final int PATTERN_NOT_SET = -1;
+ private static final int INV_SLOT_START = 4;
+ private static final int INV_SLOT_END = 31;
+@@ -61,6 +80,13 @@
+ LoomMenu.this.slotsChanged(this);
+ LoomMenu.this.slotUpdateListener.run();
+ }
++
++ // CraftBukkit start
++ @Override
++ public Location getLocation() {
++ return access.getLocation();
++ }
++ // CraftBukkit end
+ };
+ this.outputContainer = new SimpleContainer(1) {
+ @Override
+@@ -69,6 +94,13 @@
+ super.setChanged();
+ LoomMenu.this.slotUpdateListener.run();
+ }
++
++ // CraftBukkit start
++ @Override
++ public Location getLocation() {
++ return access.getLocation();
++ }
++ // CraftBukkit end
+ };
+ this.access = containerlevelaccess;
+ this.bannerSlot = this.addSlot(new Slot(this.inputContainer, 0, 13, 26) {
+@@ -134,11 +161,12 @@
+ }
+
+ this.addDataSlot(this.selectedBannerPatternIndex);
++ player = (Player) playerInventory.player.getBukkitEntity(); // CraftBukkit
+ }
+
+ @Override
+- @Override
+- public boolean stillValid(Player player) {
++ public boolean stillValid(net.minecraft.world.entity.player.Player player) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return stillValid(this.access, player, Blocks.LOOM);
+ }
+
+@@ -318,8 +342,13 @@
+ CompoundTag compoundtag = BlockItem.getBlockEntityData(itemstack2);
+ ListTag listtag;
+
+- if (compoundtag != null && compoundtag.contains("Patterns", 9)) {
+- listtag = compoundtag.getList("Patterns", 10);
++ if (nbttagcompound != null && nbttagcompound.contains("Patterns", 9)) {
++ nbttaglist = nbttagcompound.getList("Patterns", 10);
++ // CraftBukkit start
++ while (nbttaglist.size() > 20) {
++ nbttaglist.remove(20);
++ }
++ // CraftBukkit end
+ } else {
+ listtag = new ListTag();
+ if (compoundtag == null) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/MenuType.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/MenuType.java.patch
new file mode 100644
index 0000000000..70d67be914
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/MenuType.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/world/inventory/MenuType.java
++++ b/net/minecraft/world/inventory/MenuType.java
+@@ -27,8 +27,8 @@
+ public static final MenuType<FurnaceMenu> FURNACE = register("furnace", FurnaceMenu::new);
+ public static final MenuType<GrindstoneMenu> GRINDSTONE = register("grindstone", GrindstoneMenu::new);
+ public static final MenuType<HopperMenu> HOPPER = register("hopper", HopperMenu::new);
+- public static final MenuType<LecternMenu> LECTERN = register("lectern", (i, inventory) -> {
+- return new LecternMenu(i);
++ public static final MenuType<LecternMenu> LECTERN = register("lectern", (i, playerinventory) -> {
++ return new LecternMenu(i, playerinventory); // CraftBukkit
+ });
+ public static final MenuType<LoomMenu> LOOM = register("loom", LoomMenu::new);
+ public static final MenuType<MerchantMenu> MERCHANT = register("merchant", MerchantMenu::new);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/MerchantContainer.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/MerchantContainer.java.patch
new file mode 100644
index 0000000000..b7fc503096
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/MerchantContainer.java.patch
@@ -0,0 +1,63 @@
+--- a/net/minecraft/world/inventory/MerchantContainer.java
++++ b/net/minecraft/world/inventory/MerchantContainer.java
+@@ -10,6 +12,13 @@
+ import net.minecraft.world.item.trading.Merchant;
+ import net.minecraft.world.item.trading.MerchantOffer;
+ import net.minecraft.world.item.trading.MerchantOffers;
++// CraftBukkit start
++import java.util.List;
++import org.bukkit.Location;
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.craftbukkit.entity.CraftAbstractVillager;
++import org.bukkit.entity.HumanEntity;
++// CraftBukkit end
+
+ public class MerchantContainer implements Container {
+
+@@ -20,6 +29,46 @@
+ private int selectionHint;
+ private int futureXp;
+
++ // CraftBukkit start - add fields and methods
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++ private int maxStack = MAX_STACK;
++
++ public List<ItemStack> getContents() {
++ return this.itemStacks;
++ }
++
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ merchant.setTradingPlayer((Player) null); // SPIGOT-4860
++ }
++
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ @Override
++ public int getMaxStackSize() {
++ return maxStack;
++ }
++
++ public void setMaxStackSize(int i) {
++ maxStack = i;
++ }
++
++ public org.bukkit.inventory.InventoryHolder getOwner() {
++ return (merchant instanceof AbstractVillager) ? (CraftAbstractVillager) ((AbstractVillager) this.merchant).getBukkitEntity() : null;
++ }
++
++ @Override
++ public Location getLocation() {
++ return (merchant instanceof Villager) ? ((Villager) this.merchant).getBukkitEntity().getLocation() : null;
++ }
++ // CraftBukkit end
++
+ public MerchantContainer(Merchant merchant) {
+ this.itemStacks = NonNullList.withSize(3, ItemStack.EMPTY);
+ this.merchant = merchant;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/MerchantMenu.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/MerchantMenu.java.patch
new file mode 100644
index 0000000000..25c8a20f81
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/MerchantMenu.java.patch
@@ -0,0 +1,50 @@
+--- a/net/minecraft/world/inventory/MerchantMenu.java
++++ b/net/minecraft/world/inventory/MerchantMenu.java
+@@ -11,6 +11,7 @@
+ import net.minecraft.world.item.trading.Merchant;
+ import net.minecraft.world.item.trading.MerchantOffer;
+ import net.minecraft.world.item.trading.MerchantOffers;
++import org.bukkit.craftbukkit.inventory.CraftInventoryView; // CraftBukkit
+
+ public class MerchantMenu extends AbstractContainerMenu {
+
+@@ -31,9 +32,18 @@
+ private boolean showProgressBar;
+ private boolean canRestock;
+
+- public MerchantMenu(int i, Inventory inventory) {
+- this(i, inventory, new ClientSideMerchant(inventory.player));
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity = null;
++ private Inventory player;
++
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity == null) {
++ bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), new org.bukkit.craftbukkit.inventory.CraftInventoryMerchant(trader, tradeContainer), this);
++ }
++ return bukkitEntity;
+ }
++ // CraftBukkit end
+
+ public MerchantMenu(int i, Inventory inventory, Merchant merchant) {
+ super(MenuType.MERCHANT, i);
+@@ -41,7 +55,8 @@
+ this.tradeContainer = new MerchantContainer(merchant);
+ this.addSlot(new Slot(this.tradeContainer, 0, 136, 37));
+ this.addSlot(new Slot(this.tradeContainer, 1, 162, 37));
+- this.addSlot(new MerchantResultSlot(inventory.player, merchant, this.tradeContainer, 2, 220, 37));
++ this.addSlot(new MerchantResultSlot(playerInventory.player, trader, this.tradeContainer, 2, 220, 37));
++ this.player = playerInventory; // CraftBukkit - save player
+
+ int j;
+
+@@ -158,7 +169,7 @@
+ }
+
+ private void playTradeSound() {
+- if (!this.trader.isClientSide()) {
++ if (!this.trader.isClientSide() && this.trader instanceof Entity) { // CraftBukkit - SPIGOT-5035
+ Entity entity = (Entity) this.trader;
+
+ entity.level().playLocalSound(entity.getX(), entity.getY(), entity.getZ(), this.trader.getNotifyTradeSound(), SoundSource.NEUTRAL, 1.0F, 1.0F, false);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/PlayerEnderChestContainer.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/PlayerEnderChestContainer.java.patch
new file mode 100644
index 0000000000..5705f524dd
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/PlayerEnderChestContainer.java.patch
@@ -0,0 +1,26 @@
+--- a/net/minecraft/world/inventory/PlayerEnderChestContainer.java
++++ b/net/minecraft/world/inventory/PlayerEnderChestContainer.java
+@@ -7,14 +7,23 @@
+ import net.minecraft.world.entity.player.Player;
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.level.block.entity.EnderChestBlockEntity;
++// CraftBukkit start
++import org.bukkit.Location;
++import org.bukkit.craftbukkit.util.CraftLocation;
++import org.bukkit.inventory.InventoryHolder;
++// CraftBukkit end
+
+ public class PlayerEnderChestContainer extends SimpleContainer {
+
+ @Nullable
+ private EnderChestBlockEntity activeChest;
++ // CraftBukkit start
++ private final Player owner;
+
+ public PlayerEnderChestContainer() {
+ super(27);
++ this.owner = owner;
++ // CraftBukkit end
+ }
+
+ public void setActiveChest(EnderChestBlockEntity enderchestblockentity) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/ResultContainer.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/ResultContainer.java.patch
new file mode 100644
index 0000000000..76f0416155
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/ResultContainer.java.patch
@@ -0,0 +1,54 @@
+--- a/net/minecraft/world/inventory/ResultContainer.java
++++ b/net/minecraft/world/inventory/ResultContainer.java
+@@ -9,12 +9,51 @@
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.item.crafting.RecipeHolder;
+
++// CraftBukkit start
++import org.bukkit.Location;
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++// CraftBukkit end
++
+ public class ResultContainer implements Container, RecipeCraftingHolder {
+
+ private final NonNullList<ItemStack> itemStacks;
+ @Nullable
+ private RecipeHolder<?> recipeUsed;
+
++ // CraftBukkit start
++ private int maxStack = MAX_STACK;
++
++ public java.util.List<ItemStack> getContents() {
++ return this.itemStacks;
++ }
++
++ public org.bukkit.inventory.InventoryHolder getOwner() {
++ return null; // Result slots don't get an owner
++ }
++
++ // Don't need a transaction; the InventoryCrafting keeps track of it for us
++ public void onOpen(CraftHumanEntity who) {}
++ public void onClose(CraftHumanEntity who) {}
++ public java.util.List<HumanEntity> getViewers() {
++ return new java.util.ArrayList<HumanEntity>();
++ }
++
++ @Override
++ public int getMaxStackSize() {
++ return maxStack;
++ }
++
++ public void setMaxStackSize(int size) {
++ maxStack = size;
++ }
++
++ @Override
++ public Location getLocation() {
++ return null;
++ }
++ // CraftBukkit end
++
+ public ResultContainer() {
+ this.itemStacks = NonNullList.withSize(1, ItemStack.EMPTY);
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/ShulkerBoxMenu.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/ShulkerBoxMenu.java.patch
new file mode 100644
index 0000000000..6f85526956
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/ShulkerBoxMenu.java.patch
@@ -0,0 +1,42 @@
+--- a/net/minecraft/world/inventory/ShulkerBoxMenu.java
++++ b/net/minecraft/world/inventory/ShulkerBoxMenu.java
+@@ -6,20 +6,30 @@
+ import net.minecraft.world.entity.player.Player;
+ import net.minecraft.world.item.ItemStack;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftInventory;
++import org.bukkit.craftbukkit.inventory.CraftInventoryView;
++// CraftBukkit end
++
+ public class ShulkerBoxMenu extends AbstractContainerMenu {
+
+ private static final int CONTAINER_SIZE = 27;
+ private final Container container;
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity;
++ private Inventory player;
+
+ public ShulkerBoxMenu(int i, Inventory inventory) {
+ this(i, inventory, new SimpleContainer(27));
+ }
++ // CraftBukkit end
+
+ public ShulkerBoxMenu(int i, Inventory inventory, Container container) {
+ super(MenuType.SHULKER_BOX, i);
+ checkContainerSize(container, 27);
+ this.container = container;
+- container.startOpen(inventory.player);
++ this.player = playerInventory; // CraftBukkit - save player
++ container.startOpen(playerInventory.player);
+ boolean flag = true;
+ boolean flag1 = true;
+
+@@ -47,6 +66,7 @@
+ @Override
+ @Override
+ public boolean stillValid(Player player) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return this.container.stillValid(player);
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/SmithingMenu.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/SmithingMenu.java.patch
new file mode 100644
index 0000000000..cb8ee7ea9a
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/SmithingMenu.java.patch
@@ -0,0 +1,60 @@
+--- a/net/minecraft/world/inventory/SmithingMenu.java
++++ b/net/minecraft/world/inventory/SmithingMenu.java
+@@ -13,6 +13,8 @@
+ import net.minecraft.world.level.block.Blocks;
+ import net.minecraft.world.level.block.state.BlockState;
+
++import org.bukkit.craftbukkit.inventory.CraftInventoryView; // CraftBukkit
++
+ public class SmithingMenu extends ItemCombinerMenu {
+
+ public static final int TEMPLATE_SLOT = 0;
+@@ -28,6 +30,9 @@
+ @Nullable
+ private RecipeHolder<SmithingRecipe> selectedRecipe;
+ private final List<RecipeHolder<SmithingRecipe>> recipes;
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity;
++ // CraftBukkit end
+
+ public SmithingMenu(int i, Inventory inventory) {
+ this(i, inventory, ContainerLevelAccess.NULL);
+@@ -102,7 +102,7 @@
+ List<RecipeHolder<SmithingRecipe>> list = this.level.getRecipeManager().getRecipesFor(RecipeType.SMITHING, this.inputSlots, this.level);
+
+ if (list.isEmpty()) {
+- this.resultSlots.setItem(0, ItemStack.EMPTY);
++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareSmithingEvent(getBukkitView(), ItemStack.EMPTY); // CraftBukkit
+ } else {
+ RecipeHolder<SmithingRecipe> recipeholder = (RecipeHolder) list.get(0);
+ ItemStack itemstack = ((SmithingRecipe) recipeholder.value()).assemble(this.inputSlots, this.level.registryAccess());
+@@ -110,7 +110,9 @@
+ if (itemstack.isItemEnabled(this.level.enabledFeatures())) {
+ this.selectedRecipe = recipeholder;
+ this.resultSlots.setRecipeUsed(recipeholder);
+- this.resultSlots.setItem(0, itemstack);
++ // CraftBukkit start
++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareSmithingEvent(getBukkitView(), itemstack);
++ // CraftBukkit end
+ }
+ }
+
+@@ -145,4 +144,18 @@
+ return !this.getSlot(i).hasItem();
+ }).findFirst();
+ }
++
++ // CraftBukkit start
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ org.bukkit.craftbukkit.inventory.CraftInventory inventory = new org.bukkit.craftbukkit.inventory.CraftInventorySmithing(
++ access.getLocation(), this.inputSlots, this.resultSlots);
++ bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this);
++ return bukkitEntity;
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/StonecutterMenu.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/StonecutterMenu.java.patch
new file mode 100644
index 0000000000..6d483e63b5
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/StonecutterMenu.java.patch
@@ -0,0 +1,64 @@
+--- a/net/minecraft/world/inventory/StonecutterMenu.java
++++ b/net/minecraft/world/inventory/StonecutterMenu.java
+@@ -16,6 +15,13 @@
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.block.Blocks;
+
++// CraftBukkit start
++import org.bukkit.Location;
++import org.bukkit.craftbukkit.inventory.CraftInventoryStonecutter;
++import org.bukkit.craftbukkit.inventory.CraftInventoryView;
++import org.bukkit.entity.Player;
++// CraftBukkit end
++
+ public class StonecutterMenu extends AbstractContainerMenu {
+
+ public static final int INPUT_SLOT = 0;
+@@ -35,10 +41,14 @@
+ Runnable slotUpdateListener;
+ public final Container container;
+ final ResultContainer resultContainer;
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity = null;
++ private Player player;
+
+ public StonecutterMenu(int i, Inventory inventory) {
+ this(i, inventory, ContainerLevelAccess.NULL);
+ }
++ // CraftBukkit end
+
+ public StonecutterMenu(int i, Inventory inventory, final ContainerLevelAccess containerlevelaccess) {
+ super(MenuType.STONECUTTER, i);
+@@ -55,6 +75,13 @@
+ StonecutterMenu.this.slotsChanged(this);
+ StonecutterMenu.this.slotUpdateListener.run();
+ }
++
++ // CraftBukkit start
++ @Override
++ public Location getLocation() {
++ return access.getLocation();
++ }
++ // CraftBukkit end
+ };
+ this.resultContainer = new ResultContainer();
+ this.access = containerlevelaccess;
+@@ -108,6 +133,7 @@
+ }
+
+ this.addDataSlot(this.selectedRecipeIndex);
++ player = (Player) playerInventory.player.getBukkitEntity(); // CraftBukkit
+ }
+
+ public int getSelectedRecipeIndex() {
+@@ -127,8 +153,8 @@
+ }
+
+ @Override
+- @Override
+- public boolean stillValid(Player player) {
++ public boolean stillValid(net.minecraft.world.entity.player.Player player) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return stillValid(this.access, player, Blocks.STONECUTTER);
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/TransientCraftingContainer.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/TransientCraftingContainer.java.patch
new file mode 100644
index 0000000000..b665ab63fe
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/inventory/TransientCraftingContainer.java.patch
@@ -0,0 +1,74 @@
+--- a/net/minecraft/world/inventory/TransientCraftingContainer.java
++++ b/net/minecraft/world/inventory/TransientCraftingContainer.java
+@@ -8,15 +9,29 @@
+ import net.minecraft.world.entity.player.StackedContents;
+ import net.minecraft.world.item.ItemStack;
+
+-public class TransientCraftingContainer implements CraftingContainer {
++// CraftBukkit start
++import java.util.List;
++import net.minecraft.world.item.crafting.RecipeHolder;
++import org.bukkit.Location;
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++import org.bukkit.event.inventory.InventoryType;
++// CraftBukkit end
+
+ private final NonNullList<ItemStack> items;
+ private final int width;
+ private final int height;
+ private final AbstractContainerMenu menu;
+
+- public TransientCraftingContainer(AbstractContainerMenu abstractcontainermenu, int i, int j) {
+- this(abstractcontainermenu, i, j, NonNullList.withSize(i * j, ItemStack.EMPTY));
++ // CraftBukkit start - add fields
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++ private RecipeHolder<?> currentRecipe;
++ public Container resultInventory;
++ private Player owner;
++ private int maxStack = MAX_STACK;
++
++ public List<ItemStack> getContents() {
++ return this.items;
+ }
+
+ public TransientCraftingContainer(AbstractContainerMenu abstractcontainermenu, int i, int j, NonNullList<ItemStack> nonnulllist) {
+@@ -28,6 +67,38 @@
+
+ @Override
+ @Override
++ public Location getLocation() {
++ return menu instanceof CraftingMenu ? ((CraftingMenu) menu).access.getLocation() : owner.getBukkitEntity().getLocation();
++ }
++
++ @Override
++ public RecipeHolder<?> getCurrentRecipe() {
++ return currentRecipe;
++ }
++
++ @Override
++ public void setCurrentRecipe(RecipeHolder<?> currentRecipe) {
++ this.currentRecipe = currentRecipe;
++ }
++
++ public TransientCraftingContainer(AbstractContainerMenu container, int i, int j, Player player) {
++ this(container, i, j);
++ this.owner = player;
++ }
++ // CraftBukkit end
++
++ public TransientCraftingContainer(AbstractContainerMenu menu, int width, int height) {
++ this(menu, width, height, NonNullList.withSize(width * height, ItemStack.EMPTY));
++ }
++
++ public TransientCraftingContainer(AbstractContainerMenu menu, int width, int height, NonNullList<ItemStack> items) {
++ this.items = items;
++ this.menu = menu;
++ this.width = width;
++ this.height = height;
++ }
++
++ @Override
+ public int getContainerSize() {
+ return this.items.size();
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/ArmorItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/ArmorItem.java.patch
new file mode 100644
index 0000000000..3d4e9cf80c
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/ArmorItem.java.patch
@@ -0,0 +1,55 @@
+--- a/net/minecraft/world/item/ArmorItem.java
++++ b/net/minecraft/world/item/ArmorItem.java
+@@ -26,6 +26,11 @@
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.block.DispenserBlock;
+ import net.minecraft.world.phys.AABB;
++// CraftBukkit start
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.block.BlockDispenseArmorEvent;
++// CraftBukkit end
+
+ public class ArmorItem extends Item implements Equipable {
+
+@@ -59,6 +63,10 @@
+ LivingEntity livingentity = (LivingEntity) list.get(0);
+ EquipmentSlot equipmentslot = Mob.getEquipmentSlotForItem(itemstack);
+ ItemStack itemstack1 = itemstack.split(1);
++ // CraftBukkit start
++ Level world = sourceblock.level();
++ org.bukkit.block.Block block = CraftBlock.at(world, sourceblock.pos());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
+
+ livingentity.setItemSlot(equipmentslot, itemstack1);
+ if (livingentity instanceof Mob) {
+@@ -66,6 +73,29 @@
+ ((Mob) livingentity).setPersistenceRequired();
+ }
+
++ if (event.isCancelled()) {
++ itemstack.grow(1);
++ return false;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ itemstack.grow(1);
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem());
++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != ArmorItem.DISPENSE_ITEM_BEHAVIOR) {
++ idispensebehavior.dispense(sourceblock, eventStack);
++ return true;
++ }
++ }
++
++ entityliving.setItemSlot(enumitemslot, CraftItemStack.asNMSCopy(event.getItem()));
++ // CraftBukkit end
++ if (entityliving instanceof Mob) {
++ ((Mob) entityliving).setDropChance(enumitemslot, 2.0F);
++ ((Mob) entityliving).setPersistenceRequired();
++ }
++
+ return true;
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/ArmorStandItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/ArmorStandItem.java.patch
new file mode 100644
index 0000000000..e5d15f07cd
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/ArmorStandItem.java.patch
@@ -0,0 +1,22 @@
+--- a/net/minecraft/world/item/ArmorStandItem.java
++++ b/net/minecraft/world/item/ArmorStandItem.java
+@@ -53,10 +52,15 @@
+
+ float f = (float) Mth.floor((Mth.wrapDegrees(useoncontext.getRotation() - 180.0F) + 22.5F) / 45.0F) * 45.0F;
+
+- armorstand.moveTo(armorstand.getX(), armorstand.getY(), armorstand.getZ(), f, 0.0F);
+- serverlevel.addFreshEntityWithPassengers(armorstand);
+- level.playSound((Player) null, armorstand.getX(), armorstand.getY(), armorstand.getZ(), SoundEvents.ARMOR_STAND_PLACE, SoundSource.BLOCKS, 0.75F, 0.8F);
+- armorstand.gameEvent(GameEvent.ENTITY_PLACE, useoncontext.getPlayer());
++ entityarmorstand.moveTo(entityarmorstand.getX(), entityarmorstand.getY(), entityarmorstand.getZ(), f, 0.0F);
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(context, entityarmorstand).isCancelled()) {
++ return InteractionResult.FAIL;
++ }
++ // CraftBukkit end
++ worldserver.addFreshEntityWithPassengers(entityarmorstand);
++ world.playSound((Player) null, entityarmorstand.getX(), entityarmorstand.getY(), entityarmorstand.getZ(), SoundEvents.ARMOR_STAND_PLACE, SoundSource.BLOCKS, 0.75F, 0.8F);
++ entityarmorstand.gameEvent(GameEvent.ENTITY_PLACE, context.getPlayer());
+ }
+
+ itemstack.shrink(1);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/BlockItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/BlockItem.java.patch
new file mode 100644
index 0000000000..b415a4a867
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/BlockItem.java.patch
@@ -0,0 +1,125 @@
+--- a/net/minecraft/world/item/BlockItem.java
++++ b/net/minecraft/world/item/BlockItem.java
+@@ -32,6 +32,10 @@
+ import net.minecraft.world.level.block.state.properties.Property;
+ import net.minecraft.world.level.gameevent.GameEvent;
+ import net.minecraft.world.phys.shapes.CollisionContext;
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.craftbukkit.block.data.CraftBlockData;
++import org.bukkit.event.block.BlockCanBuildEvent;
++// CraftBukkit end
+
+ public class BlockItem extends Item {
+
+@@ -71,7 +74,13 @@
+ if (blockplacecontext1 == null) {
+ return InteractionResult.FAIL;
+ } else {
+- BlockState blockstate = this.getPlacementState(blockplacecontext1);
++ IBlockData iblockdata = this.getPlacementState(blockactioncontext1);
++ // CraftBukkit start - special case for handling block placement with water lilies and snow buckets
++ org.bukkit.block.BlockState blockstate = null;
++ if (this instanceof PlaceOnWaterBlockItem || this instanceof SolidBucketItem) {
++ blockstate = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(blockactioncontext1.getLevel(), blockactioncontext1.getClickedPos());
++ }
++ // CraftBukkit end
+
+ if (blockstate == null) {
+ return InteractionResult.FAIL;
+@@ -84,20 +93,33 @@
+ ItemStack itemstack = blockplacecontext1.getItemInHand();
+ BlockState blockstate1 = level.getBlockState(blockpos);
+
+- if (blockstate1.is(blockstate.getBlock())) {
+- blockstate1 = this.updateBlockStateFromTag(blockpos, level, itemstack, blockstate1);
+- this.updateCustomBlockEntityTag(blockpos, level, player, itemstack, blockstate1);
+- blockstate1.getBlock().setPlacedBy(level, blockpos, blockstate1, player, itemstack);
+- if (player instanceof ServerPlayer) {
+- CriteriaTriggers.PLACED_BLOCK.trigger((ServerPlayer) player, blockpos, itemstack);
++ if (iblockdata1.is(iblockdata.getBlock())) {
++ iblockdata1 = this.updateBlockStateFromTag(blockposition, world, itemstack, iblockdata1);
++ this.updateCustomBlockEntityTag(blockposition, world, entityhuman, itemstack, iblockdata1);
++ iblockdata1.getBlock().setPlacedBy(world, blockposition, iblockdata1, entityhuman, itemstack);
++ // CraftBukkit start
++ if (blockstate != null) {
++ org.bukkit.event.block.BlockPlaceEvent placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent((ServerLevel) world, entityhuman, blockactioncontext1.getHand(), blockstate, blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ if (placeEvent != null && (placeEvent.isCancelled() || !placeEvent.canBuild())) {
++ blockstate.update(true, false);
++
++ if (this instanceof SolidBucketItem) {
++ ((ServerPlayer) entityhuman).getBukkitEntity().updateInventory(); // SPIGOT-4541
++ }
++ return InteractionResult.FAIL;
++ }
+ }
++ // CraftBukkit end
++ if (entityhuman instanceof ServerPlayer) {
++ CriteriaTriggers.PLACED_BLOCK.trigger((ServerPlayer) entityhuman, blockposition, itemstack);
++ }
+ }
+
+ SoundType soundtype = blockstate1.getSoundType();
+
+- level.playSound(player, blockpos, this.getPlaceSound(blockstate1), SoundSource.BLOCKS, (soundtype.getVolume() + 1.0F) / 2.0F, soundtype.getPitch() * 0.8F);
+- level.gameEvent(GameEvent.BLOCK_PLACE, blockpos, GameEvent.Context.of(player, blockstate1));
+- if (player == null || !player.getAbilities().instabuild) {
++ // world.playSound(entityhuman, blockposition, this.getPlaceSound(iblockdata1), SoundCategory.BLOCKS, (soundeffecttype.getVolume() + 1.0F) / 2.0F, soundeffecttype.getPitch() * 0.8F);
++ world.gameEvent(GameEvent.BLOCK_PLACE, blockposition, GameEvent.Context.of(entityhuman, iblockdata1));
++ if ((entityhuman == null || !entityhuman.getAbilities().instabuild) && itemstack != ItemStack.EMPTY) { // CraftBukkit
+ itemstack.shrink(1);
+ }
+
+@@ -131,11 +153,26 @@
+ BlockState blockstate1 = blockstate;
+ CompoundTag compoundtag = itemstack.getTag();
+
+- if (compoundtag != null) {
+- CompoundTag compoundtag1 = compoundtag.getCompound("BlockStateTag");
+- StateDefinition<Block, BlockState> statedefinition = blockstate.getBlock().getStateDefinition();
+- Iterator iterator = compoundtag1.getAllKeys().iterator();
++ if (nbttagcompound != null) {
++ CompoundTag nbttagcompound1 = nbttagcompound.getCompound("BlockStateTag");
++ // CraftBukkit start
++ iblockdata1 = getBlockState(iblockdata1, nbttagcompound1);
++ }
+
++ if (iblockdata1 != state) {
++ level.setBlock(pos, iblockdata1, 2);
++ }
++
++ return iblockdata1;
++ }
++
++ public static IBlockData getBlockState(IBlockData iblockdata, CompoundTag nbttagcompound1) {
++ IBlockData iblockdata1 = iblockdata;
++ {
++ // CraftBukkit end
++ StateDefinition<Block, IBlockData> blockstatelist = iblockdata.getBlock().getStateDefinition();
++ Iterator iterator = nbttagcompound1.getAllKeys().iterator();
++
+ while (iterator.hasNext()) {
+ String s = (String) iterator.next();
+ Property<?> property = statedefinition.getProperty(s);
+@@ -161,11 +193,18 @@
+ }).orElse(blockstate);
+ }
+
+- protected boolean canPlace(BlockPlaceContext blockplacecontext, BlockState blockstate) {
+- Player player = blockplacecontext.getPlayer();
+- CollisionContext collisioncontext = player == null ? CollisionContext.empty() : CollisionContext.of(player);
++ protected boolean canPlace(BlockPlaceContext context, IBlockData state) {
++ Player entityhuman = context.getPlayer();
++ CollisionContext voxelshapecollision = entityhuman == null ? CollisionContext.empty() : CollisionContext.of(entityhuman);
++ // CraftBukkit start - store default return
++ boolean defaultReturn = (!this.mustSurvive() || state.canSurvive(context.getLevel(), context.getClickedPos())) && context.getLevel().isUnobstructed(state, context.getClickedPos(), voxelshapecollision);
++ org.bukkit.entity.Player player = (context.getPlayer() instanceof ServerPlayer) ? (org.bukkit.entity.Player) context.getPlayer().getBukkitEntity() : null;
+
+- return (!this.mustSurvive() || blockstate.canSurvive(blockplacecontext.getLevel(), blockplacecontext.getClickedPos())) && blockplacecontext.getLevel().isUnobstructed(blockstate, blockplacecontext.getClickedPos(), collisioncontext);
++ BlockCanBuildEvent event = new BlockCanBuildEvent(CraftBlock.at(context.getLevel(), context.getClickedPos()), player, CraftBlockData.fromData(state), defaultReturn);
++ context.getLevel().getCraftServer().getPluginManager().callEvent(event);
++
++ return event.isBuildable();
++ // CraftBukkit end
+ }
+
+ protected boolean mustSurvive() {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/BoatItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/BoatItem.java.patch
new file mode 100644
index 0000000000..454abde943
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/BoatItem.java.patch
@@ -0,0 +1,42 @@
+--- a/net/minecraft/world/item/BoatItem.java
++++ b/net/minecraft/world/item/BoatItem.java
+@@ -60,17 +59,32 @@
+ }
+ }
+
+- if (blockhitresult.getType() == HitResult.Type.BLOCK) {
+- Boat boat = this.getBoat(level, blockhitresult, itemstack, player);
++ if (movingobjectpositionblock.getType() == HitResult.EnumMovingObjectType.BLOCK) {
++ // CraftBukkit start - Boat placement
++ org.bukkit.event.player.PlayerInteractEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent(player, org.bukkit.event.block.Action.RIGHT_CLICK_BLOCK, movingobjectpositionblock.getBlockPos(), movingobjectpositionblock.getDirection(), itemstack, false, hand, movingobjectpositionblock.getLocation());
+
+- boat.setVariant(this.type);
+- boat.setYRot(player.getYRot());
+- if (!level.noCollision(boat, boat.getBoundingBox())) {
++ if (event.isCancelled()) {
++ return InteractionResultHolder.pass(itemstack);
++ }
++ // CraftBukkit end
++ Boat entityboat = this.getBoat(level, movingobjectpositionblock, itemstack, player);
++
++ entityboat.setVariant(this.type);
++ entityboat.setYRot(player.getYRot());
++ if (!level.noCollision(entityboat, entityboat.getBoundingBox())) {
+ return InteractionResultHolder.fail(itemstack);
+ } else {
+ if (!level.isClientSide) {
+- level.addFreshEntity(boat);
+- level.gameEvent((Entity) player, GameEvent.ENTITY_PLACE, blockhitresult.getLocation());
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(level, movingobjectpositionblock.getBlockPos(), movingobjectpositionblock.getDirection(), player, entityboat, hand).isCancelled()) {
++ return InteractionResultHolder.fail(itemstack);
++ }
++
++ if (!level.addFreshEntity(entityboat)) {
++ return InteractionResultHolder.pass(itemstack);
++ }
++ // CraftBukkit end
++ level.gameEvent((Entity) player, GameEvent.ENTITY_PLACE, movingobjectpositionblock.getLocation());
+ if (!player.getAbilities().instabuild) {
+ itemstack.shrink(1);
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/BoneMealItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/BoneMealItem.java.patch
new file mode 100644
index 0000000000..881e489080
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/BoneMealItem.java.patch
@@ -0,0 +1,48 @@
+--- a/net/minecraft/world/item/BoneMealItem.java
++++ b/net/minecraft/world/item/BoneMealItem.java
+@@ -33,16 +33,21 @@
+ }
+
+ @Override
+- @Override
+- public InteractionResult useOn(UseOnContext useoncontext) {
+- Level level = useoncontext.getLevel();
+- BlockPos blockpos = useoncontext.getClickedPos();
+- BlockPos blockpos1 = blockpos.relative(useoncontext.getClickedFace());
++ public InteractionResult useOn(UseOnContext context) {
++ // CraftBukkit start - extract bonemeal application logic to separate, static method
++ return applyBonemeal(context);
++ }
+
+- if (growCrop(useoncontext.getItemInHand(), level, blockpos)) {
+- if (!level.isClientSide) {
+- useoncontext.getPlayer().gameEvent(GameEvent.ITEM_INTERACT_FINISH);
+- level.levelEvent(1505, blockpos, 0);
++ public static InteractionResult applyBonemeal(UseOnContext itemactioncontext) {
++ // CraftBukkit end
++ Level world = itemactioncontext.getLevel();
++ BlockPos blockposition = itemactioncontext.getClickedPos();
++ BlockPos blockposition1 = blockposition.relative(itemactioncontext.getClickedFace());
++
++ if (growCrop(itemactioncontext.getItemInHand(), world, blockposition)) {
++ if (!world.isClientSide) {
++ if (itemactioncontext.getPlayer() != null) itemactioncontext.getPlayer().gameEvent(GameEvent.ITEM_INTERACT_FINISH); // CraftBukkit - SPIGOT-7518
++ world.levelEvent(1505, blockposition, 0);
+ }
+
+ return InteractionResult.sidedSuccess(level.isClientSide);
+@@ -50,10 +55,10 @@
+ BlockState blockstate = level.getBlockState(blockpos);
+ boolean flag = blockstate.isFaceSturdy(level, blockpos, useoncontext.getClickedFace());
+
+- if (flag && growWaterPlant(useoncontext.getItemInHand(), level, blockpos1, useoncontext.getClickedFace())) {
+- if (!level.isClientSide) {
+- useoncontext.getPlayer().gameEvent(GameEvent.ITEM_INTERACT_FINISH);
+- level.levelEvent(1505, blockpos1, 0);
++ if (flag && growWaterPlant(itemactioncontext.getItemInHand(), world, blockposition1, itemactioncontext.getClickedFace())) {
++ if (!world.isClientSide) {
++ if (itemactioncontext.getPlayer() != null) itemactioncontext.getPlayer().gameEvent(GameEvent.ITEM_INTERACT_FINISH); // CraftBukkit - SPIGOT-7518
++ world.levelEvent(1505, blockposition1, 0);
+ }
+
+ return InteractionResult.sidedSuccess(level.isClientSide);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/BowItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/BowItem.java.patch
new file mode 100644
index 0000000000..1f61431d98
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/BowItem.java.patch
@@ -0,0 +1,35 @@
+--- a/net/minecraft/world/item/BowItem.java
++++ b/net/minecraft/world/item/BowItem.java
+@@ -65,6 +64,14 @@
+ if (EnchantmentHelper.getItemEnchantmentLevel(Enchantments.FLAMING_ARROWS, itemstack) > 0) {
+ abstractarrow.setSecondsOnFire(100);
+ }
++ // CraftBukkit start
++ org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(entityhuman, stack, itemstack1, entityarrow, entityhuman.getUsedItemHand(), f, !flag1);
++ if (event.isCancelled()) {
++ event.getProjectile().remove();
++ return;
++ }
++ flag1 = !event.shouldConsumeItem();
++ // CraftBukkit end
+
+ itemstack.hurtAndBreak(1, player, (player1) -> {
+ player1.broadcastBreakEvent(player.getUsedItemHand());
+@@ -73,7 +80,16 @@
+ abstractarrow.pickup = AbstractArrow.Pickup.CREATIVE_ONLY;
+ }
+
+- level.addFreshEntity(abstractarrow);
++ // CraftBukkit start
++ if (event.getProjectile() == entityarrow.getBukkitEntity()) {
++ if (!level.addFreshEntity(entityarrow)) {
++ if (entityhuman instanceof net.minecraft.server.level.ServerPlayer) {
++ ((net.minecraft.server.level.ServerPlayer) entityhuman).getBukkitEntity().updateInventory();
++ }
++ return;
++ }
++ }
++ // CraftBukkit end
+ }
+
+ level.playSound((Player) null, player.getX(), player.getY(), player.getZ(), SoundEvents.ARROW_SHOOT, SoundSource.PLAYERS, 1.0F, 1.0F / (level.getRandom().nextFloat() * 0.4F + 1.2F) + f * 0.5F);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/BucketItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/BucketItem.java.patch
new file mode 100644
index 0000000000..9738ca0790
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/BucketItem.java.patch
@@ -0,0 +1,107 @@
+--- a/net/minecraft/world/item/BucketItem.java
++++ b/net/minecraft/world/item/BucketItem.java
+@@ -28,6 +30,12 @@
+ import net.minecraft.world.level.material.Fluids;
+ import net.minecraft.world.phys.BlockHitResult;
+ import net.minecraft.world.phys.HitResult;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.craftbukkit.util.DummyGeneratorAccess;
++import org.bukkit.event.player.PlayerBucketEmptyEvent;
++import org.bukkit.event.player.PlayerBucketFillEvent;
++// CraftBukkit end
+
+ public class BucketItem extends Item implements DispensibleContainerItem {
+
+@@ -61,16 +68,27 @@
+ Block block = blockstate.getBlock();
+
+ if (block instanceof BucketPickup) {
+- BucketPickup bucketpickup = (BucketPickup) block;
+- ItemStack itemstack1 = bucketpickup.pickupBlock(player, level, blockpos, blockstate);
++ BucketPickup ifluidsource = (BucketPickup) block;
++ // CraftBukkit start
++ ItemStack dummyFluid = ifluidsource.pickupBlock(player, DummyGeneratorAccess.INSTANCE, blockposition, iblockdata);
++ if (dummyFluid.isEmpty()) return InteractionResultHolder.fail(itemstack); // Don't fire event if the bucket won't be filled.
++ PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) level, player, blockposition, blockposition, movingobjectpositionblock.getDirection(), itemstack, dummyFluid.getItem(), hand);
+
++ if (event.isCancelled()) {
++ ((ServerPlayer) player).connection.send(new ClientboundBlockUpdatePacket(level, blockposition)); // SPIGOT-5163 (see PlayerInteractManager)
++ ((ServerPlayer) player).getBukkitEntity().updateInventory(); // SPIGOT-4541
++ return InteractionResultHolder.fail(itemstack);
++ }
++ // CraftBukkit end
++ ItemStack itemstack1 = ifluidsource.pickupBlock(player, level, blockposition, iblockdata);
++
+ if (!itemstack1.isEmpty()) {
+ player.awardStat(Stats.ITEM_USED.get(this));
+ bucketpickup.getPickupSound().ifPresent((soundevent) -> {
+ player.playSound(soundevent, 1.0F, 1.0F);
+ });
+- level.gameEvent((Entity) player, GameEvent.FLUID_PICKUP, blockpos);
+- ItemStack itemstack2 = ItemUtils.createFilledResult(itemstack, player, itemstack1);
++ level.gameEvent((Entity) player, GameEvent.FLUID_PICKUP, blockposition);
++ ItemStack itemstack2 = ItemUtils.createFilledResult(itemstack, player, CraftItemStack.asNMSCopy(event.getItemStack())); // CraftBukkit
+
+ if (!level.isClientSide) {
+ CriteriaTriggers.FILLED_BUCKET.trigger((ServerPlayer) player, itemstack1);
+@@ -85,8 +103,8 @@
+ blockstate = level.getBlockState(blockpos);
+ BlockPos blockpos2 = blockstate.getBlock() instanceof LiquidBlockContainer && this.content == Fluids.WATER ? blockpos : blockpos1;
+
+- if (this.emptyContents(player, level, blockpos2, blockhitresult)) {
+- this.checkExtraContent(player, level, itemstack, blockpos2);
++ if (this.emptyContents(player, level, blockposition2, movingobjectpositionblock, movingobjectpositionblock.getDirection(), blockposition, itemstack, hand)) { // CraftBukkit
++ this.checkExtraContent(player, level, itemstack, blockposition2);
+ if (player instanceof ServerPlayer) {
+ CriteriaTriggers.PLACED_BLOCK.trigger((ServerPlayer) player, blockpos2, itemstack);
+ }
+@@ -112,11 +129,16 @@
+ public void checkExtraContent(@Nullable Player player, Level level, ItemStack itemstack, BlockPos blockpos) {}
+
+ @Override
+- @Override
+- public boolean emptyContents(@Nullable Player player, Level level, BlockPos blockpos, @Nullable BlockHitResult blockhitresult) {
+- Fluid fluid = this.content;
++ public boolean emptyContents(@Nullable Player player, Level level, BlockPos pos, @Nullable BlockHitResult result) {
++ // CraftBukkit start
++ return emptyContents(player, level, pos, result, null, null, null, EnumHand.MAIN_HAND);
++ }
+
+- if (!(fluid instanceof FlowingFluid)) {
++ public boolean emptyContents(Player entityhuman, Level world, BlockPos blockposition, @Nullable BlockHitResult movingobjectpositionblock, Direction enumdirection, BlockPos clicked, ItemStack itemstack, EnumHand enumhand) {
++ // CraftBukkit end
++ Fluid fluidtype = this.content;
++
++ if (!(fluidtype instanceof FlowingFluid)) {
+ return false;
+ } else {
+ FlowingFluid flowingfluid;
+@@ -151,12 +173,22 @@
+
+ boolean flag2 = flag1;
+
++ // CraftBukkit start
++ if (flag2 && entityhuman != null) {
++ PlayerBucketEmptyEvent event = CraftEventFactory.callPlayerBucketEmptyEvent((ServerLevel) world, entityhuman, blockposition, clicked, enumdirection, itemstack, enumhand);
++ if (event.isCancelled()) {
++ ((ServerPlayer) entityhuman).connection.send(new ClientboundBlockUpdatePacket(world, blockposition)); // SPIGOT-4238: needed when looking through entity
++ ((ServerPlayer) entityhuman).getBukkitEntity().updateInventory(); // SPIGOT-4541
++ return false;
++ }
++ }
++ // CraftBukkit end
+ if (!flag2) {
+- return blockhitresult != null && this.emptyContents(player, level, blockhitresult.getBlockPos().relative(blockhitresult.getDirection()), (BlockHitResult) null);
+- } else if (level.dimensionType().ultraWarm() && this.content.is(FluidTags.WATER)) {
+- int i = blockpos.getX();
+- int j = blockpos.getY();
+- int k = blockpos.getZ();
++ return movingobjectpositionblock != null && this.emptyContents(entityhuman, world, movingobjectpositionblock.getBlockPos().relative(movingobjectpositionblock.getDirection()), (BlockHitResult) null, enumdirection, clicked, itemstack, enumhand); // CraftBukkit
++ } else if (world.dimensionType().ultraWarm() && this.content.is(FluidTags.WATER)) {
++ int i = blockposition.getX();
++ int j = blockposition.getY();
++ int k = blockposition.getZ();
+
+ level.playSound(player, blockpos, SoundEvents.FIRE_EXTINGUISH, SoundSource.BLOCKS, 0.5F, 2.6F + (level.random.nextFloat() - level.random.nextFloat()) * 0.8F);
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/ChorusFruitItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/ChorusFruitItem.java.patch
new file mode 100644
index 0000000000..7732cbb121
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/ChorusFruitItem.java.patch
@@ -0,0 +1,33 @@
+--- a/net/minecraft/world/item/ChorusFruitItem.java
++++ b/net/minecraft/world/item/ChorusFruitItem.java
+@@ -36,14 +35,23 @@
+
+ Vec3 vec3 = livingentity.position();
+
+- if (livingentity.randomTeleport(d0, d1, d2, true)) {
+- level.gameEvent(GameEvent.TELEPORT, vec3, GameEvent.Context.of((Entity) livingentity));
+- SoundEvent soundevent;
+- SoundSource soundsource;
++ // CraftBukkit start - handle canceled status of teleport event
++ java.util.Optional<Boolean> status = entityLiving.randomTeleport(d0, d1, d2, true, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.CHORUS_FRUIT);
+
+- if (livingentity instanceof Fox) {
+- soundevent = SoundEvents.FOX_TELEPORT;
+- soundsource = SoundSource.NEUTRAL;
++ if (!status.isPresent()) {
++ // teleport event was canceled, no more tries
++ break;
++ }
++
++ if (status.get()) {
++ // CraftBukkit end
++ level.gameEvent(GameEvent.TELEPORT, vec3d, GameEvent.Context.of((Entity) entityLiving));
++ SoundEvent soundeffect;
++ SoundSource soundcategory;
++
++ if (entityLiving instanceof Fox) {
++ soundeffect = SoundEvents.FOX_TELEPORT;
++ soundcategory = SoundSource.NEUTRAL;
+ } else {
+ soundevent = SoundEvents.CHORUS_FRUIT_TELEPORT;
+ soundsource = SoundSource.PLAYERS;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/CrossbowItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/CrossbowItem.java.patch
new file mode 100644
index 0000000000..f4dee8fd60
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/CrossbowItem.java.patch
@@ -0,0 +1,33 @@
+--- a/net/minecraft/world/item/CrossbowItem.java
++++ b/net/minecraft/world/item/CrossbowItem.java
+@@ -240,12 +236,28 @@
+
+ ((Projectile) object).shoot((double) vector3f.x(), (double) vector3f.y(), (double) vector3f.z(), f1, f2);
+ }
++ // CraftBukkit start
++ org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(shooter, crossbowStack, ammoStack, (Entity) object, shooter.getUsedItemHand(), soundPitch, true);
++ if (event.isCancelled()) {
++ event.getProjectile().remove();
++ return;
++ }
++ // CraftBukkit end
+
+ itemstack.hurtAndBreak(flag1 ? 3 : 1, livingentity, (livingentity1) -> {
+ livingentity1.broadcastBreakEvent(interactionhand);
+ });
+- level.addFreshEntity((Entity) object);
+- level.playSound((Player) null, livingentity.getX(), livingentity.getY(), livingentity.getZ(), SoundEvents.CROSSBOW_SHOOT, SoundSource.PLAYERS, 1.0F, f);
++ // CraftBukkit start
++ if (event.getProjectile() == ((Entity) object).getBukkitEntity()) {
++ if (!level.addFreshEntity((Entity) object)) {
++ if (shooter instanceof ServerPlayer) {
++ ((ServerPlayer) shooter).getBukkitEntity().updateInventory();
++ }
++ return;
++ }
++ }
++ // CraftBukkit end
++ level.playSound((Player) null, shooter.getX(), shooter.getY(), shooter.getZ(), SoundEvents.CROSSBOW_SHOOT, SoundSource.PLAYERS, 1.0F, soundPitch);
+ }
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/DebugStickItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/DebugStickItem.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/DebugStickItem.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/DyeItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/DyeItem.java.patch
new file mode 100644
index 0000000000..74656d485b
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/DyeItem.java.patch
@@ -0,0 +1,31 @@
+--- a/net/minecraft/world/item/DyeItem.java
++++ b/net/minecraft/world/item/DyeItem.java
+@@ -12,6 +12,7 @@
+ import net.minecraft.world.entity.player.Player;
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.block.entity.SignBlockEntity;
++import org.bukkit.event.entity.SheepDyeWoolEvent; // CraftBukkit
+
+ public class DyeItem extends Item implements SignApplicator {
+
+@@ -33,8 +33,18 @@
+ if (sheep.isAlive() && !sheep.isSheared() && sheep.getColor() != this.dyeColor) {
+ sheep.level().playSound(player, (Entity) sheep, SoundEvents.DYE_USE, SoundSource.PLAYERS, 1.0F, 1.0F);
+ if (!player.level().isClientSide) {
+- sheep.setColor(this.dyeColor);
+- itemstack.shrink(1);
++ // CraftBukkit start
++ byte bColor = (byte) this.dyeColor.getId();
++ SheepDyeWoolEvent event = new SheepDyeWoolEvent((org.bukkit.entity.Sheep) entitysheep.getBukkitEntity(), org.bukkit.DyeColor.getByWoolData(bColor), (org.bukkit.entity.Player) player.getBukkitEntity());
++ entitysheep.level().getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return InteractionResult.PASS;
++ }
++
++ entitysheep.setColor(DyeColor.byId((byte) event.getColor().getWoolData()));
++ // CraftBukkit end
++ stack.shrink(1);
+ }
+
+ return InteractionResult.sidedSuccess(player.level().isClientSide);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/EggItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/EggItem.java.patch
new file mode 100644
index 0000000000..c13da4b071
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/EggItem.java.patch
@@ -0,0 +1,27 @@
+--- a/net/minecraft/world/item/EggItem.java
++++ b/net/minecraft/world/item/EggItem.java
+@@ -20,13 +19,20 @@
+ public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand interactionhand) {
+ ItemStack itemstack = player.getItemInHand(interactionhand);
+
+- level.playSound((Player) null, player.getX(), player.getY(), player.getZ(), SoundEvents.EGG_THROW, SoundSource.PLAYERS, 0.5F, 0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F));
++ // world.playSound((EntityHuman) null, entityhuman.getX(), entityhuman.getY(), entityhuman.getZ(), SoundEffects.EGG_THROW, SoundCategory.PLAYERS, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F)); // CraftBukkit - moved down
+ if (!level.isClientSide) {
+ ThrownEgg thrownegg = new ThrownEgg(level, player);
+
+- thrownegg.setItem(itemstack);
+- thrownegg.shootFromRotation(player, player.getXRot(), player.getYRot(), 0.0F, 1.5F, 1.0F);
+- level.addFreshEntity(thrownegg);
++ entityegg.setItem(itemstack);
++ entityegg.shootFromRotation(player, player.getXRot(), player.getYRot(), 0.0F, 1.5F, 1.0F);
++ // CraftBukkit start
++ if (!level.addFreshEntity(entityegg)) {
++ if (player instanceof net.minecraft.server.level.ServerPlayer) {
++ ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().updateInventory();
++ }
++ return InteractionResultHolder.fail(itemstack);
++ }
++ // CraftBukkit end
+ }
+
+ player.awardStat(Stats.ITEM_USED.get(this));
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/EndCrystalItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/EndCrystalItem.java.patch
new file mode 100644
index 0000000000..706c3edb1e
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/EndCrystalItem.java.patch
@@ -0,0 +1,22 @@
+--- a/net/minecraft/world/item/EndCrystalItem.java
++++ b/net/minecraft/world/item/EndCrystalItem.java
+@@ -46,10 +45,15 @@
+ if (level instanceof ServerLevel) {
+ EndCrystal endcrystal = new EndCrystal(level, d0 + 0.5D, d1, d2 + 0.5D);
+
+- endcrystal.setShowBottom(false);
+- level.addFreshEntity(endcrystal);
+- level.gameEvent((Entity) useoncontext.getPlayer(), GameEvent.ENTITY_PLACE, blockpos1);
+- EndDragonFight enddragonfight = ((ServerLevel) level).getDragonFight();
++ entityendercrystal.setShowBottom(false);
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(context, entityendercrystal).isCancelled()) {
++ return InteractionResult.FAIL;
++ }
++ // CraftBukkit end
++ world.addFreshEntity(entityendercrystal);
++ world.gameEvent((Entity) context.getPlayer(), GameEvent.ENTITY_PLACE, blockposition1);
++ EndDragonFight enderdragonbattle = ((ServerLevel) world).getDragonFight();
+
+ if (enddragonfight != null) {
+ enddragonfight.tryRespawn();
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/EnderEyeItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/EnderEyeItem.java.patch
new file mode 100644
index 0000000000..bdbf4103f3
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/EnderEyeItem.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/item/EnderEyeItem.java
++++ b/net/minecraft/world/item/EnderEyeItem.java
+@@ -88,10 +86,14 @@
+ if (blockpos != null) {
+ EyeOfEnder eyeofender = new EyeOfEnder(level, player.getX(), player.getY(0.5D), player.getZ());
+
+- eyeofender.setItem(itemstack);
+- eyeofender.signalTo(blockpos);
+- level.gameEvent(GameEvent.PROJECTILE_SHOOT, eyeofender.position(), GameEvent.Context.of((Entity) player));
+- level.addFreshEntity(eyeofender);
++ entityendersignal.setItem(itemstack);
++ entityendersignal.signalTo(blockposition);
++ level.gameEvent(GameEvent.PROJECTILE_SHOOT, entityendersignal.position(), GameEvent.Context.of((Entity) player));
++ // CraftBukkit start
++ if (!level.addFreshEntity(entityendersignal)) {
++ return new InteractionResultHolder(InteractionResult.FAIL, itemstack);
++ }
++ // CraftBukkit end
+ if (player instanceof ServerPlayer) {
+ CriteriaTriggers.USED_ENDER_EYE.trigger((ServerPlayer) player, blockpos);
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/EnderpearlItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/EnderpearlItem.java.patch
new file mode 100644
index 0000000000..3dec247baf
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/EnderpearlItem.java.patch
@@ -0,0 +1,23 @@
+--- a/net/minecraft/world/item/EnderpearlItem.java
++++ b/net/minecraft/world/item/EnderpearlItem.java
+@@ -20,8 +19,7 @@
+ public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand interactionhand) {
+ ItemStack itemstack = player.getItemInHand(interactionhand);
+
+- level.playSound((Player) null, player.getX(), player.getY(), player.getZ(), SoundEvents.ENDER_PEARL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F));
+- player.getCooldowns().addCooldown(this, 20);
++ // CraftBukkit start - change order
+ if (!level.isClientSide) {
+ ThrownEnderpearl thrownenderpearl = new ThrownEnderpearl(level, player);
+
+@@ -30,6 +33,10 @@
+ level.addFreshEntity(thrownenderpearl);
+ }
+
++ level.playSound((Player) null, player.getX(), player.getY(), player.getZ(), SoundEvents.ENDER_PEARL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F));
++ player.getCooldowns().addCooldown(this, 20);
++ // CraftBukkit end
++
+ player.awardStat(Stats.ITEM_USED.get(this));
+ if (!player.getAbilities().instabuild) {
+ itemstack.shrink(1);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/FireChargeItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/FireChargeItem.java.patch
new file mode 100644
index 0000000000..4865beeaa3
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/FireChargeItem.java.patch
@@ -0,0 +1,46 @@
+--- a/net/minecraft/world/item/FireChargeItem.java
++++ b/net/minecraft/world/item/FireChargeItem.java
+@@ -31,18 +30,34 @@
+ BlockState blockstate = level.getBlockState(blockpos);
+ boolean flag = false;
+
+- if (!CampfireBlock.canLight(blockstate) && !CandleBlock.canLight(blockstate) && !CandleCakeBlock.canLight(blockstate)) {
+- blockpos = blockpos.relative(useoncontext.getClickedFace());
+- if (BaseFireBlock.canBePlacedAt(level, blockpos, useoncontext.getHorizontalDirection())) {
+- this.playSound(level, blockpos);
+- level.setBlockAndUpdate(blockpos, BaseFireBlock.getState(level, blockpos));
+- level.gameEvent((Entity) useoncontext.getPlayer(), GameEvent.BLOCK_PLACE, blockpos);
++ if (!CampfireBlock.canLight(iblockdata) && !CandleBlock.canLight(iblockdata) && !CandleCakeBlock.canLight(iblockdata)) {
++ blockposition = blockposition.relative(context.getClickedFace());
++ if (BaseFireBlock.canBePlacedAt(world, blockposition, context.getHorizontalDirection())) {
++ // CraftBukkit start - fire BlockIgniteEvent
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(world, blockposition, org.bukkit.event.block.BlockIgniteEvent.IgniteCause.FIREBALL, context.getPlayer()).isCancelled()) {
++ if (!context.getPlayer().getAbilities().instabuild) {
++ context.getItemInHand().shrink(1);
++ }
++ return InteractionResult.PASS;
++ }
++ // CraftBukkit end
++ this.playSound(world, blockposition);
++ world.setBlockAndUpdate(blockposition, BaseFireBlock.getState(world, blockposition));
++ world.gameEvent((Entity) context.getPlayer(), GameEvent.BLOCK_PLACE, blockposition);
+ flag = true;
+ }
+ } else {
+- this.playSound(level, blockpos);
+- level.setBlockAndUpdate(blockpos, (BlockState) blockstate.setValue(BlockStateProperties.LIT, true));
+- level.gameEvent((Entity) useoncontext.getPlayer(), GameEvent.BLOCK_CHANGE, blockpos);
++ // CraftBukkit start - fire BlockIgniteEvent
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(world, blockposition, org.bukkit.event.block.BlockIgniteEvent.IgniteCause.FIREBALL, context.getPlayer()).isCancelled()) {
++ if (!context.getPlayer().getAbilities().instabuild) {
++ context.getItemInHand().shrink(1);
++ }
++ return InteractionResult.PASS;
++ }
++ // CraftBukkit end
++ this.playSound(world, blockposition);
++ world.setBlockAndUpdate(blockposition, (IBlockData) iblockdata.setValue(BlockStateProperties.LIT, true));
++ world.gameEvent((Entity) context.getPlayer(), GameEvent.BLOCK_CHANGE, blockposition);
+ flag = true;
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/FishingRodItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/FishingRodItem.java.patch
new file mode 100644
index 0000000000..e7042e73eb
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/FishingRodItem.java.patch
@@ -0,0 +1,34 @@
+--- a/net/minecraft/world/item/FishingRodItem.java
++++ b/net/minecraft/world/item/FishingRodItem.java
+@@ -11,7 +11,10 @@
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.gameevent.GameEvent;
+
+-public class FishingRodItem extends Item implements Vanishable {
++// CraftBukkit start
++import org.bukkit.event.player.PlayerFishEvent;
++import org.bukkit.craftbukkit.CraftEquipmentSlot;
++// CraftBukkit end
+
+ public FishingRodItem(Item.Properties item_properties) {
+ super(item_properties);
+@@ -39,7 +43,18 @@
+ i = EnchantmentHelper.getFishingSpeedBonus(itemstack);
+ int j = EnchantmentHelper.getFishingLuckBonus(itemstack);
+
+- level.addFreshEntity(new FishingHook(player, level, j, i));
++ // CraftBukkit start
++ FishingHook entityfishinghook = new FishingHook(player, level, j, i);
++ PlayerFishEvent playerFishEvent = new PlayerFishEvent((org.bukkit.entity.Player) player.getBukkitEntity(), null, (org.bukkit.entity.FishHook) entityfishinghook.getBukkitEntity(), CraftEquipmentSlot.getHand(hand), PlayerFishEvent.State.FISHING);
++ level.getCraftServer().getPluginManager().callEvent(playerFishEvent);
++
++ if (playerFishEvent.isCancelled()) {
++ player.fishing = null;
++ return InteractionResultHolder.pass(itemstack);
++ }
++ level.playSound((Player) null, player.getX(), player.getY(), player.getZ(), SoundEvents.FISHING_BOBBER_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F));
++ level.addFreshEntity(entityfishinghook);
++ // CraftBukkit end
+ }
+
+ player.awardStat(Stats.ITEM_USED.get(this));
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/FlintAndSteelItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/FlintAndSteelItem.java.patch
new file mode 100644
index 0000000000..46e3e7dd6b
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/FlintAndSteelItem.java.patch
@@ -0,0 +1,51 @@
+--- a/net/minecraft/world/item/FlintAndSteelItem.java
++++ b/net/minecraft/world/item/FlintAndSteelItem.java
+@@ -35,9 +34,17 @@
+ if (!CampfireBlock.canLight(blockstate) && !CandleBlock.canLight(blockstate) && !CandleCakeBlock.canLight(blockstate)) {
+ BlockPos blockpos1 = blockpos.relative(useoncontext.getClickedFace());
+
+- if (BaseFireBlock.canBePlacedAt(level, blockpos1, useoncontext.getHorizontalDirection())) {
+- level.playSound(player, blockpos1, SoundEvents.FLINTANDSTEEL_USE, SoundSource.BLOCKS, 1.0F, level.getRandom().nextFloat() * 0.4F + 0.8F);
+- BlockState blockstate1 = BaseFireBlock.getState(level, blockpos1);
++ if (BaseFireBlock.canBePlacedAt(world, blockposition1, context.getHorizontalDirection())) {
++ // CraftBukkit start - Store the clicked block
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(world, blockposition1, org.bukkit.event.block.BlockIgniteEvent.IgniteCause.FLINT_AND_STEEL, entityhuman).isCancelled()) {
++ context.getItemInHand().hurtAndBreak(1, entityhuman, (entityhuman1) -> {
++ entityhuman1.broadcastBreakEvent(context.getHand());
++ });
++ return InteractionResult.PASS;
++ }
++ // CraftBukkit end
++ world.playSound(entityhuman, blockposition1, SoundEvents.FLINTANDSTEEL_USE, SoundSource.BLOCKS, 1.0F, world.getRandom().nextFloat() * 0.4F + 0.8F);
++ IBlockData iblockdata1 = BaseFireBlock.getState(world, blockposition1);
+
+ level.setBlock(blockpos1, blockstate1, 11);
+ level.gameEvent((Entity) player, GameEvent.BLOCK_PLACE, blockpos);
+@@ -55,14 +62,21 @@
+ return InteractionResult.FAIL;
+ }
+ } else {
+- level.playSound(player, blockpos, SoundEvents.FLINTANDSTEEL_USE, SoundSource.BLOCKS, 1.0F, level.getRandom().nextFloat() * 0.4F + 0.8F);
+- level.setBlock(blockpos, (BlockState) blockstate.setValue(BlockStateProperties.LIT, true), 11);
+- level.gameEvent((Entity) player, GameEvent.BLOCK_CHANGE, blockpos);
+- if (player != null) {
+- useoncontext.getItemInHand().hurtAndBreak(1, player, (player1) -> {
+- player1.broadcastBreakEvent(useoncontext.getHand());
++ // CraftBukkit start - Store the clicked block
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(world, blockposition, org.bukkit.event.block.BlockIgniteEvent.IgniteCause.FLINT_AND_STEEL, entityhuman).isCancelled()) {
++ context.getItemInHand().hurtAndBreak(1, entityhuman, (entityhuman1) -> {
++ entityhuman1.broadcastBreakEvent(context.getHand());
+ });
+ }
++ // CraftBukkit end
++ world.playSound(entityhuman, blockposition, SoundEvents.FLINTANDSTEEL_USE, SoundSource.BLOCKS, 1.0F, world.getRandom().nextFloat() * 0.4F + 0.8F);
++ world.setBlock(blockposition, (IBlockData) iblockdata.setValue(BlockStateProperties.LIT, true), 11);
++ world.gameEvent((Entity) entityhuman, GameEvent.BLOCK_CHANGE, blockposition);
++ if (entityhuman != null) {
++ context.getItemInHand().hurtAndBreak(1, entityhuman, (entityhuman1) -> {
++ entityhuman1.broadcastBreakEvent(context.getHand());
++ });
++ }
+
+ return InteractionResult.sidedSuccess(level.isClientSide());
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/HangingEntityItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/HangingEntityItem.java.patch
new file mode 100644
index 0000000000..5cfdae7aa5
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/HangingEntityItem.java.patch
@@ -0,0 +1,36 @@
+--- a/net/minecraft/world/item/HangingEntityItem.java
++++ b/net/minecraft/world/item/HangingEntityItem.java
+@@ -22,6 +21,11 @@
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.gameevent.GameEvent;
+
++// CraftBukkit start
++import org.bukkit.entity.Player;
++import org.bukkit.event.hanging.HangingPlaceEvent;
++// CraftBukkit end
++
+ public class HangingEntityItem extends Item {
+
+ private static final Component TOOLTIP_RANDOM_VARIANT = Component.translatable("painting.random").withStyle(ChatFormatting.GRAY);
+@@ -72,7 +75,20 @@
+ }
+
+ if (((HangingEntity) object).survives()) {
+- if (!level.isClientSide) {
++ if (!world.isClientSide) {
++ // CraftBukkit start - fire HangingPlaceEvent
++ Player who = (context.getPlayer() == null) ? null : (Player) context.getPlayer().getBukkitEntity();
++ org.bukkit.block.Block blockClicked = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ org.bukkit.block.BlockFace blockFace = org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(enumdirection);
++ org.bukkit.inventory.EquipmentSlot hand = org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(context.getHand());
++
++ HangingPlaceEvent event = new HangingPlaceEvent((org.bukkit.entity.Hanging) ((HangingEntity) object).getBukkitEntity(), who, blockClicked, blockFace, hand, org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack));
++ world.getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return InteractionResult.FAIL;
++ }
++ // CraftBukkit end
+ ((HangingEntity) object).playPlacementSound();
+ level.gameEvent((Entity) player, GameEvent.ENTITY_PLACE, ((HangingEntity) object).position());
+ level.addFreshEntity((Entity) object);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/ItemStack.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/ItemStack.java.patch
new file mode 100644
index 0000000000..505db2a2b4
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/ItemStack.java.patch
@@ -0,0 +1,324 @@
+--- a/net/minecraft/world/item/ItemStack.java
++++ b/net/minecraft/world/item/ItemStack.java
+@@ -81,6 +82,41 @@
+ import net.minecraft.world.level.block.state.pattern.BlockInWorld;
+ import org.slf4j.Logger;
+
++// CraftBukkit start
++import com.mojang.serialization.Dynamic;
++import java.util.Map;
++import java.util.Objects;
++import net.minecraft.server.MinecraftServer;
++import net.minecraft.server.level.ServerLevel;
++import net.minecraft.server.level.ServerPlayer;
++import net.minecraft.sounds.SoundEvent;
++import net.minecraft.sounds.SoundSource;
++import net.minecraft.stats.Stats;
++import net.minecraft.util.datafix.fixes.DataConverterTypes;
++import net.minecraft.world.level.block.Blocks;
++import net.minecraft.world.level.block.SaplingBlock;
++import net.minecraft.world.level.block.SignBlock;
++import net.minecraft.world.level.block.SoundType;
++import net.minecraft.world.level.block.WitherSkullBlock;
++import net.minecraft.world.level.block.entity.BlockEntity;
++import net.minecraft.world.level.block.entity.JukeboxBlockEntity;
++import net.minecraft.world.level.block.entity.SignBlockEntity;
++import net.minecraft.world.level.block.entity.SkullBlockEntity;
++import net.minecraft.world.level.gameevent.GameEvent;
++import org.bukkit.Location;
++import org.bukkit.TreeType;
++import org.bukkit.block.BlockState;
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.craftbukkit.block.CraftBlockState;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.craftbukkit.util.CraftLocation;
++import org.bukkit.craftbukkit.util.CraftMagicNumbers;
++import org.bukkit.entity.Player;
++import org.bukkit.event.block.BlockFertilizeEvent;
++import org.bukkit.event.player.PlayerItemDamageEvent;
++import org.bukkit.event.world.StructureGrowEvent;
++// CraftBukkit end
++
+ public final class ItemStack {
+
+ public static final Codec<ItemStack> CODEC = RecordCodecBuilder.create((instance) -> {
+@@ -175,11 +211,22 @@
+ this.item = null;
+ }
+
+- private ItemStack(CompoundTag compoundtag) {
+- this.item = (Item) BuiltInRegistries.ITEM.get(new ResourceLocation(compoundtag.getString("id")));
+- this.count = compoundtag.getByte("Count");
+- if (compoundtag.contains("tag", 10)) {
+- this.tag = compoundtag.getCompound("tag").copy();
++ // Called to run this stack through the data converter to handle older storage methods and serialized items
++ public void convertStack(int version) {
++ if (0 < version && version < CraftMagicNumbers.INSTANCE.getDataVersion()) {
++ CompoundTag savedStack = new CompoundTag();
++ this.save(savedStack);
++ savedStack = (CompoundTag) MinecraftServer.getServer().fixerUpper.update(DataConverterTypes.ITEM_STACK, new Dynamic(NbtOps.INSTANCE, savedStack), version, CraftMagicNumbers.INSTANCE.getDataVersion()).getValue();
++ this.load(savedStack);
++ }
++ }
++
++ // CraftBukkit - break into own method
++ private void load(CompoundTag nbttagcompound) {
++ this.item = (Item) BuiltInRegistries.ITEM.get(new ResourceLocation(nbttagcompound.getString("id")));
++ this.count = nbttagcompound.getByte("Count");
++ if (nbttagcompound.contains("tag", 10)) {
++ this.tag = nbttagcompound.getCompound("tag").copy();
+ this.getItem().verifyTagAfterLoad(this.tag);
+ }
+
+@@ -189,7 +236,12 @@
+
+ }
+
+- public static ItemStack of(CompoundTag compoundtag) {
++ private ItemStack(CompoundTag compoundTag) {
++ this.load(compoundTag);
++ // CraftBukkit end
++ }
++
++ public static ItemStack of(CompoundTag compoundTag) {
+ try {
+ return new ItemStack(compoundtag);
+ } catch (RuntimeException runtimeexception) {
+@@ -266,13 +318,165 @@
+ return InteractionResult.PASS;
+ } else {
+ Item item = this.getItem();
+- InteractionResult interactionresult = item.useOn(useoncontext);
++ // CraftBukkit start - handle all block place event logic here
++ CompoundTag oldData = this.getTagClone();
++ int oldCount = this.getCount();
++ ServerLevel world = (ServerLevel) context.getLevel();
+
+ if (player != null && interactionresult.shouldAwardStats()) {
+ player.awardStat(Stats.ITEM_USED.get(item));
+ }
+
+- return interactionresult;
++ BlockFertilizeEvent fertilizeEvent = new BlockFertilizeEvent(CraftBlock.at(world, blockposition), (Player) entityhuman.getBukkitEntity(), (List< BlockState>) (List<? extends BlockState>) blocks);
++ fertilizeEvent.setCancelled(structureEvent != null && structureEvent.isCancelled());
++ org.bukkit.Bukkit.getPluginManager().callEvent(fertilizeEvent);
++
++ if (!fertilizeEvent.isCancelled()) {
++ // Change the stack to its new contents if it hasn't been tampered with.
++ if (this.getCount() == oldCount && Objects.equals(this.tag, oldData)) {
++ this.setTag(newData);
++ this.setCount(newCount);
++ }
++ for (CraftBlockState blockstate : blocks) {
++ world.setBlock(blockstate.getPosition(),blockstate.getHandle(), blockstate.getFlag()); // SPIGOT-7248 - manual update to avoid physics where appropriate
++ }
++ entityhuman.awardStat(Stats.ITEM_USED.get(item)); // SPIGOT-7236 - award stat
++ }
++
++ SignItem.openSign = null; // SPIGOT-6758 - Reset on early return
++ return enuminteractionresult;
++ }
++ world.captureTreeGeneration = false;
++
++ if (entityhuman != null && enuminteractionresult.shouldAwardStats()) {
++ EnumHand enumhand = context.getHand();
++ org.bukkit.event.block.BlockPlaceEvent placeEvent = null;
++ List<BlockState> blocks = new java.util.ArrayList<>(world.capturedBlockStates.values());
++ world.capturedBlockStates.clear();
++ if (blocks.size() > 1) {
++ placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(world, entityhuman, enumhand, blocks, blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ } else if (blocks.size() == 1) {
++ placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent(world, entityhuman, enumhand, blocks.get(0), blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ }
++
++ if (placeEvent != null && (placeEvent.isCancelled() || !placeEvent.canBuild())) {
++ enuminteractionresult = InteractionResult.FAIL; // cancel placement
++ // PAIL: Remove this when MC-99075 fixed
++ placeEvent.getPlayer().updateInventory();
++ // revert back all captured blocks
++ world.preventPoiUpdated = true; // CraftBukkit - SPIGOT-5710
++ for (BlockState blockstate : blocks) {
++ blockstate.update(true, false);
++ }
++ world.preventPoiUpdated = false;
++
++ // Brute force all possible updates
++ BlockPos placedPos = ((CraftBlock) placeEvent.getBlock()).getPosition();
++ for (Direction dir : Direction.values()) {
++ ((ServerPlayer) entityhuman).connection.send(new ClientboundBlockUpdatePacket(world, placedPos.relative(dir)));
++ }
++ SignItem.openSign = null; // SPIGOT-6758 - Reset on early return
++ } else {
++ // Change the stack to its new contents if it hasn't been tampered with.
++ if (this.getCount() == oldCount && Objects.equals(this.tag, oldData)) {
++ this.setTag(newData);
++ this.setCount(newCount);
++ }
++
++ for (Map.Entry<BlockPos, BlockEntity> e : world.capturedTileEntities.entrySet()) {
++ world.setBlockEntity(e.getValue());
++ }
++
++ for (BlockState blockstate : blocks) {
++ int updateFlag = ((CraftBlockState) blockstate).getFlag();
++ IBlockData oldBlock = ((CraftBlockState) blockstate).getHandle();
++ BlockPos newblockposition = ((CraftBlockState) blockstate).getPosition();
++ IBlockData block = world.getBlockState(newblockposition);
++
++ if (!(block.getBlock() instanceof BaseEntityBlock)) { // Containers get placed automatically
++ block.getBlock().onPlace(block, world, newblockposition, oldBlock, true);
++ }
++
++ world.notifyAndUpdatePhysics(newblockposition, null, oldBlock, block, world.getBlockState(newblockposition), updateFlag, 512); // send null chunk as chunk.k() returns false by this point
++ }
++
++ // Special case juke boxes as they update their tile entity. Copied from ItemRecord.
++ // PAIL: checkme on updates.
++ if (this.item instanceof RecordItem) {
++ BlockEntity tileentity = world.getBlockEntity(blockposition);
++
++ if (tileentity instanceof JukeboxBlockEntity) {
++ JukeboxBlockEntity tileentityjukebox = (JukeboxBlockEntity) tileentity;
++
++ // There can only be one
++ ItemStack record = this.copy();
++ if (!record.isEmpty()) {
++ record.setCount(1);
++ }
++
++ tileentityjukebox.setTheItem(record);
++ world.gameEvent(GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of(entityhuman, world.getBlockState(blockposition)));
++ }
++
++ this.shrink(1);
++ entityhuman.awardStat(Stats.PLAY_RECORD);
++ }
++
++ if (this.item == Items.WITHER_SKELETON_SKULL) { // Special case skulls to allow wither spawns to be cancelled
++ BlockPos bp = blockposition;
++ if (!world.getBlockState(blockposition).canBeReplaced()) {
++ if (!world.getBlockState(blockposition).isSolid()) {
++ bp = null;
++ } else {
++ bp = bp.relative(context.getClickedFace());
++ }
++ }
++ if (bp != null) {
++ BlockEntity te = world.getBlockEntity(bp);
++ if (te instanceof SkullBlockEntity) {
++ WitherSkullBlock.checkSpawn(world, bp, (SkullBlockEntity) te);
++ }
++ }
++ }
++
++ // SPIGOT-4678
++ if (this.item instanceof SignItem && SignItem.openSign != null) {
++ try {
++ if (world.getBlockEntity(SignItem.openSign) instanceof SignBlockEntity tileentitysign) {
++ if (world.getBlockState(SignItem.openSign).getBlock() instanceof SignBlock blocksign) {
++ blocksign.openTextEdit(entityhuman, tileentitysign, true, org.bukkit.event.player.PlayerSignOpenEvent.Cause.PLACE); // Craftbukkit
++ }
++ }
++ } finally {
++ SignItem.openSign = null;
++ }
++ }
++
++ // SPIGOT-7315: Moved from BlockBed#setPlacedBy
++ if (placeEvent != null && this.item instanceof BedItem) {
++ BlockPos position = ((CraftBlock) placeEvent.getBlock()).getPosition();
++ IBlockData blockData = world.getBlockState(position);
++
++ if (blockData.getBlock() instanceof BedBlock) {
++ world.blockUpdated(position, Blocks.AIR);
++ blockData.updateNeighbourShapes(world, position, 3);
++ }
++ }
++
++ // SPIGOT-1288 - play sound stripped from ItemBlock
++ if (this.item instanceof BlockItem) {
++ SoundType soundeffecttype = ((BlockItem) this.item).getBlock().defaultBlockState().getSoundType(); // TODO: not strictly correct, however currently only affects decorated pots
++ world.playSound(entityhuman, blockposition, soundeffecttype.getPlaceSound(), SoundSource.BLOCKS, (soundeffecttype.getVolume() + 1.0F) / 2.0F, soundeffecttype.getPitch() * 0.8F);
++ }
++
++ entityhuman.awardStat(Stats.ITEM_USED.get(item));
++ }
++ }
++ world.capturedTileEntities.clear();
++ world.capturedBlockStates.clear();
++ // CraftBukkit end
++
++ return enuminteractionresult;
+ }
+ }
+
+@@ -350,8 +581,23 @@
+ }
+ }
+
+- i -= k;
+- if (i <= 0) {
++ amount -= k;
++ // CraftBukkit start
++ if (user != null) {
++ PlayerItemDamageEvent event = new PlayerItemDamageEvent(user.getBukkitEntity(), CraftItemStack.asCraftMirror(this), amount);
++ event.getPlayer().getServer().getPluginManager().callEvent(event);
++
++ if (amount != event.getDamage() || event.isCancelled()) {
++ event.getPlayer().updateInventory();
++ }
++ if (event.isCancelled()) {
++ return false;
++ }
++
++ amount = event.getDamage();
++ }
++ // CraftBukkit end
++ if (amount <= 0) {
+ return false;
+ }
+ }
+@@ -372,6 +618,11 @@
+ if (this.hurt(i, t0.getRandom(), t0 instanceof ServerPlayer ? (ServerPlayer) t0 : null)) {
+ consumer.accept(t0);
+ Item item = this.getItem();
++ // CraftBukkit start - Check for item breaking
++ if (this.count == 1 && entity instanceof net.minecraft.world.entity.player.Player) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemBreakEvent((net.minecraft.world.entity.player.Player) entity, this);
++ }
++ // CraftBukkit end
+
+ this.shrink(1);
+ if (t0 instanceof Player) {
+@@ -525,6 +775,17 @@
+ return this.tag;
+ }
+
++ // CraftBukkit start
++ @Nullable
++ private CompoundTag getTagClone() {
++ return this.tag == null ? null : this.tag.copy();
++ }
++
++ private void setTagClone(@Nullable CompoundTag nbtttagcompound) {
++ this.setTag(nbtttagcompound == null ? null : nbtttagcompound.copy());
++ }
++ // CraftBukkit end
++
+ public CompoundTag getOrCreateTag() {
+ if (this.tag == null) {
+ this.setTag(new CompoundTag());
+@@ -949,6 +1210,13 @@
+ listtag.add(compoundtag);
+ }
+
++ // CraftBukkit start
++ @Deprecated
++ public void setItem(Item item) {
++ this.item = item;
++ }
++ // CraftBukkit end
++
+ public Component getDisplayName() {
+ MutableComponent mutablecomponent = Component.empty().append(this.getHoverName());
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/LeadItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/LeadItem.java.patch
new file mode 100644
index 0000000000..875c3ae2e9
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/LeadItem.java.patch
@@ -0,0 +1,82 @@
+--- a/net/minecraft/world/item/LeadItem.java
++++ b/net/minecraft/world/item/LeadItem.java
+@@ -14,6 +14,10 @@
+ import net.minecraft.world.level.block.state.BlockState;
+ import net.minecraft.world.level.gameevent.GameEvent;
+ import net.minecraft.world.phys.AABB;
++// CraftBukkit start
++import org.bukkit.craftbukkit.CraftEquipmentSlot;
++import org.bukkit.event.hanging.HangingPlaceEvent;
++// CraftBukkit end
+
+ public class LeadItem extends Item {
+
+@@ -31,8 +34,8 @@
+ if (blockstate.is(BlockTags.FENCES)) {
+ Player player = useoncontext.getPlayer();
+
+- if (!level.isClientSide && player != null) {
+- bindPlayerMobs(player, level, blockpos);
++ if (!world.isClientSide && entityhuman != null) {
++ bindPlayerMobs(entityhuman, world, blockposition, context.getHand()); // CraftBukkit - Pass hand
+ }
+
+ return InteractionResult.sidedSuccess(level.isClientSide);
+@@ -41,8 +44,8 @@
+ }
+ }
+
+- public static InteractionResult bindPlayerMobs(Player player, Level level, BlockPos blockpos) {
+- LeashFenceKnotEntity leashfenceknotentity = null;
++ public static InteractionResult bindPlayerMobs(Player entityhuman, Level world, BlockPos blockposition, net.minecraft.world.EnumHand enumhand) { // CraftBukkit - Add EnumHand
++ LeashFenceKnotEntity entityleash = null;
+ boolean flag = false;
+ double d0 = 7.0D;
+ int i = blockpos.getX();
+@@ -54,13 +57,30 @@
+ while (iterator.hasNext()) {
+ Mob mob = (Mob) iterator.next();
+
+- if (mob.getLeashHolder() == player) {
+- if (leashfenceknotentity == null) {
+- leashfenceknotentity = LeashFenceKnotEntity.getOrCreateKnot(level, blockpos);
+- leashfenceknotentity.playPlacementSound();
++ if (entityinsentient.getLeashHolder() == entityhuman) {
++ if (entityleash == null) {
++ entityleash = LeashFenceKnotEntity.getOrCreateKnot(world, blockposition);
++
++ // CraftBukkit start - fire HangingPlaceEvent
++ org.bukkit.inventory.EquipmentSlot hand = CraftEquipmentSlot.getHand(enumhand);
++ HangingPlaceEvent event = new HangingPlaceEvent((org.bukkit.entity.Hanging) entityleash.getBukkitEntity(), entityhuman != null ? (org.bukkit.entity.Player) entityhuman.getBukkitEntity() : null, world.getWorld().getBlockAt(i, j, k), org.bukkit.block.BlockFace.SELF, hand);
++ world.getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ entityleash.discard();
++ return InteractionResult.PASS;
++ }
++ // CraftBukkit end
++ entityleash.playPlacementSound();
+ }
+
+- mob.setLeashedTo(leashfenceknotentity, true);
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerLeashEntityEvent(entityinsentient, entityleash, entityhuman, enumhand).isCancelled()) {
++ continue;
++ }
++ // CraftBukkit end
++
++ entityinsentient.setLeashedTo(entityleash, true);
+ flag = true;
+ }
+ }
+@@ -71,4 +91,10 @@
+
+ return flag ? InteractionResult.SUCCESS : InteractionResult.PASS;
+ }
++
++ // CraftBukkit start
++ public static InteractionResult bindPlayerMobs(Player player, Level level, BlockPos pos) {
++ return bindPlayerMobs(player, level, pos, net.minecraft.world.EnumHand.MAIN_HAND);
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/MapItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/MapItem.java.patch
new file mode 100644
index 0000000000..547eacc615
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/MapItem.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/item/MapItem.java
++++ b/net/minecraft/world/item/MapItem.java
+@@ -69,7 +69,7 @@
+ public static Integer getMapId(ItemStack itemstack) {
+ CompoundTag compoundtag = itemstack.getTag();
+
+- return compoundtag != null && compoundtag.contains("map", 99) ? compoundtag.getInt("map") : null;
++ return nbttagcompound != null && nbttagcompound.contains("map", 99) ? nbttagcompound.getInt("map") : -1; // CraftBukkit - make new maps for no tag
+ }
+
+ private static int createNewSavedData(Level level, int i, int j, int k, boolean flag, boolean flag1, ResourceKey<Level> resourcekey) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/MilkBucketItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/MilkBucketItem.java.patch
new file mode 100644
index 0000000000..9319057302
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/MilkBucketItem.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/item/MilkBucketItem.java
++++ b/net/minecraft/world/item/MilkBucketItem.java
+@@ -32,7 +31,7 @@
+ }
+
+ if (!level.isClientSide) {
+- livingentity.removeAllEffects();
++ entityLiving.removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.MILK); // CraftBukkit
+ }
+
+ return itemstack.isEmpty() ? new ItemStack(Items.BUCKET) : itemstack;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/MinecartItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/MinecartItem.java.patch
new file mode 100644
index 0000000000..9f82c85999
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/MinecartItem.java.patch
@@ -0,0 +1,73 @@
+--- a/net/minecraft/world/item/MinecartItem.java
++++ b/net/minecraft/world/item/MinecartItem.java
+@@ -18,6 +18,11 @@
+ import net.minecraft.world.level.block.state.properties.RailShape;
+ import net.minecraft.world.level.gameevent.GameEvent;
+ import net.minecraft.world.phys.Vec3;
++// CraftBukkit start
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.block.BlockDispenseEvent;
++// CraftBukkit end
+
+ public class MinecartItem extends Item {
+
+@@ -59,10 +63,39 @@
+ }
+ }
+
+- AbstractMinecart abstractminecart = AbstractMinecart.createMinecart(serverlevel, d0, d1 + d3, d2, ((MinecartItem) itemstack.getItem()).type, itemstack, (Player) null);
++ // CraftBukkit start
++ // EntityMinecartAbstract entityminecartabstract = EntityMinecartAbstract.createMinecart(worldserver, d0, d1 + d3, d2, ((ItemMinecart) itemstack.getItem()).type);
++ ItemStack itemstack1 = itemstack.split(1);
++ org.bukkit.block.Block block2 = CraftBlock.at(worldserver, sourceblock.pos());
++ CraftItemStack craftItem = CraftItemStack.asCraftMirror(itemstack1);
+
+- serverlevel.addFreshEntity(abstractminecart);
+- itemstack.shrink(1);
++ BlockDispenseEvent event = new BlockDispenseEvent(block2, craftItem.clone(), new org.bukkit.util.Vector(d0, d1 + d3, d2));
++ if (!DispenserBlock.eventFired) {
++ worldserver.getCraftServer().getPluginManager().callEvent(event);
++ }
++
++ if (event.isCancelled()) {
++ itemstack.grow(1);
++ return itemstack;
++ }
++
++ if (!event.getItem().equals(craftItem)) {
++ itemstack.grow(1);
++ // Chain to handler for new item
++ ItemStack eventStack = CraftItemStack.asNMSCopy(event.getItem());
++ DispenseItemBehavior idispensebehavior = (DispenseItemBehavior) DispenserBlock.DISPENSER_REGISTRY.get(eventStack.getItem());
++ if (idispensebehavior != DispenseItemBehavior.NOOP && idispensebehavior != this) {
++ idispensebehavior.dispense(sourceblock, eventStack);
++ return itemstack;
++ }
++ }
++
++ itemstack1 = CraftItemStack.asNMSCopy(event.getItem());
++ AbstractMinecart entityminecartabstract = AbstractMinecart.createMinecart(worldserver, event.getVelocity().getX(), event.getVelocity().getY(), event.getVelocity().getZ(), ((MinecartItem) itemstack1.getItem()).type, itemstack1, (Player) null);
++
++ if (!worldserver.addFreshEntity(entityminecartabstract)) itemstack.grow(1);
++ // itemstack.shrink(1); // CraftBukkit - handled during event processing
++ // CraftBukkit end
+ return itemstack;
+ }
+
+@@ -103,8 +134,13 @@
+
+ AbstractMinecart abstractminecart = AbstractMinecart.createMinecart(serverlevel, (double) blockpos.getX() + 0.5D, (double) blockpos.getY() + 0.0625D + d0, (double) blockpos.getZ() + 0.5D, this.type, itemstack, useoncontext.getPlayer());
+
+- serverlevel.addFreshEntity(abstractminecart);
+- serverlevel.gameEvent(GameEvent.ENTITY_PLACE, blockpos, GameEvent.Context.of(useoncontext.getPlayer(), serverlevel.getBlockState(blockpos.below())));
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityPlaceEvent(context, entityminecartabstract).isCancelled()) {
++ return InteractionResult.FAIL;
++ }
++ // CraftBukkit end
++ if (!worldserver.addFreshEntity(entityminecartabstract)) return InteractionResult.PASS; // CraftBukkit
++ worldserver.gameEvent(GameEvent.ENTITY_PLACE, blockposition, GameEvent.Context.of(context.getPlayer(), worldserver.getBlockState(blockposition.below())));
+ }
+
+ itemstack.shrink(1);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/PotionItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/PotionItem.java.patch
new file mode 100644
index 0000000000..efd70f391c
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/PotionItem.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/item/PotionItem.java
++++ b/net/minecraft/world/item/PotionItem.java
+@@ -62,7 +60,7 @@
+ if (mobeffectinstance.getEffect().isInstantenous()) {
+ mobeffectinstance.getEffect().applyInstantenousEffect(player, player, livingentity, mobeffectinstance.getAmplifier(), 1.0D);
+ } else {
+- livingentity.addEffect(new MobEffectInstance(mobeffectinstance));
++ entityLiving.addEffect(new MobEffectInstance(mobeffect), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.POTION_DRINK); // CraftBukkit
+ }
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/RecordItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/RecordItem.java.patch
new file mode 100644
index 0000000000..7e377a438b
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/RecordItem.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/world/item/RecordItem.java
++++ b/net/minecraft/world/item/RecordItem.java
+@@ -46,9 +45,10 @@
+ if (blockstate.is(Blocks.JUKEBOX) && !(Boolean) blockstate.getValue(JukeboxBlock.HAS_RECORD)) {
+ ItemStack itemstack = useoncontext.getItemInHand();
+
+- if (!level.isClientSide) {
+- Player player = useoncontext.getPlayer();
+- BlockEntity blockentity = level.getBlockEntity(blockpos);
++ if (!world.isClientSide) {
++ if (true) return InteractionResult.SUCCESS; // CraftBukkit - handled in ItemStack
++ Player entityhuman = context.getPlayer();
++ BlockEntity tileentity = world.getBlockEntity(blockposition);
+
+ if (blockentity instanceof JukeboxBlockEntity) {
+ JukeboxBlockEntity jukeboxblockentity = (JukeboxBlockEntity) blockentity;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/SignItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/SignItem.java.patch
new file mode 100644
index 0000000000..51e2e94845
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/SignItem.java.patch
@@ -0,0 +1,27 @@
+--- a/net/minecraft/world/item/SignItem.java
++++ b/net/minecraft/world/item/SignItem.java
+@@ -13,8 +13,10 @@
+
+ public class SignItem extends StandingAndWallBlockItem {
+
+- public SignItem(Item.Properties item_properties, Block block, Block block1) {
+- super(block, block1, item_properties, Direction.DOWN);
++ public static BlockPos openSign; // CraftBukkit
++
++ public SignItem(Item.Properties properties, Block standingBlock, Block wallBlock) {
++ super(standingBlock, wallBlock, properties, Direction.DOWN);
+ }
+
+ public SignItem(Item.Properties item_properties, Block block, Block block1, Direction direction) {
+@@ -36,7 +37,10 @@
+ if (block instanceof SignBlock) {
+ SignBlock signblock = (SignBlock) block;
+
+- signblock.openTextEdit(player, signblockentity, true);
++ // CraftBukkit start - SPIGOT-4678
++ // blocksign.openTextEdit(entityhuman, tileentitysign, true);
++ SignItem.openSign = pos;
++ // CraftBukkit end
+ }
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/SnowballItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/SnowballItem.java.patch
new file mode 100644
index 0000000000..9a0cd1a7ee
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/SnowballItem.java.patch
@@ -0,0 +1,26 @@
+--- a/net/minecraft/world/item/SnowballItem.java
++++ b/net/minecraft/world/item/SnowballItem.java
+@@ -20,7 +19,8 @@
+ public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand interactionhand) {
+ ItemStack itemstack = player.getItemInHand(interactionhand);
+
+- level.playSound((Player) null, player.getX(), player.getY(), player.getZ(), SoundEvents.SNOWBALL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F));
++ // CraftBukkit - moved down
++ // world.playSound((EntityHuman) null, entityhuman.getX(), entityhuman.getY(), entityhuman.getZ(), SoundEffects.SNOWBALL_THROW, SoundCategory.NEUTRAL, 0.5F, 0.4F / (world.getRandom().nextFloat() * 0.4F + 0.8F));
+ if (!level.isClientSide) {
+ Snowball snowball = new Snowball(level, player);
+
+@@ -28,9 +36,12 @@
+ snowball.shootFromRotation(player, player.getXRot(), player.getYRot(), 0.0F, 1.5F, 1.0F);
+ level.addFreshEntity(snowball);
+ }
++ // CraftBukkit end
+
+ player.awardStat(Stats.ITEM_USED.get(this));
+- if (!player.getAbilities().instabuild) {
++ // CraftBukkit start - moved up
++ /*
++ if (!entityhuman.getAbilities().instabuild) {
+ itemstack.shrink(1);
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/SpawnEggItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/SpawnEggItem.java.patch
new file mode 100644
index 0000000000..87e1849bfc
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/SpawnEggItem.java.patch
@@ -0,0 +1,17 @@
+--- a/net/minecraft/world/item/SpawnEggItem.java
++++ b/net/minecraft/world/item/SpawnEggItem.java
+@@ -182,10 +179,10 @@
+ if (!((Mob) object).isBaby()) {
+ return Optional.empty();
+ } else {
+- ((Mob) object).moveTo(vec3.x(), vec3.y(), vec3.z(), 0.0F, 0.0F);
+- serverlevel.addFreshEntityWithPassengers((Entity) object);
+- if (itemstack.hasCustomHoverName()) {
+- ((Mob) object).setCustomName(itemstack.getHoverName());
++ ((Mob) object).moveTo(pos.x(), pos.y(), pos.z(), 0.0F, 0.0F);
++ serverLevel.addFreshEntityWithPassengers((Entity) object, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG); // CraftBukkit
++ if (stack.hasCustomHoverName()) {
++ ((Mob) object).setCustomName(stack.getHoverName());
+ }
+
+ if (!player.getAbilities().instabuild) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/StandingAndWallBlockItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/StandingAndWallBlockItem.java.patch
new file mode 100644
index 0000000000..930370c442
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/StandingAndWallBlockItem.java.patch
@@ -0,0 +1,34 @@
+--- a/net/minecraft/world/item/StandingAndWallBlockItem.java
++++ b/net/minecraft/world/item/StandingAndWallBlockItem.java
+@@ -10,6 +11,10 @@
+ import net.minecraft.world.level.block.Block;
+ import net.minecraft.world.level.block.state.BlockState;
+ import net.minecraft.world.phys.shapes.CollisionContext;
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.craftbukkit.block.data.CraftBlockData;
++import org.bukkit.event.block.BlockCanBuildEvent;
++// CraftBukkit end
+
+ public class StandingAndWallBlockItem extends BlockItem {
+
+@@ -50,7 +54,19 @@
+ }
+ }
+
+- return blockstate1 != null && level.isUnobstructed(blockstate1, blockpos, CollisionContext.empty()) ? blockstate1 : null;
++ // CraftBukkit start
++ if (iblockdata1 != null) {
++ boolean defaultReturn = world.isUnobstructed(iblockdata1, blockposition, CollisionContext.empty());
++ org.bukkit.entity.Player player = (context.getPlayer() instanceof ServerPlayer) ? (org.bukkit.entity.Player) context.getPlayer().getBukkitEntity() : null;
++
++ BlockCanBuildEvent event = new BlockCanBuildEvent(CraftBlock.at(world, blockposition), player, CraftBlockData.fromData(iblockdata1), defaultReturn);
++ context.getLevel().getCraftServer().getPluginManager().callEvent(event);
++
++ return (event.isBuildable()) ? iblockdata1 : null;
++ } else {
++ return null;
++ }
++ // CraftBukkit end
+ }
+
+ @Override
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/TridentItem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/TridentItem.java.patch
new file mode 100644
index 0000000000..8a451c85af
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/TridentItem.java.patch
@@ -0,0 +1,63 @@
+--- a/net/minecraft/world/item/TridentItem.java
++++ b/net/minecraft/world/item/TridentItem.java
+@@ -72,8 +68,10 @@
+
+ if (k <= 0 || player.isInWaterOrRain()) {
+ if (!level.isClientSide) {
+- itemstack.hurtAndBreak(1, player, (player1) -> {
+- player1.broadcastBreakEvent(livingentity.getUsedItemHand());
++ // CraftBukkit - moved down
++ /*
++ itemstack.hurtAndBreak(1, entityhuman, (entityhuman1) -> {
++ entityhuman1.broadcastBreakEvent(entityliving.getUsedItemHand());
+ });
+ if (k == 0) {
+ ThrownTrident throwntrident = new ThrownTrident(level, player, itemstack);
+@@ -83,18 +82,41 @@
+ throwntrident.pickup = AbstractArrow.Pickup.CREATIVE_ONLY;
+ }
+
+- level.addFreshEntity(throwntrident);
+- level.playSound((Player) null, (Entity) throwntrident, SoundEvents.TRIDENT_THROW, SoundSource.PLAYERS, 1.0F, 1.0F);
+- if (!player.getAbilities().instabuild) {
+- player.getInventory().removeItem(itemstack);
++ // CraftBukkit start
++ if (!level.addFreshEntity(entitythrowntrident)) {
++ if (entityhuman instanceof net.minecraft.server.level.ServerPlayer) {
++ ((net.minecraft.server.level.ServerPlayer) entityhuman).getBukkitEntity().updateInventory();
++ }
++ return;
+ }
++
++ stack.hurtAndBreak(1, entityhuman, (entityhuman1) -> {
++ entityhuman1.broadcastBreakEvent(entityLiving.getUsedItemHand());
++ });
++ entitythrowntrident.pickupItemStack = stack.copy(); // SPIGOT-4511 update since damage call moved
++ // CraftBukkit end
++
++ level.playSound((Player) null, (Entity) entitythrowntrident, SoundEvents.TRIDENT_THROW, SoundSource.PLAYERS, 1.0F, 1.0F);
++ if (!entityhuman.getAbilities().instabuild) {
++ entityhuman.getInventory().removeItem(stack);
++ }
++ // CraftBukkit start - SPIGOT-5458 also need in this branch :(
++ } else {
++ stack.hurtAndBreak(1, entityhuman, (entityhuman1) -> {
++ entityhuman1.broadcastBreakEvent(entityLiving.getUsedItemHand());
++ });
++ // CraftBukkkit end
+ }
+ }
+
+ player.awardStat(Stats.ITEM_USED.get(this));
+ if (k > 0) {
+- float f = player.getYRot();
+- float f1 = player.getXRot();
++ // CraftBukkit start
++ org.bukkit.event.player.PlayerRiptideEvent event = new org.bukkit.event.player.PlayerRiptideEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack));
++ event.getPlayer().getServer().getPluginManager().callEvent(event);
++ // CraftBukkit end
++ float f = entityhuman.getYRot();
++ float f1 = entityhuman.getXRot();
+ float f2 = -Mth.sin(f * 0.017453292F) * Mth.cos(f1 * 0.017453292F);
+ float f3 = -Mth.sin(f1 * 0.017453292F);
+ float f4 = Mth.cos(f * 0.017453292F) * Mth.cos(f1 * 0.017453292F);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/BlastingRecipe.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/BlastingRecipe.java.patch
new file mode 100644
index 0000000000..c9f9c5f2be
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/BlastingRecipe.java.patch
@@ -0,0 +1,35 @@
+--- a/net/minecraft/world/item/crafting/BlastingRecipe.java
++++ b/net/minecraft/world/item/crafting/BlastingRecipe.java
+@@ -3,6 +3,14 @@
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.level.block.Blocks;
+
++// CraftBukkit start
++import org.bukkit.NamespacedKey;
++import org.bukkit.craftbukkit.inventory.CraftBlastingRecipe;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.craftbukkit.inventory.CraftRecipe;
++import org.bukkit.inventory.Recipe;
++// CraftBukkit end
++
+ public class BlastingRecipe extends AbstractCookingRecipe {
+
+ public BlastingRecipe(String s, CookingBookCategory cookingbookcategory, Ingredient ingredient, ItemStack itemstack, float f, int i) {
+@@ -20,4 +26,17 @@
+ public RecipeSerializer<?> getSerializer() {
+ return RecipeSerializer.BLASTING_RECIPE;
+ }
++
++ // CraftBukkit start
++ @Override
++ public Recipe toBukkitRecipe(NamespacedKey id) {
++ CraftItemStack result = CraftItemStack.asCraftMirror(this.result);
++
++ CraftBlastingRecipe recipe = new CraftBlastingRecipe(id, result, CraftRecipe.toBukkit(this.ingredient), this.experience, this.cookingTime);
++ recipe.setGroup(this.group);
++ recipe.setCategory(CraftRecipe.getCategory(this.category()));
++
++ return recipe;
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/CampfireCookingRecipe.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/CampfireCookingRecipe.java.patch
new file mode 100644
index 0000000000..fb81e72c5b
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/CampfireCookingRecipe.java.patch
@@ -0,0 +1,35 @@
+--- a/net/minecraft/world/item/crafting/CampfireCookingRecipe.java
++++ b/net/minecraft/world/item/crafting/CampfireCookingRecipe.java
+@@ -3,6 +3,14 @@
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.level.block.Blocks;
+
++// CraftBukkit start
++import org.bukkit.NamespacedKey;
++import org.bukkit.craftbukkit.inventory.CraftCampfireRecipe;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.craftbukkit.inventory.CraftRecipe;
++import org.bukkit.inventory.Recipe;
++// CraftBukkit end
++
+ public class CampfireCookingRecipe extends AbstractCookingRecipe {
+
+ public CampfireCookingRecipe(String s, CookingBookCategory cookingbookcategory, Ingredient ingredient, ItemStack itemstack, float f, int i) {
+@@ -20,4 +26,17 @@
+ public RecipeSerializer<?> getSerializer() {
+ return RecipeSerializer.CAMPFIRE_COOKING_RECIPE;
+ }
++
++ // CraftBukkit start
++ @Override
++ public Recipe toBukkitRecipe(NamespacedKey id) {
++ CraftItemStack result = CraftItemStack.asCraftMirror(this.result);
++
++ CraftCampfireRecipe recipe = new CraftCampfireRecipe(id, result, CraftRecipe.toBukkit(this.ingredient), this.experience, this.cookingTime);
++ recipe.setGroup(this.group);
++ recipe.setCategory(CraftRecipe.getCategory(this.category()));
++
++ return recipe;
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/CustomRecipe.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/CustomRecipe.java.patch
new file mode 100644
index 0000000000..e48edf6f3c
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/CustomRecipe.java.patch
@@ -0,0 +1,26 @@
+--- a/net/minecraft/world/item/crafting/CustomRecipe.java
++++ b/net/minecraft/world/item/crafting/CustomRecipe.java
+@@ -3,7 +3,10 @@
+ import net.minecraft.core.RegistryAccess;
+ import net.minecraft.world.item.ItemStack;
+
+-public abstract class CustomRecipe implements CraftingRecipe {
++// CraftBukkit start
++import org.bukkit.NamespacedKey;
++import org.bukkit.inventory.Recipe;
++// CraftBukkit end
+
+ private final CraftingBookCategory category;
+
+@@ -28,4 +30,11 @@
+ public CraftingBookCategory category() {
+ return this.category;
+ }
++
++ // CraftBukkit start
++ @Override
++ public Recipe toBukkitRecipe(NamespacedKey id) {
++ return new org.bukkit.craftbukkit.inventory.CraftComplexRecipe(id, this);
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/Ingredient.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/Ingredient.java.patch
new file mode 100644
index 0000000000..169cb8b3c7
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/Ingredient.java.patch
@@ -0,0 +1,28 @@
+--- a/net/minecraft/world/item/crafting/Ingredient.java
++++ b/net/minecraft/world/item/crafting/Ingredient.java
+@@ -35,6 +35,7 @@
+ private ItemStack[] itemStacks;
+ @Nullable
+ private IntList stackingIds;
++ public boolean exact; // CraftBukkit
+ public static final Codec<Ingredient> CODEC = codec(true);
+ public static final Codec<Ingredient> CODEC_NONEMPTY = codec(false);
+
+@@ -73,7 +73,16 @@
+ for (int j = 0; j < i; ++j) {
+ ItemStack itemstack1 = aitemstack[j];
+
+- if (itemstack1.is(itemstack.getItem())) {
++ // CraftBukkit start
++ if (exact) {
++ if (itemstack1.getItem() == stack.getItem() && ItemStack.isSameItemSameTags(stack, itemstack1)) {
++ return true;
++ }
++
++ continue;
++ }
++ // CraftBukkit end
++ if (itemstack1.is(stack.getItem())) {
+ return true;
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/Recipe.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/Recipe.java.patch
new file mode 100644
index 0000000000..d7cfffbac3
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/Recipe.java.patch
@@ -0,0 +1,9 @@
+--- a/net/minecraft/world/item/crafting/Recipe.java
++++ b/net/minecraft/world/item/crafting/Recipe.java
+@@ -67,4 +67,6 @@
+ return ingredient.getItems().length == 0;
+ });
+ }
++
++ org.bukkit.inventory.Recipe toBukkitRecipe(org.bukkit.NamespacedKey id); // CraftBukkit
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/RecipeHolder.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/RecipeHolder.java.patch
new file mode 100644
index 0000000000..35a86ff378
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/RecipeHolder.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/item/crafting/RecipeHolder.java
++++ b/net/minecraft/world/item/crafting/RecipeHolder.java
+@@ -1,10 +1,17 @@
+ package net.minecraft.world.item.crafting;
+
+ import net.minecraft.resources.ResourceLocation;
++// CraftBukkit start
++import org.bukkit.craftbukkit.util.CraftNamespacedKey;
++import org.bukkit.inventory.Recipe;
+
+ public record RecipeHolder<T extends Recipe<?>> (ResourceLocation id, T value) {
+
+- @Override
++ public final Recipe toBukkitRecipe() {
++ return this.value.toBukkitRecipe(CraftNamespacedKey.fromMinecraft(this.id));
++ }
++ // CraftBukkit end
++
+ public boolean equals(Object object) {
+ if (this == object) {
+ return true;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/RecipeManager.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/RecipeManager.java.patch
new file mode 100644
index 0000000000..764085dcde
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/RecipeManager.java.patch
@@ -0,0 +1,161 @@
+--- a/net/minecraft/world/item/crafting/RecipeManager.java
++++ b/net/minecraft/world/item/crafting/RecipeManager.java
+@@ -24,6 +24,15 @@
+ import javax.annotation.Nullable;
+ import net.minecraft.Util;
+ import net.minecraft.core.NonNullList;
++import net.minecraft.world.Container;
++import net.minecraft.world.item.ItemStack;
++import net.minecraft.world.level.Level;
++import org.slf4j.Logger;
++
++// CraftBukkit start
++import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
++import net.minecraft.core.registries.BuiltInRegistries;
++// CraftBukkit end
+ import net.minecraft.resources.ResourceLocation;
+ import net.minecraft.server.packs.resources.ResourceManager;
+ import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener;
+@@ -38,7 +43,7 @@
+
+ private static final Gson GSON = (new GsonBuilder()).setPrettyPrinting().disableHtmlEscaping().create();
+ private static final Logger LOGGER = LogUtils.getLogger();
+- private Map<RecipeType<?>, Map<ResourceLocation, RecipeHolder<?>>> recipes = ImmutableMap.of();
++ public Map<RecipeType<?>, Object2ObjectLinkedOpenHashMap<ResourceLocation, RecipeHolder<?>>> recipes = ImmutableMap.of(); // CraftBukkit
+ private Map<ResourceLocation, RecipeHolder<?>> byName = ImmutableMap.of();
+ private boolean hasErrors;
+
+@@ -49,7 +53,12 @@
+ @Override
+ protected void apply(Map<ResourceLocation, JsonElement> map, ResourceManager resourcemanager, ProfilerFiller profilerfiller) {
+ this.hasErrors = false;
+- Map<RecipeType<?>, Builder<ResourceLocation, RecipeHolder<?>>> map1 = Maps.newHashMap();
++ // CraftBukkit start - SPIGOT-5667 make sure all types are populated and mutable
++ Map<RecipeType<?>, Object2ObjectLinkedOpenHashMap<ResourceLocation, RecipeHolder<?>>> map1 = Maps.newHashMap();
++ for (RecipeType<?> recipeType : BuiltInRegistries.RECIPE_TYPE) {
++ map1.put(recipeType, new Object2ObjectLinkedOpenHashMap<>());
++ }
++ // CraftBukkit end
+ Builder<ResourceLocation, RecipeHolder<?>> builder = ImmutableMap.builder();
+ Iterator iterator = map.entrySet().iterator();
+
+@@ -60,30 +69,49 @@
+ try {
+ RecipeHolder<?> recipeholder = fromJson(resourcelocation, GsonHelper.convertToJsonObject((JsonElement) entry.getValue(), "top element"));
+
+- ((Builder) map1.computeIfAbsent(recipeholder.value().getType(), (recipetype) -> {
+- return ImmutableMap.builder();
+- })).put(resourcelocation, recipeholder);
+- builder.put(resourcelocation, recipeholder);
++ // CraftBukkit start
++ (map1.computeIfAbsent(recipeholder.value().getType(), (recipes) -> {
++ return new Object2ObjectLinkedOpenHashMap<>();
++ // CraftBukkit end
++ })).put(minecraftkey, recipeholder);
++ builder.put(minecraftkey, recipeholder);
+ } catch (IllegalArgumentException | JsonParseException jsonparseexception) {
+ RecipeManager.LOGGER.error("Parsing error loading recipe {}", resourcelocation, jsonparseexception);
+ }
+ }
+
+ this.recipes = (Map) map1.entrySet().stream().collect(ImmutableMap.toImmutableMap(Entry::getKey, (entry1) -> {
+- return ((Builder) entry1.getValue()).build();
++ return (entry1.getValue()); // CraftBukkit
+ }));
+- this.byName = builder.build();
++ this.byName = Maps.newHashMap(builder.build()); // CraftBukkit
+ RecipeManager.LOGGER.info("Loaded {} recipes", map1.size());
+ }
+
++ // CraftBukkit start
++ public void addRecipe(RecipeHolder<?> irecipe) {
++ Object2ObjectLinkedOpenHashMap<ResourceLocation, RecipeHolder<?>> map = this.recipes.get(irecipe.value().getType()); // CraftBukkit
++
++ if (byName.containsKey(irecipe.id()) || map.containsKey(irecipe.id())) {
++ throw new IllegalStateException("Duplicate recipe ignored with ID " + irecipe.id());
++ } else {
++ map.putAndMoveToFirst(irecipe.id(), irecipe); // CraftBukkit - SPIGOT-4638: last recipe gets priority
++ byName.put(irecipe.id(), irecipe);
++ }
++ }
++ // CraftBukkit end
++
+ public boolean hadErrorsLoading() {
+ return this.hasErrors;
+ }
+
+- public <C extends Container, T extends Recipe<C>> Optional<RecipeHolder<T>> getRecipeFor(RecipeType<T> recipetype, C c0, Level level) {
+- return this.byType(recipetype).values().stream().filter((recipeholder) -> {
+- return recipeholder.value().matches(c0, level);
++ public <C extends Container, T extends Recipe<C>> Optional<RecipeHolder<T>> getRecipeFor(RecipeType<T> recipeType, C inventory, Level level) {
++ // CraftBukkit start
++ Optional<RecipeHolder<T>> recipe = this.byType(recipeType).values().stream().filter((recipeholder) -> {
++ return recipeholder.value().matches(inventory, level);
+ }).findFirst();
++ inventory.setCurrentRecipe(recipe.orElse(null)); // CraftBukkit - Clear recipe when no recipe is found
++ return recipe;
++ // CraftBukkit end
+ }
+
+ public <C extends Container, T extends Recipe<C>> Optional<Pair<ResourceLocation, RecipeHolder<T>>> getRecipeFor(RecipeType<T> recipetype, C c0, Level level, @Nullable ResourceLocation resourcelocation) {
+@@ -116,8 +144,8 @@
+ })).collect(Collectors.toList());
+ }
+
+- private <C extends Container, T extends Recipe<C>> Map<ResourceLocation, RecipeHolder<T>> byType(RecipeType<T> recipetype) {
+- return (Map) this.recipes.getOrDefault(recipetype, Collections.emptyMap());
++ private <C extends Container, T extends Recipe<C>> Map<ResourceLocation, RecipeHolder<T>> byType(RecipeType<T> recipeType) {
++ return (Map) this.recipes.getOrDefault(recipeType, new Object2ObjectLinkedOpenHashMap<>()); // CraftBukkit
+ }
+
+ public <C extends Container, T extends Recipe<C>> NonNullList<ItemStack> getRemainingItemsFor(RecipeType<T> recipetype, C c0, Level level) {
+@@ -160,12 +188,12 @@
+
+ public void replaceRecipes(Iterable<RecipeHolder<?>> iterable) {
+ this.hasErrors = false;
+- Map<RecipeType<?>, Map<ResourceLocation, RecipeHolder<?>>> map = Maps.newHashMap();
++ Map<RecipeType<?>, Object2ObjectLinkedOpenHashMap<ResourceLocation, RecipeHolder<?>>> map = Maps.newHashMap(); // CraftBukkit
+ Builder<ResourceLocation, RecipeHolder<?>> builder = ImmutableMap.builder();
+
+- iterable.forEach((recipeholder) -> {
+- Map<ResourceLocation, RecipeHolder<?>> map1 = (Map) map.computeIfAbsent(recipeholder.value().getType(), (recipetype) -> {
+- return Maps.newHashMap();
++ recipes.forEach((recipeholder) -> {
++ Map<ResourceLocation, RecipeHolder<?>> map1 = (Map) map.computeIfAbsent(recipeholder.value().getType(), (recipes) -> {
++ return new Object2ObjectLinkedOpenHashMap<>(); // CraftBukkit
+ });
+ ResourceLocation resourcelocation = recipeholder.id();
+ RecipeHolder<?> recipeholder1 = (RecipeHolder) map1.put(resourcelocation, recipeholder);
+@@ -176,10 +204,30 @@
+ }
+ });
+ this.recipes = ImmutableMap.copyOf(map);
+- this.byName = builder.build();
++ this.byName = Maps.newHashMap(builder.build()); // CraftBukkit
+ }
+
+- public static <C extends Container, T extends Recipe<C>> RecipeManager.CachedCheck<C, T> createCheck(final RecipeType<T> recipetype) {
++ // CraftBukkit start
++ public boolean removeRecipe(ResourceLocation mcKey) {
++ for (Object2ObjectLinkedOpenHashMap<ResourceLocation, RecipeHolder<?>> recipes : recipes.values()) {
++ recipes.remove(mcKey);
++ }
++
++ return byName.remove(mcKey) != null;
++ }
++
++ public void clearRecipes() {
++ this.recipes = Maps.newHashMap();
++
++ for (RecipeType<?> recipeType : BuiltInRegistries.RECIPE_TYPE) {
++ this.recipes.put(recipeType, new Object2ObjectLinkedOpenHashMap<>());
++ }
++
++ this.byName = Maps.newHashMap();
++ }
++ // CraftBukkit end
++
++ public static <C extends Container, T extends Recipe<C>> RecipeManager.CachedCheck<C, T> createCheck(final RecipeType<T> recipeType) {
+ return new RecipeManager.CachedCheck<C, T>() {
+ @Nullable
+ private ResourceLocation lastRecipe;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/ShapedRecipe.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/ShapedRecipe.java.patch
new file mode 100644
index 0000000000..e52d917b29
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/ShapedRecipe.java.patch
@@ -0,0 +1,85 @@
+--- a/net/minecraft/world/item/crafting/ShapedRecipe.java
++++ b/net/minecraft/world/item/crafting/ShapedRecipe.java
+@@ -9,6 +9,13 @@
+ import net.minecraft.world.inventory.CraftingContainer;
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.level.Level;
++// CraftBukkit start
++import org.bukkit.NamespacedKey;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.craftbukkit.inventory.CraftRecipe;
++import org.bukkit.craftbukkit.inventory.CraftShapedRecipe;
++import org.bukkit.inventory.RecipeChoice;
++// CraftBukkit end
+
+ public class ShapedRecipe implements CraftingRecipe {
+
+@@ -30,7 +37,68 @@
+ this(s, craftingbookcategory, shapedrecipepattern, itemstack, true);
+ }
+
++ // CraftBukkit start
+ @Override
++ public org.bukkit.inventory.ShapedRecipe toBukkitRecipe(NamespacedKey id) {
++ CraftItemStack result = CraftItemStack.asCraftMirror(this.result);
++ CraftShapedRecipe recipe = new CraftShapedRecipe(id, result, this);
++ recipe.setGroup(this.group);
++ recipe.setCategory(CraftRecipe.getCategory(this.category()));
++
++ switch (this.pattern.height()) {
++ case 1:
++ switch (this.pattern.width()) {
++ case 1:
++ recipe.shape("a");
++ break;
++ case 2:
++ recipe.shape("ab");
++ break;
++ case 3:
++ recipe.shape("abc");
++ break;
++ }
++ break;
++ case 2:
++ switch (this.pattern.width()) {
++ case 1:
++ recipe.shape("a","b");
++ break;
++ case 2:
++ recipe.shape("ab","cd");
++ break;
++ case 3:
++ recipe.shape("abc","def");
++ break;
++ }
++ break;
++ case 3:
++ switch (this.pattern.width()) {
++ case 1:
++ recipe.shape("a","b","c");
++ break;
++ case 2:
++ recipe.shape("ab","cd","ef");
++ break;
++ case 3:
++ recipe.shape("abc","def","ghi");
++ break;
++ }
++ break;
++ }
++ char c = 'a';
++ for (Ingredient list : this.pattern.ingredients()) {
++ RecipeChoice choice = CraftRecipe.toBukkit(list);
++ if (choice != null) {
++ recipe.setIngredient(c, choice);
++ }
++
++ c++;
++ }
++ return recipe;
++ }
++ // CraftBukkit end
++
+ @Override
+ public RecipeSerializer<?> getSerializer() {
+ return RecipeSerializer.SHAPED_RECIPE;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/ShapelessRecipe.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/ShapelessRecipe.java.patch
new file mode 100644
index 0000000000..299d6f7c27
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/ShapelessRecipe.java.patch
@@ -0,0 +1,38 @@
+--- a/net/minecraft/world/item/crafting/ShapelessRecipe.java
++++ b/net/minecraft/world/item/crafting/ShapelessRecipe.java
+@@ -13,6 +13,12 @@
+ import net.minecraft.world.inventory.CraftingContainer;
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.level.Level;
++// CraftBukkit start
++import org.bukkit.NamespacedKey;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.craftbukkit.inventory.CraftRecipe;
++import org.bukkit.craftbukkit.inventory.CraftShapelessRecipe;
++// CraftBukkit end
+
+ public class ShapelessRecipe implements CraftingRecipe {
+
+@@ -28,7 +34,22 @@
+ this.ingredients = nonnulllist;
+ }
+
++ // CraftBukkit start
++ @SuppressWarnings("unchecked")
+ @Override
++ public org.bukkit.inventory.ShapelessRecipe toBukkitRecipe(NamespacedKey id) {
++ CraftItemStack result = CraftItemStack.asCraftMirror(this.result);
++ CraftShapelessRecipe recipe = new CraftShapelessRecipe(id, result, this);
++ recipe.setGroup(this.group);
++ recipe.setCategory(CraftRecipe.getCategory(this.category()));
++
++ for (Ingredient list : this.ingredients) {
++ recipe.addIngredient(CraftRecipe.toBukkit(list));
++ }
++ return recipe;
++ }
++ // CraftBukkit end
++
+ @Override
+ public RecipeSerializer<?> getSerializer() {
+ return RecipeSerializer.SHAPELESS_RECIPE;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/SmeltingRecipe.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/SmeltingRecipe.java.patch
new file mode 100644
index 0000000000..13a11d2982
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/SmeltingRecipe.java.patch
@@ -0,0 +1,35 @@
+--- a/net/minecraft/world/item/crafting/SmeltingRecipe.java
++++ b/net/minecraft/world/item/crafting/SmeltingRecipe.java
+@@ -3,6 +3,14 @@
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.level.block.Blocks;
+
++// CraftBukkit start
++import org.bukkit.NamespacedKey;
++import org.bukkit.craftbukkit.inventory.CraftFurnaceRecipe;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.craftbukkit.inventory.CraftRecipe;
++import org.bukkit.inventory.Recipe;
++// CraftBukkit end
++
+ public class SmeltingRecipe extends AbstractCookingRecipe {
+
+ public SmeltingRecipe(String s, CookingBookCategory cookingbookcategory, Ingredient ingredient, ItemStack itemstack, float f, int i) {
+@@ -20,4 +26,17 @@
+ public RecipeSerializer<?> getSerializer() {
+ return RecipeSerializer.SMELTING_RECIPE;
+ }
++
++ // CraftBukkit start
++ @Override
++ public Recipe toBukkitRecipe(NamespacedKey id) {
++ CraftItemStack result = CraftItemStack.asCraftMirror(this.result);
++
++ CraftFurnaceRecipe recipe = new CraftFurnaceRecipe(id, result, CraftRecipe.toBukkit(this.ingredient), this.experience, this.cookingTime);
++ recipe.setGroup(this.group);
++ recipe.setCategory(CraftRecipe.getCategory(this.category()));
++
++ return recipe;
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/SmithingTransformRecipe.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/SmithingTransformRecipe.java.patch
new file mode 100644
index 0000000000..e4042bea81
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/SmithingTransformRecipe.java.patch
@@ -0,0 +1,37 @@
+--- a/net/minecraft/world/item/crafting/SmithingTransformRecipe.java
++++ b/net/minecraft/world/item/crafting/SmithingTransformRecipe.java
+@@ -9,6 +9,13 @@
+ import net.minecraft.world.Container;
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.level.Level;
++// CraftBukkit start
++import org.bukkit.NamespacedKey;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.craftbukkit.inventory.CraftRecipe;
++import org.bukkit.craftbukkit.inventory.CraftSmithingTransformRecipe;
++import org.bukkit.inventory.Recipe;
++// CraftBukkit end
+
+ public class SmithingTransformRecipe implements SmithingRecipe {
+
+@@ -79,8 +78,19 @@
+ return Stream.of(this.template, this.base, this.addition).anyMatch(Ingredient::isEmpty);
+ }
+
+- public static class Serializer implements RecipeSerializer<SmithingTransformRecipe> {
++ // CraftBukkit start
++ @Override
++ public Recipe toBukkitRecipe(NamespacedKey id) {
++ CraftItemStack result = CraftItemStack.asCraftMirror(this.result);
+
++ CraftSmithingTransformRecipe recipe = new CraftSmithingTransformRecipe(id, result, CraftRecipe.toBukkit(this.template), CraftRecipe.toBukkit(this.base), CraftRecipe.toBukkit(this.addition));
++
++ return recipe;
++ }
++ // CraftBukkit end
++
++ public static class a implements RecipeSerializer<SmithingTransformRecipe> {
++
+ private static final Codec<SmithingTransformRecipe> CODEC = RecordCodecBuilder.create((instance) -> {
+ return instance.group(Ingredient.CODEC.fieldOf("template").forGetter((smithingtransformrecipe) -> {
+ return smithingtransformrecipe.template;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/SmithingTrimRecipe.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/SmithingTrimRecipe.java.patch
new file mode 100644
index 0000000000..fda66586fc
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/SmithingTrimRecipe.java.patch
@@ -0,0 +1,29 @@
+--- a/net/minecraft/world/item/crafting/SmithingTrimRecipe.java
++++ b/net/minecraft/world/item/crafting/SmithingTrimRecipe.java
+@@ -17,6 +17,12 @@
+ import net.minecraft.world.item.armortrim.TrimPattern;
+ import net.minecraft.world.item.armortrim.TrimPatterns;
+ import net.minecraft.world.level.Level;
++// CraftBukkit start
++import org.bukkit.NamespacedKey;
++import org.bukkit.craftbukkit.inventory.CraftRecipe;
++import org.bukkit.craftbukkit.inventory.CraftSmithingTrimRecipe;
++import org.bukkit.inventory.Recipe;
++// CraftBukkit end
+
+ public class SmithingTrimRecipe implements SmithingRecipe {
+
+@@ -115,7 +113,12 @@
+ return Stream.of(this.template, this.base, this.addition).anyMatch(Ingredient::isEmpty);
+ }
+
+- public static class Serializer implements RecipeSerializer<SmithingTrimRecipe> {
++ // CraftBukkit start
++ @Override
++ public Recipe toBukkitRecipe(NamespacedKey id) {
++ return new CraftSmithingTrimRecipe(id, CraftRecipe.toBukkit(this.template), CraftRecipe.toBukkit(this.base), CraftRecipe.toBukkit(this.addition));
++ }
++ // CraftBukkit end
+
+ private static final Codec<SmithingTrimRecipe> CODEC = RecordCodecBuilder.create((instance) -> {
+ return instance.group(Ingredient.CODEC.fieldOf("template").forGetter((smithingtrimrecipe) -> {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/SmokingRecipe.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/SmokingRecipe.java.patch
new file mode 100644
index 0000000000..c09a16b9e8
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/SmokingRecipe.java.patch
@@ -0,0 +1,35 @@
+--- a/net/minecraft/world/item/crafting/SmokingRecipe.java
++++ b/net/minecraft/world/item/crafting/SmokingRecipe.java
+@@ -3,6 +3,14 @@
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.level.block.Blocks;
+
++// CraftBukkit start
++import org.bukkit.NamespacedKey;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.craftbukkit.inventory.CraftRecipe;
++import org.bukkit.craftbukkit.inventory.CraftSmokingRecipe;
++import org.bukkit.inventory.Recipe;
++// CraftBukkit end
++
+ public class SmokingRecipe extends AbstractCookingRecipe {
+
+ public SmokingRecipe(String s, CookingBookCategory cookingbookcategory, Ingredient ingredient, ItemStack itemstack, float f, int i) {
+@@ -20,4 +26,17 @@
+ public RecipeSerializer<?> getSerializer() {
+ return RecipeSerializer.SMOKING_RECIPE;
+ }
++
++ // CraftBukkit start
++ @Override
++ public Recipe toBukkitRecipe(NamespacedKey id) {
++ CraftItemStack result = CraftItemStack.asCraftMirror(this.result);
++
++ CraftSmokingRecipe recipe = new CraftSmokingRecipe(id, result, CraftRecipe.toBukkit(this.ingredient), this.experience, this.cookingTime);
++ recipe.setGroup(this.group);
++ recipe.setCategory(CraftRecipe.getCategory(this.category()));
++
++ return recipe;
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/StonecutterRecipe.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/StonecutterRecipe.java.patch
new file mode 100644
index 0000000000..5a8da8ebc4
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/crafting/StonecutterRecipe.java.patch
@@ -0,0 +1,34 @@
+--- a/net/minecraft/world/item/crafting/StonecutterRecipe.java
++++ b/net/minecraft/world/item/crafting/StonecutterRecipe.java
+@@ -5,6 +5,14 @@
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.block.Blocks;
+
++// CraftBukkit start
++import org.bukkit.NamespacedKey;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.craftbukkit.inventory.CraftRecipe;
++import org.bukkit.craftbukkit.inventory.CraftStonecuttingRecipe;
++import org.bukkit.inventory.Recipe;
++// CraftBukkit end
++
+ public class StonecutterRecipe extends SingleItemRecipe {
+
+ public StonecutterRecipe(String s, Ingredient ingredient, ItemStack itemstack) {
+@@ -22,4 +28,16 @@
+ public ItemStack getToastSymbol() {
+ return new ItemStack(Blocks.STONECUTTER);
+ }
++
++ // CraftBukkit start
++ @Override
++ public Recipe toBukkitRecipe(NamespacedKey id) {
++ CraftItemStack result = CraftItemStack.asCraftMirror(this.result);
++
++ CraftStonecuttingRecipe recipe = new CraftStonecuttingRecipe(id, result, CraftRecipe.toBukkit(this.ingredient));
++ recipe.setGroup(this.group);
++
++ return recipe;
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/enchantment/DamageEnchantment.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/enchantment/DamageEnchantment.java.patch
new file mode 100644
index 0000000000..48596baafd
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/enchantment/DamageEnchantment.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/item/enchantment/DamageEnchantment.java
++++ b/net/minecraft/world/item/enchantment/DamageEnchantment.java
+@@ -70,7 +63,7 @@
+ if (this.type == 2 && i > 0 && livingentity1.getMobType() == MobType.ARTHROPOD) {
+ int j = 20 + livingentity.getRandom().nextInt(10 * i);
+
+- livingentity1.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, j, 3));
++ entityliving1.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, j, 3), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+ }
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/enchantment/FrostWalkerEnchantment.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/enchantment/FrostWalkerEnchantment.java.patch
new file mode 100644
index 0000000000..76b89e3376
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/enchantment/FrostWalkerEnchantment.java.patch
@@ -0,0 +1,18 @@
+--- a/net/minecraft/world/item/enchantment/FrostWalkerEnchantment.java
++++ b/net/minecraft/world/item/enchantment/FrostWalkerEnchantment.java
+@@ -58,9 +54,12 @@
+ if (blockstate1.isAir()) {
+ BlockState blockstate2 = level.getBlockState(blockpos1);
+
+- if (blockstate2 == FrostedIceBlock.meltsInto() && blockstate.canSurvive(level, blockpos1) && level.isUnobstructed(blockstate, blockpos1, CollisionContext.empty())) {
+- level.setBlockAndUpdate(blockpos1, blockstate);
+- level.scheduleTick(blockpos1, Blocks.FROSTED_ICE, Mth.nextInt(livingentity.getRandom(), 60, 120));
++ if (iblockdata2 == FrostedIceBlock.meltsInto() && iblockdata.canSurvive(level, blockposition1) && level.isUnobstructed(iblockdata, blockposition1, CollisionContext.empty())) {
++ // CraftBukkit Start - Call EntityBlockFormEvent for Frost Walker
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(level, blockposition1, iblockdata, living)) {
++ level.scheduleTick(blockposition1, Blocks.FROSTED_ICE, Mth.nextInt(living.getRandom(), 60, 120));
++ }
++ // CraftBukkit End
+ }
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/trading/Merchant.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/trading/Merchant.java.patch
new file mode 100644
index 0000000000..592a148801
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/trading/Merchant.java.patch
@@ -0,0 +1,9 @@
+--- a/net/minecraft/world/item/trading/Merchant.java
++++ b/net/minecraft/world/item/trading/Merchant.java
+@@ -52,4 +52,6 @@
+ }
+
+ boolean isClientSide();
++
++ org.bukkit.craftbukkit.inventory.CraftMerchant getCraftMerchant(); // CraftBukkit
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/trading/MerchantOffer.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/trading/MerchantOffer.java.patch
new file mode 100644
index 0000000000..497a5ecc84
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/item/trading/MerchantOffer.java.patch
@@ -0,0 +1,73 @@
+--- a/net/minecraft/world/item/trading/MerchantOffer.java
++++ b/net/minecraft/world/item/trading/MerchantOffer.java
+@@ -5,20 +5,38 @@
+ import net.minecraft.util.Mth;
+ import net.minecraft.world.item.ItemStack;
+
++import org.bukkit.craftbukkit.inventory.CraftMerchantRecipe; // CraftBukkit
++
+ public class MerchantOffer {
+
+- private final ItemStack baseCostA;
+- private final ItemStack costB;
+- private final ItemStack result;
+- private int uses;
+- private final int maxUses;
+- private boolean rewardExp;
+- private int specialPriceDiff;
+- private int demand;
+- private float priceMultiplier;
+- private int xp;
++ public ItemStack baseCostA;
++ public ItemStack costB;
++ public final ItemStack result;
++ public int uses;
++ public int maxUses;
++ public boolean rewardExp;
++ public int specialPriceDiff;
++ public int demand;
++ public float priceMultiplier;
++ public int xp;
++ // CraftBukkit start
++ private CraftMerchantRecipe bukkitHandle;
+
+- public MerchantOffer(CompoundTag compoundtag) {
++ public CraftMerchantRecipe asBukkit() {
++ return (bukkitHandle == null) ? bukkitHandle = new CraftMerchantRecipe(this) : bukkitHandle;
++ }
++
++ public MerchantOffer(ItemStack itemstack, ItemStack itemstack1, ItemStack itemstack2, int uses, int maxUses, int experience, float priceMultiplier, CraftMerchantRecipe bukkit) {
++ this(itemstack, itemstack1, itemstack2, uses, maxUses, experience, priceMultiplier, 0, bukkit);
++ }
++
++ public MerchantOffer(ItemStack itemstack, ItemStack itemstack1, ItemStack itemstack2, int uses, int maxUses, int experience, float priceMultiplier, int demand, CraftMerchantRecipe bukkit) {
++ this(itemstack, itemstack1, itemstack2, uses, maxUses, experience, priceMultiplier, demand);
++ this.bukkitHandle = bukkit;
++ }
++ // CraftBukkit end
++
++ public MerchantOffer(CompoundTag compoundTag) {
+ this.rewardExp = true;
+ this.xp = 1;
+ this.baseCostA = ItemStack.of(compoundtag.getCompound("buy"));
+@@ -96,6 +114,7 @@
+ return ItemStack.EMPTY;
+ } else {
+ int i = this.baseCostA.getCount();
++ if (i <= 0) return ItemStack.EMPTY; // CraftBukkit - SPIGOT-5476
+ int j = Math.max(0, Mth.floor((float) (i * this.demand) * this.priceMultiplier));
+
+ return this.baseCostA.copyWithCount(Mth.clamp(i + j + this.specialPriceDiff, 1, this.baseCostA.getItem().getMaxStackSize()));
+@@ -216,7 +235,11 @@
+ if (!this.satisfiedBy(itemstack, itemstack1)) {
+ return false;
+ } else {
+- itemstack.shrink(this.getCostA().getCount());
++ // CraftBukkit start
++ if (!this.getCostA().isEmpty()) {
++ playerOfferA.shrink(this.getCostA().getCount());
++ }
++ // CraftBukkit end
+ if (!this.getCostB().isEmpty()) {
+ itemstack1.shrink(this.getCostB().getCount());
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/BaseCommandBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/BaseCommandBlock.java.patch
new file mode 100644
index 0000000000..c3f913c304
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/BaseCommandBlock.java.patch
@@ -0,0 +1,22 @@
+--- a/net/minecraft/world/level/BaseCommandBlock.java
++++ b/net/minecraft/world/level/BaseCommandBlock.java
+@@ -30,6 +30,10 @@
+ private Component lastOutput;
+ private String command = "";
+ private Component name;
++ // CraftBukkit start
++ @Override
++ public abstract org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper);
++ // CraftBukkit end
+
+ public BaseCommandBlock() {
+ this.name = BaseCommandBlock.DEFAULT_NAME;
+@@ -126,7 +130,7 @@
+
+ });
+
+- minecraftserver.getCommands().performPrefixedCommand(commandsourcestack, this.command);
++ minecraftserver.getCommands().dispatchServerCommand(commandlistenerwrapper, this.command); // CraftBukkit
+ } catch (Throwable throwable) {
+ CrashReport crashreport = CrashReport.forThrowable(throwable, "Executing command block");
+ CrashReportCategory crashreportcategory = crashreport.addCategory("Command to be executed");
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/BaseSpawner.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/BaseSpawner.java.patch
new file mode 100644
index 0000000000..d597c8cea9
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/BaseSpawner.java.patch
@@ -0,0 +1,30 @@
+--- a/net/minecraft/world/level/BaseSpawner.java
++++ b/net/minecraft/world/level/BaseSpawner.java
+@@ -50,8 +50,9 @@
+
+ public BaseSpawner() {}
+
+- public void setEntityId(EntityType<?> entitytype, @Nullable Level level, RandomSource randomsource, BlockPos blockpos) {
+- this.getOrCreateNextSpawnData(level, randomsource, blockpos).getEntityToSpawn().putString("id", BuiltInRegistries.ENTITY_TYPE.getKey(entitytype).toString());
++ public void setEntityId(EntityType<?> type, @Nullable Level level, RandomSource random, BlockPos pos) {
++ this.getOrCreateNextSpawnData(level, random, pos).getEntityToSpawn().putString("id", BuiltInRegistries.ENTITY_TYPE.getKey(type).toString());
++ this.spawnPotentials = SimpleWeightedRandomList.empty(); // CraftBukkit - SPIGOT-3496, MC-92282
+ }
+
+ private boolean isNearPlayer(Level level, BlockPos blockpos) {
+@@ -154,8 +155,13 @@
+ }
+ }
+
+- if (!serverlevel.tryAddFreshEntityWithPassengers(entity)) {
+- this.delay(serverlevel, blockpos);
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callSpawnerSpawnEvent(entity, pos).isCancelled()) {
++ continue;
++ }
++ if (!serverLevel.tryAddFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER)) {
++ // CraftBukkit end
++ this.delay(serverLevel, pos);
+ return;
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/BlockGetter.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/BlockGetter.java.patch
new file mode 100644
index 0000000000..b36a28adfd
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/BlockGetter.java.patch
@@ -0,0 +1,49 @@
+--- a/net/minecraft/world/level/BlockGetter.java
++++ b/net/minecraft/world/level/BlockGetter.java
+@@ -58,24 +58,30 @@
+ });
+ }
+
+- default BlockHitResult clip(ClipContext clipcontext) {
+- return (BlockHitResult) traverseBlocks(clipcontext.getFrom(), clipcontext.getTo(), clipcontext, (clipcontext1, blockpos) -> {
+- BlockState blockstate = this.getBlockState(blockpos);
+- FluidState fluidstate = this.getFluidState(blockpos);
+- Vec3 vec3 = clipcontext1.getFrom();
+- Vec3 vec31 = clipcontext1.getTo();
+- VoxelShape voxelshape = clipcontext1.getBlockShape(blockstate, this, blockpos);
+- BlockHitResult blockhitresult = this.clipWithInteractionOverride(vec3, vec31, blockpos, voxelshape, blockstate);
+- VoxelShape voxelshape1 = clipcontext1.getFluidShape(fluidstate, this, blockpos);
+- BlockHitResult blockhitresult1 = voxelshape1.clip(vec3, vec31, blockpos);
+- double d0 = blockhitresult == null ? Double.MAX_VALUE : clipcontext1.getFrom().distanceToSqr(blockhitresult.getLocation());
+- double d1 = blockhitresult1 == null ? Double.MAX_VALUE : clipcontext1.getFrom().distanceToSqr(blockhitresult1.getLocation());
++ // CraftBukkit start - moved block handling into separate method for use by Block#rayTrace
++ default BlockHitResult clip(ClipContext raytrace1, BlockPos blockposition) {
++ IBlockData iblockdata = this.getBlockState(blockposition);
++ FluidState fluid = this.getFluidState(blockposition);
++ Vec3 vec3d = raytrace1.getFrom();
++ Vec3 vec3d1 = raytrace1.getTo();
++ VoxelShape voxelshape = raytrace1.getBlockShape(iblockdata, this, blockposition);
++ BlockHitResult movingobjectpositionblock = this.clipWithInteractionOverride(vec3d, vec3d1, blockposition, voxelshape, iblockdata);
++ VoxelShape voxelshape1 = raytrace1.getFluidShape(fluid, this, blockposition);
++ BlockHitResult movingobjectpositionblock1 = voxelshape1.clip(vec3d, vec3d1, blockposition);
++ double d0 = movingobjectpositionblock == null ? Double.MAX_VALUE : raytrace1.getFrom().distanceToSqr(movingobjectpositionblock.getLocation());
++ double d1 = movingobjectpositionblock1 == null ? Double.MAX_VALUE : raytrace1.getFrom().distanceToSqr(movingobjectpositionblock1.getLocation());
+
+- return d0 <= d1 ? blockhitresult : blockhitresult1;
+- }, (clipcontext1) -> {
+- Vec3 vec3 = clipcontext1.getFrom().subtract(clipcontext1.getTo());
++ return d0 <= d1 ? movingobjectpositionblock : movingobjectpositionblock1;
++ }
++ // CraftBukkit end
+
+- return BlockHitResult.miss(clipcontext1.getTo(), Direction.getNearest(vec3.x, vec3.y, vec3.z), BlockPos.containing(clipcontext1.getTo()));
++ default BlockHitResult clip(ClipContext context) {
++ return (BlockHitResult) traverseBlocks(context.getFrom(), context.getTo(), context, (raytrace1, blockposition) -> {
++ return this.clip(raytrace1, blockposition); // CraftBukkit - moved into separate method
++ }, (raytrace1) -> {
++ Vec3 vec3d = raytrace1.getFrom().subtract(raytrace1.getTo());
++
++ return BlockHitResult.miss(raytrace1.getTo(), Direction.getNearest(vec3d.x, vec3d.y, vec3d.z), BlockPos.containing(raytrace1.getTo()));
+ });
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/ClipContext.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/ClipContext.java.patch
new file mode 100644
index 0000000000..8f1992fa07
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/ClipContext.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/world/level/ClipContext.java
++++ b/net/minecraft/world/level/ClipContext.java
+@@ -21,8 +21,8 @@
+ private final ClipContext.Fluid fluid;
+ private final CollisionContext collisionContext;
+
+- public ClipContext(Vec3 vec3, Vec3 vec31, ClipContext.Block clipcontext_block, ClipContext.Fluid clipcontext_fluid, Entity entity) {
+- this(vec3, vec31, clipcontext_block, clipcontext_fluid, CollisionContext.of(entity));
++ public ClipContext(Vec3 from, Vec3 to, ClipContext.Block block, ClipContext.Fluid fluid, Entity entity) {
++ this(from, to, block, fluid, (entity == null) ? CollisionContext.empty() : CollisionContext.of(entity)); // CraftBukkit
+ }
+
+ public ClipContext(Vec3 vec3, Vec3 vec31, ClipContext.Block clipcontext_block, ClipContext.Fluid clipcontext_fluid, CollisionContext collisioncontext) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/Explosion.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/Explosion.java.patch
new file mode 100644
index 0000000000..9c6b59259b
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/Explosion.java.patch
@@ -0,0 +1,189 @@
+--- a/net/minecraft/world/level/Explosion.java
++++ b/net/minecraft/world/level/Explosion.java
+@@ -38,6 +40,12 @@
+ import net.minecraft.world.phys.AABB;
+ import net.minecraft.world.phys.HitResult;
+ import net.minecraft.world.phys.Vec3;
++import net.minecraft.world.level.block.Blocks;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityExplodeEvent;
++import org.bukkit.Location;
++import org.bukkit.event.block.BlockExplodeEvent;
++// CraftBukkit end
+
+ public class Explosion {
+
+@@ -60,6 +68,10 @@
+ private final SoundEvent explosionSound;
+ private final ObjectArrayList<BlockPos> toBlow;
+ private final Map<Player, Vec3> hitPlayers;
++ // CraftBukkit - add field
++ public boolean wasCanceled = false;
++ public float yield;
++ // CraftBukkit end
+
+ public static DamageSource getDefaultDamageSource(Level level, @Nullable Entity entity) {
+ return level.damageSources().explosion(entity, getIndirectSourceEntityInternal(entity));
+@@ -85,7 +97,7 @@
+ this.hitPlayers = Maps.newHashMap();
+ this.level = level;
+ this.source = entity;
+- this.radius = f;
++ this.radius = (float) Math.max(f, 0.0); // CraftBukkit - clamp bad values
+ this.x = d0;
+ this.y = d1;
+ this.z = d2;
+@@ -93,9 +105,10 @@
+ this.blockInteraction = explosion_blockinteraction;
+ this.damageSource = damagesource == null ? level.damageSources().explosion(this) : damagesource;
+ this.damageCalculator = explosiondamagecalculator == null ? this.makeDamageCalculator(entity) : explosiondamagecalculator;
+- this.smallExplosionParticles = particleoptions;
+- this.largeExplosionParticles = particleoptions1;
+- this.explosionSound = soundevent;
++ this.smallExplosionParticles = particleparam;
++ this.largeExplosionParticles = particleparam1;
++ this.explosionSound = soundeffect;
++ this.yield = this.blockInteraction == Explosion.Effect.DESTROY_WITH_DECAY ? 1.0F / this.radius : 1.0F; // CraftBukkit
+ }
+
+ private ExplosionDamageCalculator makeDamageCalculator(@Nullable Entity entity) {
+@@ -146,6 +159,11 @@
+ }
+
+ public void explode() {
++ // CraftBukkit start
++ if (this.radius < 0.1F) {
++ return;
++ }
++ // CraftBukkit end
+ this.level.gameEvent(this.source, GameEvent.EXPLODE, new Vec3(this.x, this.y, this.z));
+ Set<BlockPos> set = Sets.newHashSet();
+ boolean flag = true;
+@@ -228,7 +246,37 @@
+ d9 /= d11;
+ d10 /= d11;
+ if (this.damageCalculator.shouldDamageEntity(this, entity)) {
+- entity.hurt(this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity));
++ // CraftBukkit start
++
++ // Special case ender dragon only give knockback if no damage is cancelled
++ // Thinks to note:
++ // - Setting a velocity to a ComplexEntityPart is ignored (and therefore not needed)
++ // - Damaging ComplexEntityPart while forward the damage to EntityEnderDragon
++ // - Damaging EntityEnderDragon does nothing
++ // - EntityEnderDragon hitbock always covers the other parts and is therefore always present
++ if (entity instanceof EnderDragonPart) {
++ continue;
++ }
++
++ CraftEventFactory.entityDamage = source;
++ entity.lastDamageCancelled = false;
++
++ if (entity instanceof EnderDragon) {
++ for (EnderDragonPart entityComplexPart : ((EnderDragon) entity).subEntities) {
++ // Calculate damage separately for each EntityComplexPart
++ if (list.contains(entityComplexPart)) {
++ entityComplexPart.hurt(this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity));
++ }
++ }
++ } else {
++ entity.hurt(this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity));
++ }
++
++ CraftEventFactory.entityDamage = null;
++ if (entity.lastDamageCancelled) { // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Skip entity if damage event was cancelled
++ continue;
++ }
++ // CraftBukkit end
+ }
+
+ double d12 = (1.0D - d7) * (double) getSeenPercent(vec3, entity);
+@@ -287,9 +335,63 @@
+
+ Util.shuffle(this.toBlow, this.level.random);
+ ObjectListIterator objectlistiterator = this.toBlow.iterator();
++ // CraftBukkit start
++ org.bukkit.World bworld = this.level.getWorld();
++ org.bukkit.entity.Entity explode = this.source == null ? null : this.source.getBukkitEntity();
++ Location location = new Location(bworld, this.x, this.y, this.z);
+
++ List<org.bukkit.block.Block> blockList = new ObjectArrayList<>();
++ for (int i1 = this.toBlow.size() - 1; i1 >= 0; i1--) {
++ BlockPos cpos = this.toBlow.get(i1);
++ org.bukkit.block.Block bblock = bworld.getBlockAt(cpos.getX(), cpos.getY(), cpos.getZ());
++ if (!bblock.getType().isAir()) {
++ blockList.add(bblock);
++ }
++ }
++
++ List<org.bukkit.block.Block> bukkitBlocks;
++
++ if (explode != null) {
++ EntityExplodeEvent event = new EntityExplodeEvent(explode, location, blockList, this.yield);
++ this.level.getCraftServer().getPluginManager().callEvent(event);
++ this.wasCanceled = event.isCancelled();
++ bukkitBlocks = event.blockList();
++ this.yield = event.getYield();
++ } else {
++ BlockExplodeEvent event = new BlockExplodeEvent(location.getBlock(), blockList, this.yield);
++ this.level.getCraftServer().getPluginManager().callEvent(event);
++ this.wasCanceled = event.isCancelled();
++ bukkitBlocks = event.blockList();
++ this.yield = event.getYield();
++ }
++
++ this.toBlow.clear();
++
++ for (org.bukkit.block.Block bblock : bukkitBlocks) {
++ BlockPos coords = new BlockPos(bblock.getX(), bblock.getY(), bblock.getZ());
++ toBlow.add(coords);
++ }
++
++ if (this.wasCanceled) {
++ return;
++ }
++ // CraftBukkit end
++ objectlistiterator = this.toBlow.iterator();
++
+ while (objectlistiterator.hasNext()) {
+- BlockPos blockpos = (BlockPos) objectlistiterator.next();
++ BlockPos blockposition = (BlockPos) objectlistiterator.next();
++ // CraftBukkit start - TNTPrimeEvent
++ IBlockData iblockdata = this.level.getBlockState(blockposition);
++ Block block = iblockdata.getBlock();
++ if (block instanceof net.minecraft.world.level.block.TntBlock) {
++ Entity sourceEntity = source == null ? null : source;
++ BlockPos sourceBlock = sourceEntity == null ? BlockPos.containing(this.x, this.y, this.z) : null;
++ if (!CraftEventFactory.callTNTPrimeEvent(this.level, blockposition, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.EXPLOSION, sourceEntity, sourceBlock)) {
++ this.level.sendBlockUpdated(blockposition, Blocks.AIR.defaultBlockState(), iblockdata, 3); // Update the block on the client
++ continue;
++ }
++ }
++ // CraftBukkit end
+
+ this.level.getBlockState(blockpos).onExplosionHit(this.level, blockpos, this, (itemstack, blockpos1) -> {
+ addOrAppendStack(list, itemstack, blockpos1);
+@@ -313,15 +415,20 @@
+ while (objectlistiterator1.hasNext()) {
+ BlockPos blockpos1 = (BlockPos) objectlistiterator1.next();
+
+- if (this.random.nextInt(3) == 0 && this.level.getBlockState(blockpos1).isAir() && this.level.getBlockState(blockpos1.below()).isSolidRender(this.level, blockpos1.below())) {
+- this.level.setBlockAndUpdate(blockpos1, BaseFireBlock.getState(this.level, blockpos1));
++ if (this.random.nextInt(3) == 0 && this.level.getBlockState(blockposition1).isAir() && this.level.getBlockState(blockposition1.below()).isSolidRender(this.level, blockposition1.below())) {
++ // CraftBukkit start - Ignition by explosion
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(this.level, blockposition1, this).isCancelled()) {
++ this.level.setBlockAndUpdate(blockposition1, BaseFireBlock.getState(this.level, blockposition1));
++ }
++ // CraftBukkit end
+ }
+ }
+ }
+
+ }
+
+- private static void addOrAppendStack(List<Pair<ItemStack, BlockPos>> list, ItemStack itemstack, BlockPos blockpos) {
++ private static void addOrAppendStack(List<Pair<ItemStack, BlockPos>> list, ItemStack itemstack, BlockPos blockposition) {
++ if (itemstack.isEmpty()) return; // CraftBukkit - SPIGOT-5425
+ for (int i = 0; i < list.size(); ++i) {
+ Pair<ItemStack, BlockPos> pair = (Pair) list.get(i);
+ ItemStack itemstack1 = (ItemStack) pair.getFirst();
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/GameRules.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/GameRules.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/GameRules.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/Level.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/Level.java.patch
new file mode 100644
index 0000000000..2e0b34452c
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/Level.java.patch
@@ -0,0 +1,316 @@
+--- a/net/minecraft/world/level/Level.java
++++ b/net/minecraft/world/level/Level.java
+@@ -75,6 +79,27 @@
+ import net.minecraft.world.phys.Vec3;
+ import net.minecraft.world.scores.Scoreboard;
+
++// CraftBukkit start
++import java.util.HashMap;
++import java.util.Map;
++import net.minecraft.network.protocol.game.ClientboundSetBorderCenterPacket;
++import net.minecraft.network.protocol.game.ClientboundSetBorderLerpSizePacket;
++import net.minecraft.network.protocol.game.ClientboundSetBorderSizePacket;
++import net.minecraft.network.protocol.game.ClientboundSetBorderWarningDelayPacket;
++import net.minecraft.network.protocol.game.ClientboundSetBorderWarningDistancePacket;
++import org.bukkit.Bukkit;
++import org.bukkit.Location;
++import org.bukkit.craftbukkit.CraftServer;
++import org.bukkit.craftbukkit.CraftWorld;
++import org.bukkit.craftbukkit.block.CapturedBlockState;
++import org.bukkit.craftbukkit.block.data.CraftBlockData;
++import org.bukkit.craftbukkit.util.CraftSpawnCategory;
++import org.bukkit.craftbukkit.util.CraftNamespacedKey;
++import org.bukkit.entity.SpawnCategory;
++import org.bukkit.event.block.BlockPhysicsEvent;
++import org.bukkit.event.world.GenericGameEvent;
++// CraftBukkit end
++
+ public abstract class Level implements LevelAccessor, AutoCloseable {
+
+ public static final Codec<ResourceKey<Level>> RESOURCE_KEY_CODEC = ResourceKey.codec(Registries.DIMENSION);
+@@ -117,7 +142,43 @@
+ private final DamageSources damageSources;
+ private long subTickCount;
+
+- protected Level(WritableLevelData writableleveldata, ResourceKey<Level> resourcekey, RegistryAccess registryaccess, Holder<DimensionType> holder, Supplier<ProfilerFiller> supplier, boolean flag, boolean flag1, long i, int j) {
++ // CraftBukkit start Added the following
++ private final CraftWorld world;
++ public boolean pvpMode;
++ public boolean keepSpawnInMemory = true;
++ public org.bukkit.generator.ChunkGenerator generator;
++
++ public boolean preventPoiUpdated = false; // CraftBukkit - SPIGOT-5710
++ public boolean captureBlockStates = false;
++ public boolean captureTreeGeneration = false;
++ public Map<BlockPos, CapturedBlockState> capturedBlockStates = new java.util.LinkedHashMap<>();
++ public Map<BlockPos, BlockEntity> capturedTileEntities = new HashMap<>();
++ public List<ItemEntity> captureDrops;
++ public final it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<SpawnCategory> ticksPerSpawnCategory = new it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<>();
++ public boolean populating;
++
++ public CraftWorld getWorld() {
++ return this.world;
++ }
++
++ public CraftServer getCraftServer() {
++ return (CraftServer) Bukkit.getServer();
++ }
++
++ public abstract ResourceKey<LevelStem> getTypeKey();
++
++ protected Level(WritableLevelData worlddatamutable, ResourceKey<Level> resourcekey, RegistryAccess iregistrycustom, Holder<DimensionType> holder, Supplier<ProfilerFiller> supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env) {
++ this.generator = gen;
++ this.world = new CraftWorld((ServerLevel) this, gen, biomeProvider, env);
++
++ // CraftBukkit Ticks things
++ for (SpawnCategory spawnCategory : SpawnCategory.values()) {
++ if (CraftSpawnCategory.isValidForLimits(spawnCategory)) {
++ this.ticksPerSpawnCategory.put(spawnCategory, (long) this.getCraftServer().getTicksPerSpawns(spawnCategory));
++ }
++ }
++
++ // CraftBukkit end
+ this.profiler = supplier;
+ this.levelData = writableleveldata;
+ this.dimensionTypeRegistration = holder;
+@@ -133,13 +193,13 @@
+ @Override
+ @Override
+ public double getCenterX() {
+- return super.getCenterX() / dimensiontype.coordinateScale();
++ return super.getCenterX(); // CraftBukkit
+ }
+
+ @Override
+ @Override
+ public double getCenterZ() {
+- return super.getCenterZ() / dimensiontype.coordinateScale();
++ return super.getCenterZ(); // CraftBukkit
+ }
+ };
+ } else {
+@@ -150,8 +209,44 @@
+ this.biomeManager = new BiomeManager(this, i);
+ this.isDebug = flag1;
+ this.neighborUpdater = new CollectingNeighborUpdater(this, j);
+- this.registryAccess = registryaccess;
+- this.damageSources = new DamageSources(registryaccess);
++ this.registryAccess = iregistrycustom;
++ this.damageSources = new DamageSources(iregistrycustom);
++ // CraftBukkit start
++ getWorldBorder().world = (ServerLevel) this;
++ // From PlayerList.setPlayerFileData
++ getWorldBorder().addListener(new BorderChangeListener() {
++ @Override
++ public void onBorderSizeSet(WorldBorder border, double size) {
++ getCraftServer().getHandle().broadcastAll(new ClientboundSetBorderSizePacket(border), border.world);
++ }
++
++ @Override
++ public void onBorderSizeLerping(WorldBorder border, double oldSize, double d1, long newSize) {
++ getCraftServer().getHandle().broadcastAll(new ClientboundSetBorderLerpSizePacket(border), border.world);
++ }
++
++ @Override
++ public void onBorderCenterSet(WorldBorder border, double x, double d1) {
++ getCraftServer().getHandle().broadcastAll(new ClientboundSetBorderCenterPacket(border), border.world);
++ }
++
++ @Override
++ public void onBorderSetWarningTime(WorldBorder border, int warningTime) {
++ getCraftServer().getHandle().broadcastAll(new ClientboundSetBorderWarningDelayPacket(border), border.world);
++ }
++
++ @Override
++ public void onBorderSetWarningBlocks(WorldBorder border, int warningBlocks) {
++ getCraftServer().getHandle().broadcastAll(new ClientboundSetBorderWarningDistancePacket(border), border.world);
++ }
++
++ @Override
++ public void onBorderSetDamagePerBlock(WorldBorder border, double damagePerBlock) {}
++
++ @Override
++ public void onBorderSetDamageSafeZOne(WorldBorder border, double damageSafeZone) {}
++ });
++ // CraftBukkit end
+ }
+
+ @Override
+@@ -213,9 +303,19 @@
+ }
+
+ @Override
+- @Override
+- public boolean setBlock(BlockPos blockpos, BlockState blockstate, int i, int j) {
+- if (this.isOutsideBuildHeight(blockpos)) {
++ public boolean setBlock(BlockPos pos, IBlockData state, int flags, int recursionLeft) {
++ // CraftBukkit start - tree generation
++ if (this.captureTreeGeneration) {
++ CapturedBlockState blockstate = capturedBlockStates.get(pos);
++ if (blockstate == null) {
++ blockstate = CapturedBlockState.getTreeBlockState(this, pos, flags);
++ this.capturedBlockStates.put(pos.immutable(), blockstate);
++ }
++ blockstate.setData(state);
++ return true;
++ }
++ // CraftBukkit end
++ if (this.isOutsideBuildHeight(pos)) {
+ return false;
+ } else if (!this.isClientSide && this.isDebug()) {
+ return false;
+@@ -224,7 +323,23 @@
+ Block block = blockstate.getBlock();
+ BlockState blockstate1 = levelchunk.setBlockState(blockpos, blockstate, (i & 64) != 0);
+
+- if (blockstate1 == null) {
++ // CraftBukkit start - capture blockstates
++ boolean captured = false;
++ if (this.captureBlockStates && !this.capturedBlockStates.containsKey(pos)) {
++ CapturedBlockState blockstate = CapturedBlockState.getBlockState(this, pos, flags);
++ this.capturedBlockStates.put(pos.immutable(), blockstate);
++ captured = true;
++ }
++ // CraftBukkit end
++
++ IBlockData iblockdata1 = chunk.setBlockState(pos, state, (flags & 64) != 0, (flags & 1024) == 0); // CraftBukkit custom NO_PLACE flag
++
++ if (iblockdata1 == null) {
++ // CraftBukkit start - remove blockstate if failed (or the same)
++ if (this.captureBlockStates && captured) {
++ this.capturedBlockStates.remove(pos);
++ }
++ // CraftBukkit end
+ return false;
+ } else {
+ BlockState blockstate2 = this.getBlockState(blockpos);
+@@ -256,13 +373,69 @@
+ this.onBlockStateChange(blockpos, blockstate1, blockstate2);
+ }
+
++ // CraftBukkit start
++ if (!this.captureBlockStates) { // Don't notify clients or update physics while capturing blockstates
++ // Modularize client and physic updates
++ notifyAndUpdatePhysics(pos, chunk, iblockdata1, state, iblockdata2, flags, recursionLeft);
++ }
++ // CraftBukkit end
++
+ return true;
+ }
+ }
+ }
+
+- public void onBlockStateChange(BlockPos blockpos, BlockState blockstate, BlockState blockstate1) {}
++ // CraftBukkit start - Split off from above in order to directly send client and physic updates
++ public void notifyAndUpdatePhysics(BlockPos blockposition, LevelChunk chunk, IBlockData oldBlock, IBlockData newBlock, IBlockData actualBlock, int i, int j) {
++ IBlockData iblockdata = newBlock;
++ IBlockData iblockdata1 = oldBlock;
++ IBlockData iblockdata2 = actualBlock;
++ if (iblockdata2 == iblockdata) {
++ if (iblockdata1 != iblockdata2) {
++ this.setBlocksDirty(blockposition, iblockdata1, iblockdata2);
++ }
+
++ if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getFullStatus() != null && chunk.getFullStatus().isOrAfter(FullChunkStatus.BLOCK_TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement
++ this.sendBlockUpdated(blockposition, iblockdata1, iblockdata, i);
++ }
++
++ if ((i & 1) != 0) {
++ this.blockUpdated(blockposition, iblockdata1.getBlock());
++ if (!this.isClientSide && iblockdata.hasAnalogOutputSignal()) {
++ this.updateNeighbourForOutputSignal(blockposition, newBlock.getBlock());
++ }
++ }
++
++ if ((i & 16) == 0 && j > 0) {
++ int k = i & -34;
++
++ // CraftBukkit start
++ iblockdata1.updateIndirectNeighbourShapes(this, blockposition, k, j - 1); // Don't call an event for the old block to limit event spam
++ CraftWorld world = ((ServerLevel) this).getWorld();
++ if (world != null) {
++ BlockPhysicsEvent event = new BlockPhysicsEvent(world.getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()), CraftBlockData.fromData(iblockdata));
++ this.getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return;
++ }
++ }
++ // CraftBukkit end
++ iblockdata.updateNeighbourShapes(this, blockposition, k, j - 1);
++ iblockdata.updateIndirectNeighbourShapes(this, blockposition, k, j - 1);
++ }
++
++ // CraftBukkit start - SPIGOT-5710
++ if (!preventPoiUpdated) {
++ this.onBlockStateChange(blockposition, iblockdata1, iblockdata2);
++ }
++ // CraftBukkit end
++ }
++ }
++ // CraftBukkit end
++
++ public void onBlockStateChange(BlockPos pos, IBlockData blockState, IBlockData newState) {}
++
+ @Override
+ @Override
+ public boolean removeBlock(BlockPos blockpos, boolean flag) {
+@@ -350,9 +518,16 @@
+ }
+
+ @Override
+- @Override
+- public BlockState getBlockState(BlockPos blockpos) {
+- if (this.isOutsideBuildHeight(blockpos)) {
++ public IBlockData getBlockState(BlockPos pos) {
++ // CraftBukkit start - tree generation
++ if (captureTreeGeneration) {
++ CapturedBlockState previous = capturedBlockStates.get(pos);
++ if (previous != null) {
++ return previous.getHandle();
++ }
++ }
++ // CraftBukkit end
++ if (this.isOutsideBuildHeight(pos)) {
+ return Blocks.VOID_AIR.defaultBlockState();
+ } else {
+ LevelChunk levelchunk = this.getChunk(SectionPos.blockToSectionCoord(blockpos.getX()), SectionPos.blockToSectionCoord(blockpos.getZ()));
+@@ -559,16 +731,31 @@
+
+ @Nullable
+ @Override
+- @Override
+- public BlockEntity getBlockEntity(BlockPos blockpos) {
+- return this.isOutsideBuildHeight(blockpos) ? null : (!this.isClientSide && Thread.currentThread() != this.thread ? null : this.getChunkAt(blockpos).getBlockEntity(blockpos, LevelChunk.EntityCreationType.IMMEDIATE));
++ public BlockEntity getBlockEntity(BlockPos pos) {
++ // CraftBukkit start
++ return getBlockEntity(pos, true);
+ }
+
+- public void setBlockEntity(BlockEntity blockentity) {
+- BlockPos blockpos = blockentity.getBlockPos();
++ @Nullable
++ public BlockEntity getBlockEntity(BlockPos blockposition, boolean validate) {
++ if (capturedTileEntities.containsKey(blockposition)) {
++ return capturedTileEntities.get(blockposition);
++ }
++ // CraftBukkit end
++ return this.isOutsideBuildHeight(blockposition) ? null : (!this.isClientSide && Thread.currentThread() != this.thread ? null : this.getChunkAt(blockposition).getBlockEntity(blockposition, LevelChunk.EnumTileEntityState.IMMEDIATE));
++ }
+
+- if (!this.isOutsideBuildHeight(blockpos)) {
+- this.getChunkAt(blockpos).addAndRegisterBlockEntity(blockentity);
++ public void setBlockEntity(BlockEntity blockEntity) {
++ BlockPos blockposition = blockEntity.getBlockPos();
++
++ if (!this.isOutsideBuildHeight(blockposition)) {
++ // CraftBukkit start
++ if (captureBlockStates) {
++ capturedTileEntities.put(blockposition.immutable(), blockEntity);
++ return;
++ }
++ // CraftBukkit end
++ this.getChunkAt(blockposition).addAndRegisterBlockEntity(blockEntity);
+ }
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/LevelAccessor.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/LevelAccessor.java.patch
new file mode 100644
index 0000000000..39ce92c7b6
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/LevelAccessor.java.patch
@@ -0,0 +1,9 @@
+--- a/net/minecraft/world/level/LevelAccessor.java
++++ b/net/minecraft/world/level/LevelAccessor.java
+@@ -116,4 +114,6 @@
+ default void gameEvent(GameEvent gameevent, BlockPos blockpos, GameEvent.Context gameevent_context) {
+ this.gameEvent(gameevent, Vec3.atCenterOf(blockpos), gameevent_context);
+ }
++
++ net.minecraft.server.level.ServerLevel getMinecraftWorld(); // CraftBukkit
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/LevelWriter.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/LevelWriter.java.patch
new file mode 100644
index 0000000000..3008822ea1
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/LevelWriter.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/world/level/LevelWriter.java
++++ b/net/minecraft/world/level/LevelWriter.java
+@@ -28,4 +28,10 @@
+ default boolean addFreshEntity(Entity entity) {
+ return false;
+ }
++
++ // CraftBukkit start
++ default boolean addFreshEntity(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
++ return false;
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/NaturalSpawner.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/NaturalSpawner.java.patch
new file mode 100644
index 0000000000..068638be02
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/NaturalSpawner.java.patch
@@ -0,0 +1,101 @@
+--- a/net/minecraft/world/level/NaturalSpawner.java
++++ b/net/minecraft/world/level/NaturalSpawner.java
+@@ -48,6 +49,10 @@
+ import net.minecraft.world.level.pathfinder.PathComputationType;
+ import net.minecraft.world.phys.Vec3;
+ import org.slf4j.Logger;
++import org.bukkit.craftbukkit.util.CraftSpawnCategory;
++import org.bukkit.entity.SpawnCategory;
++import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
++// CraftBukkit end
+
+ public final class NaturalSpawner {
+
+@@ -113,15 +118,30 @@
+ MobCategory[] amobcategory = NaturalSpawner.SPAWNING_CATEGORIES;
+ int i = amobcategory.length;
+
++ LevelData worlddata = level.getLevelData(); // CraftBukkit - Other mob type spawn tick rate
++
+ for (int j = 0; j < i; ++j) {
+- MobCategory mobcategory = amobcategory[j];
++ MobCategory enumcreaturetype = aenumcreaturetype[j];
++ // CraftBukkit start - Use per-world spawn limits
++ boolean spawnThisTick = true;
++ int limit = enumcreaturetype.getMaxInstancesPerChunk();
++ SpawnCategory spawnCategory = CraftSpawnCategory.toBukkit(enumcreaturetype);
++ if (CraftSpawnCategory.isValidForLimits(spawnCategory)) {
++ spawnThisTick = level.ticksPerSpawnCategory.getLong(spawnCategory) != 0 && worlddata.getGameTime() % level.ticksPerSpawnCategory.getLong(spawnCategory) == 0;
++ limit = level.getWorld().getSpawnLimit(spawnCategory);
++ }
+
+ if ((flag || !mobcategory.isFriendly()) && (flag1 || mobcategory.isFriendly()) && (flag2 || !mobcategory.isPersistent()) && naturalspawner_spawnstate.canSpawnForCategory(mobcategory, levelchunk.getPos())) {
+ Objects.requireNonNull(naturalspawner_spawnstate);
+ NaturalSpawner.SpawnPredicate naturalspawner_spawnpredicate = naturalspawner_spawnstate::canSpawn;
+
+- Objects.requireNonNull(naturalspawner_spawnstate);
+- spawnCategoryForChunk(mobcategory, serverlevel, levelchunk, naturalspawner_spawnpredicate, naturalspawner_spawnstate::afterSpawn);
++ if ((spawnFriendlies || !enumcreaturetype.isFriendly()) && (spawnMonsters || enumcreaturetype.isFriendly()) && (forcedDespawn || !enumcreaturetype.isPersistent()) && spawnState.canSpawnForCategory(enumcreaturetype, chunk.getPos(), limit)) {
++ // CraftBukkit end
++ Objects.requireNonNull(spawnState);
++ NaturalSpawner.SpawnPredicate spawnercreature_c = spawnState::canSpawn;
++
++ Objects.requireNonNull(spawnState);
++ spawnCategoryForChunk(enumcreaturetype, level, chunk, spawnercreature_c, spawnState::afterSpawn);
+ }
+ }
+
+@@ -198,14 +218,19 @@
+ return;
+ }
+
+- mob.moveTo(d0, (double) i, d1, serverlevel.random.nextFloat() * 360.0F, 0.0F);
+- if (isValidPositionForMob(serverlevel, mob, d2)) {
+- spawngroupdata = mob.finalizeSpawn(serverlevel, serverlevel.getCurrentDifficultyAt(mob.blockPosition()), MobSpawnType.NATURAL, spawngroupdata, (CompoundTag) null);
+- ++j;
+- ++k1;
+- serverlevel.addFreshEntityWithPassengers(mob);
+- naturalspawner_afterspawncallback.run(mob, chunkaccess);
+- if (j >= mob.getMaxSpawnClusterSize()) {
++ entityinsentient.moveTo(d0, (double) i, d1, level.random.nextFloat() * 360.0F, 0.0F);
++ if (isValidPositionForMob(level, entityinsentient, d2)) {
++ groupdataentity = entityinsentient.finalizeSpawn(level, level.getCurrentDifficultyAt(entityinsentient.blockPosition()), EnumMobSpawn.NATURAL, groupdataentity, (CompoundTag) null);
++ // CraftBukkit start
++ // SPIGOT-7045: Give ocelot babies back their special spawn reason. Note: This is the only modification required as ocelots count as monsters which means they only spawn during normal chunk ticking and do not spawn during chunk generation as starter mobs.
++ level.addFreshEntityWithPassengers(entityinsentient, (entityinsentient instanceof net.minecraft.world.entity.animal.Ocelot && !((org.bukkit.entity.Ageable) entityinsentient.getBukkitEntity()).isAdult()) ? SpawnReason.OCELOT_BABY : SpawnReason.NATURAL);
++ if (!entityinsentient.isRemoved()) {
++ ++j;
++ ++k1;
++ callback.run(entityinsentient, chunk);
++ }
++ // CraftBukkit end
++ if (j >= entityinsentient.getMaxSpawnClusterSize()) {
+ return;
+ }
+
+@@ -388,9 +413,9 @@
+ if (entity instanceof Mob) {
+ Mob mob = (Mob) entity;
+
+- if (mob.checkSpawnRules(serverlevelaccessor, MobSpawnType.CHUNK_GENERATION) && mob.checkSpawnObstruction(serverlevelaccessor)) {
+- spawngroupdata = mob.finalizeSpawn(serverlevelaccessor, serverlevelaccessor.getCurrentDifficultyAt(mob.blockPosition()), MobSpawnType.CHUNK_GENERATION, spawngroupdata, (CompoundTag) null);
+- serverlevelaccessor.addFreshEntityWithPassengers(mob);
++ if (entityinsentient.checkSpawnRules(levelAccessor, EnumMobSpawn.CHUNK_GENERATION) && entityinsentient.checkSpawnObstruction(levelAccessor)) {
++ groupdataentity = entityinsentient.finalizeSpawn(levelAccessor, levelAccessor.getCurrentDifficultyAt(entityinsentient.blockPosition()), EnumMobSpawn.CHUNK_GENERATION, groupdataentity, (CompoundTag) null);
++ levelAccessor.addFreshEntityWithPassengers(entityinsentient, SpawnReason.CHUNK_GEN); // CraftBukkit
+ flag = true;
+ }
+ }
+@@ -511,8 +536,10 @@
+ return this.unmodifiableMobCategoryCounts;
+ }
+
+- boolean canSpawnForCategory(MobCategory mobcategory, ChunkPos chunkpos) {
+- int i = mobcategory.getMaxInstancesPerChunk() * this.spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER;
++ // CraftBukkit start
++ boolean canSpawnForCategory(MobCategory enumcreaturetype, ChunkPos chunkcoordintpair, int limit) {
++ int i = limit * this.spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER;
++ // CraftBukkit end
+
+ return this.mobCategoryCounts.getInt(mobcategory) >= i ? false : this.localMobCapCalculator.canSpawn(mobcategory, chunkpos);
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/ServerLevelAccessor.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/ServerLevelAccessor.java.patch
new file mode 100644
index 0000000000..5318edb2bd
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/ServerLevelAccessor.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/level/ServerLevelAccessor.java
++++ b/net/minecraft/world/level/ServerLevelAccessor.java
+@@ -8,6 +8,17 @@
+ ServerLevel getLevel();
+
+ default void addFreshEntityWithPassengers(Entity entity) {
+- entity.getSelfAndPassengers().forEach(this::addFreshEntity);
++ // CraftBukkit start
++ this.addFreshEntityWithPassengers(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
+ }
++
++ default void addFreshEntityWithPassengers(Entity entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
++ entity.getSelfAndPassengers().forEach((e) -> this.addFreshEntity(e, reason));
++ }
++
++ @Override
++ default ServerLevel getMinecraftWorld() {
++ return getLevel();
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/AbstractCandleBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/AbstractCandleBlock.java.patch
new file mode 100644
index 0000000000..43a86bef96
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/AbstractCandleBlock.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/level/block/AbstractCandleBlock.java
++++ b/net/minecraft/world/level/block/AbstractCandleBlock.java
+@@ -44,10 +43,14 @@
+ }
+
+ @Override
+- @Override
+- public void onProjectileHit(Level level, BlockState blockstate, BlockHitResult blockhitresult, Projectile projectile) {
+- if (!level.isClientSide && projectile.isOnFire() && this.canBeLit(blockstate)) {
+- setLit(level, blockstate, blockhitresult.getBlockPos(), true);
++ public void onProjectileHit(Level level, IBlockData state, BlockHitResult hit, Projectile projectile) {
++ if (!level.isClientSide && projectile.isOnFire() && this.canBeLit(state)) {
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(level, hit.getBlockPos(), projectile).isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
++ setLit(level, state, hit.getBlockPos(), true);
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BambooSaplingBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BambooSaplingBlock.java.patch
new file mode 100644
index 0000000000..1fe629ace2
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BambooSaplingBlock.java.patch
@@ -0,0 +1,12 @@
+--- a/net/minecraft/world/level/block/BambooSaplingBlock.java
++++ b/net/minecraft/world/level/block/BambooSaplingBlock.java
+@@ -104,7 +94,7 @@
+ return player.getMainHandItem().getItem() instanceof SwordItem ? 1.0F : super.getDestroyProgress(blockstate, player, blockgetter, blockpos);
+ }
+
+- protected void growBamboo(Level level, BlockPos blockpos) {
+- level.setBlock(blockpos.above(), (BlockState) Blocks.BAMBOO.defaultBlockState().setValue(BambooStalkBlock.LEAVES, BambooLeaves.SMALL), 3);
++ protected void growBamboo(Level level, BlockPos state) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, state, state.above(), (IBlockData) Blocks.BAMBOO.defaultBlockState().setValue(BambooStalkBlock.LEAVES, BambooLeaves.SMALL), 3); // CraftBukkit - BlockSpreadEvent
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BambooStalkBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BambooStalkBlock.java.patch
new file mode 100644
index 0000000000..4dcaa89ffe
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BambooStalkBlock.java.patch
@@ -0,0 +1,63 @@
+--- a/net/minecraft/world/level/block/BambooStalkBlock.java
++++ b/net/minecraft/world/level/block/BambooStalkBlock.java
+@@ -203,7 +187,7 @@
+ BlockPos blockpos1 = blockpos.above(i);
+ BlockState blockstate1 = serverlevel.getBlockState(blockpos1);
+
+- if (k >= 16 || (Integer) blockstate1.getValue(BambooStalkBlock.STAGE) == 1 || !serverlevel.isEmptyBlock(blockpos1.above())) {
++ if (k >= 16 || !iblockdata1.is(Blocks.BAMBOO) || (Integer) iblockdata1.getValue(BambooStalkBlock.STAGE) == 1 || !level.isEmptyBlock(blockposition1.above())) { // CraftBukkit - If the BlockSpreadEvent was cancelled, we have no bamboo here
+ return;
+ }
+
+@@ -220,19 +203,23 @@
+ return player.getMainHandItem().getItem() instanceof SwordItem ? 1.0F : super.getDestroyProgress(blockstate, player, blockgetter, blockpos);
+ }
+
+- protected void growBamboo(BlockState blockstate, Level level, BlockPos blockpos, RandomSource randomsource, int i) {
+- BlockState blockstate1 = level.getBlockState(blockpos.below());
+- BlockPos blockpos1 = blockpos.below(2);
+- BlockState blockstate2 = level.getBlockState(blockpos1);
+- BambooLeaves bambooleaves = BambooLeaves.NONE;
++ protected void growBamboo(IBlockData state, Level level, BlockPos pos, RandomSource random, int age) {
++ IBlockData iblockdata1 = level.getBlockState(pos.below());
++ BlockPos blockposition1 = pos.below(2);
++ IBlockData iblockdata2 = level.getBlockState(blockposition1);
++ BambooLeaves blockpropertybamboosize = BambooLeaves.NONE;
++ boolean shouldUpdateOthers = false; // CraftBukkit
+
+- if (i >= 1) {
+- if (blockstate1.is(Blocks.BAMBOO) && blockstate1.getValue(BambooStalkBlock.LEAVES) != BambooLeaves.NONE) {
+- if (blockstate1.is(Blocks.BAMBOO) && blockstate1.getValue(BambooStalkBlock.LEAVES) != BambooLeaves.NONE) {
+- bambooleaves = BambooLeaves.LARGE;
+- if (blockstate2.is(Blocks.BAMBOO)) {
+- level.setBlock(blockpos.below(), (BlockState) blockstate1.setValue(BambooStalkBlock.LEAVES, BambooLeaves.SMALL), 3);
+- level.setBlock(blockpos1, (BlockState) blockstate2.setValue(BambooStalkBlock.LEAVES, BambooLeaves.NONE), 3);
++ if (age >= 1) {
++ if (iblockdata1.is(Blocks.BAMBOO) && iblockdata1.getValue(BambooStalkBlock.LEAVES) != BambooLeaves.NONE) {
++ if (iblockdata1.is(Blocks.BAMBOO) && iblockdata1.getValue(BambooStalkBlock.LEAVES) != BambooLeaves.NONE) {
++ blockpropertybamboosize = BambooLeaves.LARGE;
++ if (iblockdata2.is(Blocks.BAMBOO)) {
++ // CraftBukkit start - moved down
++ // world.setBlock(blockposition.below(), (IBlockData) iblockdata1.setValue(BlockBamboo.LEAVES, BlockPropertyBambooSize.SMALL), 3);
++ // world.setBlock(blockposition1, (IBlockData) iblockdata2.setValue(BlockBamboo.LEAVES, BlockPropertyBambooSize.NONE), 3);
++ shouldUpdateOthers = true;
++ // CraftBukkit end
+ }
+ }
+ } else {
+@@ -243,7 +230,14 @@
+ int j = (Integer) blockstate.getValue(BambooStalkBlock.AGE) != 1 && !blockstate2.is(Blocks.BAMBOO) ? 0 : 1;
+ int k = (i < 11 || randomsource.nextFloat() >= 0.25F) && i != 15 ? 0 : 1;
+
+- level.setBlock(blockpos.above(), (BlockState) ((BlockState) ((BlockState) this.defaultBlockState().setValue(BambooStalkBlock.AGE, j)).setValue(BambooStalkBlock.LEAVES, bambooleaves)).setValue(BambooStalkBlock.STAGE, k), 3);
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, pos.above(), (IBlockData) ((IBlockData) ((IBlockData) this.defaultBlockState().setValue(BambooStalkBlock.AGE, j)).setValue(BambooStalkBlock.LEAVES, blockpropertybamboosize)).setValue(BambooStalkBlock.STAGE, k), 3)) {
++ if (shouldUpdateOthers) {
++ level.setBlock(pos.below(), (IBlockData) iblockdata1.setValue(BambooStalkBlock.LEAVES, BambooLeaves.SMALL), 3);
++ level.setBlock(blockposition1, (IBlockData) iblockdata2.setValue(BambooStalkBlock.LEAVES, BambooLeaves.NONE), 3);
++ }
++ }
++ // CraftBukkit end
+ }
+
+ protected int getHeightAboveUpToMax(BlockGetter blockgetter, BlockPos blockpos) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BaseFireBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BaseFireBlock.java.patch
new file mode 100644
index 0000000000..58464d61d0
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BaseFireBlock.java.patch
@@ -0,0 +1,50 @@
+--- a/net/minecraft/world/level/block/BaseFireBlock.java
++++ b/net/minecraft/world/level/block/BaseFireBlock.java
+@@ -132,7 +127,14 @@
+ if (!entity.fireImmune()) {
+ entity.setRemainingFireTicks(entity.getRemainingFireTicks() + 1);
+ if (entity.getRemainingFireTicks() == 0) {
+- entity.setSecondsOnFire(8);
++ // CraftBukkit start
++ org.bukkit.event.entity.EntityCombustEvent event = new org.bukkit.event.entity.EntityCombustByBlockEvent(org.bukkit.craftbukkit.block.CraftBlock.at(level, pos), entity.getBukkitEntity(), 8);
++ level.getCraftServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ entity.setSecondsOnFire(event.getDuration(), false);
++ }
++ // CraftBukkit end
+ }
+ }
+
+@@ -153,15 +154,15 @@
+ }
+ }
+
+- if (!blockstate.canSurvive(level, blockpos)) {
+- level.removeBlock(blockpos, false);
++ if (!state.canSurvive(level, pos)) {
++ fireExtinguished(level, pos); // CraftBukkit - fuel block broke
+ }
+
+ }
+ }
+
+ private static boolean inPortalDimension(Level level) {
+- return level.dimension() == Level.OVERWORLD || level.dimension() == Level.NETHER;
++ return level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.OVERWORLD || level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.NETHER; // CraftBukkit - getTypeKey()
+ }
+
+ @Override
+@@ -211,4 +210,12 @@
+ }
+ }
+ }
++
++ // CraftBukkit start
++ protected void fireExtinguished(net.minecraft.world.level.LevelAccessor world, BlockPos position) {
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(world, position, Blocks.AIR.defaultBlockState()).isCancelled()) {
++ world.removeBlock(position, false);
++ }
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BasePressurePlateBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BasePressurePlateBlock.java.patch
new file mode 100644
index 0000000000..18de1cf8fe
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BasePressurePlateBlock.java.patch
@@ -0,0 +1,48 @@
+--- a/net/minecraft/world/level/block/BasePressurePlateBlock.java
++++ b/net/minecraft/world/level/block/BasePressurePlateBlock.java
+@@ -21,6 +21,7 @@
+ import net.minecraft.world.phys.AABB;
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit
+
+ public abstract class BasePressurePlateBlock extends Block {
+
+@@ -97,13 +91,15 @@
+ boolean flag = i > 0;
+ boolean flag1 = j > 0;
+
+- if (i != j) {
+- BlockState blockstate1 = this.setSignalForState(blockstate, j);
++ // CraftBukkit start - Interact Pressure Plate
++ org.bukkit.World bworld = level.getWorld();
++ org.bukkit.plugin.PluginManager manager = level.getCraftServer().getPluginManager();
+
+ level.setBlock(blockpos, blockstate1, 2);
+ this.updateNeighbours(level, blockpos);
+ level.setBlocksDirty(blockpos, blockstate, blockstate1);
+ }
++ // CraftBukkit end
+
+ if (!flag1 && flag) {
+ level.playSound((Player) null, blockpos, this.type.pressurePlateClickOff(), SoundSource.BLOCKS);
+@@ -154,10 +157,16 @@
+ return true;
+ }
+
+- protected static int getEntityCount(Level level, AABB aabb, Class<? extends Entity> oclass) {
+- return level.getEntitiesOfClass(oclass, aabb, EntitySelector.NO_SPECTATORS.and((entity) -> {
++ protected static int getEntityCount(Level level, AABB box, Class<? extends Entity> entityClass) {
++ // CraftBukkit start
++ return getEntities(level, box, entityClass).size();
++ }
++
++ protected static <T extends Entity> java.util.List<T> getEntities(Level world, AABB axisalignedbb, Class<T> oclass) {
++ // CraftBukkit end
++ return world.getEntitiesOfClass(oclass, axisalignedbb, EntitySelector.NO_SPECTATORS.and((entity) -> {
+ return !entity.isIgnoringBlockTriggers();
+- })).size();
++ })); // CraftBukkit
+ }
+
+ protected abstract int getSignalStrength(Level level, BlockPos pos);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BedBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BedBlock.java.patch
new file mode 100644
index 0000000000..ec6b4b8138
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BedBlock.java.patch
@@ -0,0 +1,87 @@
+--- a/net/minecraft/world/level/block/BedBlock.java
++++ b/net/minecraft/world/level/block/BedBlock.java
+@@ -96,9 +94,10 @@
+ }
+ }
+
+- if (!canSetSpawn(level)) {
+- level.removeBlock(blockpos, false);
+- BlockPos blockpos1 = blockpos.relative(((Direction) blockstate.getValue(BedBlock.FACING)).getOpposite());
++ // CraftBukkit - moved world and biome check into EntityHuman
++ if (false && !canSetSpawn(level)) {
++ level.removeBlock(pos, false);
++ BlockPos blockposition1 = pos.relative(((Direction) state.getValue(BedBlock.FACING)).getOpposite());
+
+ if (level.getBlockState(blockpos1).is((Block) this)) {
+ level.removeBlock(blockpos1, false);
+@@ -115,9 +114,18 @@
+
+ return InteractionResult.SUCCESS;
+ } else {
+- player.startSleepInBed(blockpos).ifLeft((player_bedsleepingproblem) -> {
+- if (player_bedsleepingproblem.getMessage() != null) {
+- player.displayClientMessage(player_bedsleepingproblem.getMessage(), true);
++ // CraftBukkit start
++ IBlockData finaliblockdata = state;
++ BlockPos finalblockposition = pos;
++ // CraftBukkit end
++ player.startSleepInBed(pos).ifLeft((entityhuman_enumbedresult) -> {
++ // CraftBukkit start - handling bed explosion from below here
++ if (!level.dimensionType().bedWorks()) {
++ this.explodeBed(finaliblockdata, level, finalblockposition);
++ } else
++ // CraftBukkit end
++ if (entityhuman_enumbedresult.getMessage() != null) {
++ player.displayClientMessage(entityhuman_enumbedresult.getMessage(), true);
+ }
+
+ });
+@@ -126,8 +134,29 @@
+ }
+ }
+
++ // CraftBukkit start
++ private InteractionResult explodeBed(IBlockData iblockdata, Level world, BlockPos blockposition) {
++ {
++ {
++ world.removeBlock(blockposition, false);
++ BlockPos blockposition1 = blockposition.relative(((Direction) iblockdata.getValue(BedBlock.FACING)).getOpposite());
++
++ if (world.getBlockState(blockposition1).getBlock() == this) {
++ world.removeBlock(blockposition1, false);
++ }
++
++ Vec3 vec3d = blockposition.getCenter();
++
++ world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.a.BLOCK);
++ return InteractionResult.SUCCESS;
++ }
++ }
++ }
++ // CraftBukkit end
++
+ public static boolean canSetSpawn(Level level) {
+- return level.dimensionType().bedWorks();
++ // CraftBukkit - moved world and biome check into EntityHuman
++ return true || level.dimensionType().bedWorks();
+ }
+
+ private boolean kickVillagerOutOfBed(Level level, BlockPos blockpos) {
+@@ -335,9 +354,14 @@
+ if (!level.isClientSide) {
+ BlockPos blockpos1 = blockpos.relative((Direction) blockstate.getValue(BedBlock.FACING));
+
+- level.setBlock(blockpos1, (BlockState) blockstate.setValue(BedBlock.PART, BedPart.HEAD), 3);
+- level.blockUpdated(blockpos, Blocks.AIR);
+- blockstate.updateNeighbourShapes(level, blockpos, 3);
++ level.setBlock(blockposition1, (IBlockData) state.setValue(BedBlock.PART, BedPart.HEAD), 3);
++ // CraftBukkit start - SPIGOT-7315: Don't updated if we capture block states
++ if (level.captureBlockStates) {
++ return;
++ }
++ // CraftBukkit end
++ level.blockUpdated(pos, Blocks.AIR);
++ state.updateNeighbourShapes(level, pos, 3);
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BeehiveBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BeehiveBlock.java.patch
new file mode 100644
index 0000000000..14ce6be3f8
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BeehiveBlock.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/level/block/BeehiveBlock.java
++++ b/net/minecraft/world/level/block/BeehiveBlock.java
+@@ -123,7 +119,7 @@
+ if (bee.getTarget() == null) {
+ Player player = (Player) Util.getRandom(list1, level.random);
+
+- bee.setTarget(player);
++ entitybee.setTarget(entityhuman, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER, true); // CraftBukkit
+ }
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BellBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BellBlock.java.patch
new file mode 100644
index 0000000000..c8af995332
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BellBlock.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/world/level/block/BellBlock.java
++++ b/net/minecraft/world/level/block/BellBlock.java
+@@ -150,6 +146,11 @@
+ if (direction == null) {
+ direction = (Direction) level.getBlockState(blockpos).getValue(BellBlock.FACING);
+ }
++ // CraftBukkit start
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBellRingEvent(level, pos, direction, entity)) {
++ return false;
++ }
++ // CraftBukkit end
+
+ ((BellBlockEntity) blockentity).onHit(direction);
+ level.playSound((Player) null, blockpos, SoundEvents.BELL_BLOCK, SoundSource.BLOCKS, 2.0F, 1.0F);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BigDripleafBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BigDripleafBlock.java.patch
new file mode 100644
index 0000000000..885aef8a5f
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BigDripleafBlock.java.patch
@@ -0,0 +1,115 @@
+--- a/net/minecraft/world/level/block/BigDripleafBlock.java
++++ b/net/minecraft/world/level/block/BigDripleafBlock.java
+@@ -41,6 +41,10 @@
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.Shapes;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityInteractEvent;
++// CraftBukkit end
+
+ public class BigDripleafBlock extends HorizontalDirectionalBlock implements BonemealableBlock, SimpleWaterloggedBlock {
+
+@@ -116,9 +119,8 @@
+ }
+
+ @Override
+- @Override
+- public void onProjectileHit(Level level, BlockState blockstate, BlockHitResult blockhitresult, Projectile projectile) {
+- this.setTiltAndScheduleTick(blockstate, level, blockhitresult.getBlockPos(), Tilt.FULL, SoundEvents.BIG_DRIPLEAF_TILT_DOWN);
++ public void onProjectileHit(Level level, IBlockData state, BlockHitResult hit, Projectile projectile) {
++ this.setTiltAndScheduleTick(state, level, hit.getBlockPos(), Tilt.FULL, SoundEvents.BIG_DRIPLEAF_TILT_DOWN, projectile); // CraftBukkit
+ }
+
+ @Override
+@@ -183,8 +178,21 @@
+ @Override
+ public void entityInside(BlockState blockstate, Level level, BlockPos blockpos, Entity entity) {
+ if (!level.isClientSide) {
+- if (blockstate.getValue(BigDripleafBlock.TILT) == Tilt.NONE && canEntityTilt(blockpos, entity) && !level.hasNeighborSignal(blockpos)) {
+- this.setTiltAndScheduleTick(blockstate, level, blockpos, Tilt.UNSTABLE, (SoundEvent) null);
++ if (state.getValue(BigDripleafBlock.TILT) == Tilt.NONE && canEntityTilt(pos, entity) && !level.hasNeighborSignal(pos)) {
++ // CraftBukkit start - tilt dripleaf
++ org.bukkit.event.Cancellable cancellable;
++ if (entity instanceof Player) {
++ cancellable = CraftEventFactory.callPlayerInteractEvent((Player) entity, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null);
++ } else {
++ cancellable = new EntityInteractEvent(entity.getBukkitEntity(), level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()));
++ level.getCraftServer().getPluginManager().callEvent((EntityInteractEvent) cancellable);
++ }
++
++ if (cancellable.isCancelled()) {
++ return;
++ }
++ this.setTiltAndScheduleTick(state, level, pos, Tilt.UNSTABLE, (SoundEvent) null, entity);
++ // CraftBukkit end
+ }
+
+ }
+@@ -199,9 +206,9 @@
+ Tilt tilt = (Tilt) blockstate.getValue(BigDripleafBlock.TILT);
+
+ if (tilt == Tilt.UNSTABLE) {
+- this.setTiltAndScheduleTick(blockstate, serverlevel, blockpos, Tilt.PARTIAL, SoundEvents.BIG_DRIPLEAF_TILT_DOWN);
++ this.setTiltAndScheduleTick(state, level, pos, Tilt.PARTIAL, SoundEvents.BIG_DRIPLEAF_TILT_DOWN, null); // CraftBukkit
+ } else if (tilt == Tilt.PARTIAL) {
+- this.setTiltAndScheduleTick(blockstate, serverlevel, blockpos, Tilt.FULL, SoundEvents.BIG_DRIPLEAF_TILT_DOWN);
++ this.setTiltAndScheduleTick(state, level, pos, Tilt.FULL, SoundEvents.BIG_DRIPLEAF_TILT_DOWN, null); // CraftBukkit
+ } else if (tilt == Tilt.FULL) {
+ resetTilt(blockstate, serverlevel, blockpos);
+ }
+@@ -228,10 +234,12 @@
+ return entity.onGround() && entity.position().y > (double) ((float) blockpos.getY() + 0.6875F);
+ }
+
+- private void setTiltAndScheduleTick(BlockState blockstate, Level level, BlockPos blockpos, Tilt tilt, @Nullable SoundEvent soundevent) {
+- setTilt(blockstate, level, blockpos, tilt);
+- if (soundevent != null) {
+- playTiltSound(level, blockpos, soundevent);
++ // CraftBukkit start
++ private void setTiltAndScheduleTick(IBlockData iblockdata, Level world, BlockPos blockposition, Tilt tilt, @Nullable SoundEvent soundeffect, @Nullable Entity entity) {
++ if (!setTilt(iblockdata, world, blockposition, tilt, entity)) return;
++ // CraftBukkit end
++ if (soundeffect != null) {
++ playTiltSound(world, blockposition, soundeffect);
+ }
+
+ int i = BigDripleafBlock.DELAY_UNTIL_NEXT_TILT_STATE.getInt(tilt);
+@@ -242,22 +250,30 @@
+
+ }
+
+- private static void resetTilt(BlockState blockstate, Level level, BlockPos blockpos) {
+- setTilt(blockstate, level, blockpos, Tilt.NONE);
+- if (blockstate.getValue(BigDripleafBlock.TILT) != Tilt.NONE) {
+- playTiltSound(level, blockpos, SoundEvents.BIG_DRIPLEAF_TILT_UP);
++ private static void resetTilt(IBlockData state, Level level, BlockPos pos) {
++ setTilt(state, level, pos, Tilt.NONE, null); // CraftBukkit
++ if (state.getValue(BigDripleafBlock.TILT) != Tilt.NONE) {
++ playTiltSound(level, pos, SoundEvents.BIG_DRIPLEAF_TILT_UP);
+ }
+
+ }
+
+- private static void setTilt(BlockState blockstate, Level level, BlockPos blockpos, Tilt tilt) {
+- Tilt tilt1 = (Tilt) blockstate.getValue(BigDripleafBlock.TILT);
++ // CraftBukkit start
++ private static boolean setTilt(IBlockData iblockdata, Level world, BlockPos blockposition, Tilt tilt, @Nullable Entity entity) {
++ if (entity != null) {
++ if (!CraftEventFactory.callEntityChangeBlockEvent(entity, blockposition, iblockdata.setValue(BigDripleafBlock.TILT, tilt))) {
++ return false;
++ }
++ }
++ // CraftBukkit end
++ Tilt tilt1 = (Tilt) iblockdata.getValue(BigDripleafBlock.TILT);
+
+ level.setBlock(blockpos, (BlockState) blockstate.setValue(BigDripleafBlock.TILT, tilt), 2);
+ if (tilt.causesVibration() && tilt != tilt1) {
+ level.gameEvent((Entity) null, GameEvent.BLOCK_CHANGE, blockpos);
+ }
+
++ return true; // CraftBukkit
+ }
+
+ @Override
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/Block.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/Block.java.patch
new file mode 100644
index 0000000000..9f70065941
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/Block.java.patch
@@ -0,0 +1,52 @@
+--- a/net/minecraft/world/level/block/Block.java
++++ b/net/minecraft/world/level/block/Block.java
+@@ -351,8 +348,14 @@
+ if (!level.isClientSide && !itemstack.isEmpty() && level.getGameRules().getBoolean(GameRules.RULE_DOBLOCKDROPS)) {
+ ItemEntity itementity = (ItemEntity) supplier.get();
+
+- itementity.setDefaultPickUpDelay();
+- level.addFreshEntity(itementity);
++ entityitem.setDefaultPickUpDelay();
++ // CraftBukkit start
++ if (level.captureDrops != null) {
++ level.captureDrops.add(entityitem);
++ } else {
++ level.addFreshEntity(entityitem);
++ }
++ // CraftBukkit end
+ }
+ }
+
+@@ -378,8 +381,8 @@
+
+ public void playerDestroy(Level level, Player player, BlockPos blockpos, BlockState blockstate, @Nullable BlockEntity blockentity, ItemStack itemstack) {
+ player.awardStat(Stats.BLOCK_MINED.get(this));
+- player.causeFoodExhaustion(0.005F);
+- dropResources(blockstate, level, blockpos, blockentity, player, itemstack);
++ player.causeFoodExhaustion(0.005F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.BLOCK_MINED); // CraftBukkit - EntityExhaustionEvent
++ dropResources(state, level, pos, blockEntity, player, tool);
+ }
+
+ public void setPlacedBy(Level level, BlockPos blockpos, BlockState blockstate, @Nullable LivingEntity livingentity, ItemStack itemstack) {}
+@@ -518,7 +518,8 @@
+ return this.builtInRegistryHolder;
+ }
+
+- protected void tryDropExperience(ServerLevel serverlevel, BlockPos blockpos, ItemStack itemstack, IntProvider intprovider) {
++ // CraftBukkit start
++ protected int tryDropExperience(ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, IntProvider intprovider) {
+ if (EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, itemstack) == 0) {
+ int i = intprovider.sample(serverlevel.random);
+
+@@ -529,6 +532,11 @@
+
+ }
+
++ public int getExpDrop(IBlockData iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) {
++ return 0;
++ }
++ // CraftBukkit end
++
+ public static final class BlockStatePairKey {
+
+ private final BlockState first;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BuddingAmethystBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BuddingAmethystBlock.java.patch
new file mode 100644
index 0000000000..3f4fa951dd
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BuddingAmethystBlock.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/level/block/BuddingAmethystBlock.java
++++ b/net/minecraft/world/level/block/BuddingAmethystBlock.java
+@@ -47,7 +45,7 @@
+ if (block != null) {
+ BlockState blockstate2 = (BlockState) ((BlockState) block.defaultBlockState().setValue(AmethystClusterBlock.FACING, direction)).setValue(AmethystClusterBlock.WATERLOGGED, blockstate1.getFluidState().getType() == Fluids.WATER);
+
+- serverlevel.setBlockAndUpdate(blockpos1, blockstate2);
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, blockposition1, iblockdata2); // CraftBukkit
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BushBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BushBlock.java.patch
new file mode 100644
index 0000000000..eb1a377017
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/BushBlock.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/level/block/BushBlock.java
++++ b/net/minecraft/world/level/block/BushBlock.java
+@@ -26,9 +25,15 @@
+ }
+
+ @Override
+- @Override
+- public BlockState updateShape(BlockState blockstate, Direction direction, BlockState blockstate1, LevelAccessor levelaccessor, BlockPos blockpos, BlockPos blockpos1) {
+- return !blockstate.canSurvive(levelaccessor, blockpos) ? Blocks.AIR.defaultBlockState() : super.updateShape(blockstate, direction, blockstate1, levelaccessor, blockpos, blockpos1);
++ public IBlockData updateShape(IBlockData state, Direction facing, IBlockData facingState, LevelAccessor level, BlockPos currentPos, BlockPos facingPos) {
++ // CraftBukkit start
++ if (!state.canSurvive(level, currentPos)) {
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(level, currentPos).isCancelled()) {
++ return Blocks.AIR.defaultBlockState();
++ }
++ }
++ return super.updateShape(state, facing, facingState, level, currentPos, facingPos);
++ // CraftBukkit end
+ }
+
+ @Override
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ButtonBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ButtonBlock.java.patch
new file mode 100644
index 0000000000..8fe77b2e7d
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ButtonBlock.java.patch
@@ -0,0 +1,80 @@
+--- a/net/minecraft/world/level/block/ButtonBlock.java
++++ b/net/minecraft/world/level/block/ButtonBlock.java
+@@ -32,6 +32,10 @@
+ import net.minecraft.world.phys.BlockHitResult;
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++// CraftBukkit start
++import org.bukkit.event.block.BlockRedstoneEvent;
++import org.bukkit.event.entity.EntityInteractEvent;
++// CraftBukkit end
+
+ public class ButtonBlock extends FaceAttachedHorizontalDirectionalBlock {
+
+@@ -127,9 +128,22 @@
+ if ((Boolean) blockstate.getValue(ButtonBlock.POWERED)) {
+ return InteractionResult.CONSUME;
+ } else {
+- this.press(blockstate, level, blockpos);
+- this.playSound(player, level, blockpos, true);
+- level.gameEvent((Entity) player, GameEvent.BLOCK_ACTIVATE, blockpos);
++ // CraftBukkit start
++ boolean powered = ((Boolean) state.getValue(POWERED));
++ org.bukkit.block.Block block = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++ int old = (powered) ? 15 : 0;
++ int current = (!powered) ? 15 : 0;
++
++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(block, old, current);
++ level.getCraftServer().getPluginManager().callEvent(eventRedstone);
++
++ if ((eventRedstone.getNewCurrent() > 0) != (!powered)) {
++ return InteractionResult.SUCCESS;
++ }
++ // CraftBukkit end
++ this.press(state, level, pos);
++ this.playSound(player, level, pos, true);
++ level.gameEvent((Entity) player, GameEvent.BLOCK_ACTIVATE, pos);
+ return InteractionResult.sidedSuccess(level.isClientSide);
+ }
+ }
+@@ -209,11 +216,36 @@
+ boolean flag = abstractarrow != null;
+ boolean flag1 = (Boolean) blockstate.getValue(ButtonBlock.POWERED);
+
++ // CraftBukkit start - Call interact event when arrows turn on wooden buttons
++ if (flag1 != flag && flag) {
++ org.bukkit.block.Block block = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++ EntityInteractEvent event = new EntityInteractEvent(entityarrow.getBukkitEntity(), block);
++ level.getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return;
++ }
++ }
++ // CraftBukkit end
++
+ if (flag != flag1) {
+- level.setBlock(blockpos, (BlockState) blockstate.setValue(ButtonBlock.POWERED, flag), 3);
+- this.updateNeighbours(blockstate, level, blockpos);
+- this.playSound((Player) null, level, blockpos, flag);
+- level.gameEvent((Entity) abstractarrow, flag ? GameEvent.BLOCK_ACTIVATE : GameEvent.BLOCK_DEACTIVATE, blockpos);
++ // CraftBukkit start
++ boolean powered = flag1;
++ org.bukkit.block.Block block = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++ int old = (powered) ? 15 : 0;
++ int current = (!powered) ? 15 : 0;
++
++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(block, old, current);
++ level.getCraftServer().getPluginManager().callEvent(eventRedstone);
++
++ if ((flag && eventRedstone.getNewCurrent() <= 0) || (!flag && eventRedstone.getNewCurrent() > 0)) {
++ return;
++ }
++ // CraftBukkit end
++ level.setBlock(pos, (IBlockData) state.setValue(ButtonBlock.POWERED, flag), 3);
++ this.updateNeighbours(state, level, pos);
++ this.playSound((Player) null, level, pos, flag);
++ level.gameEvent((Entity) entityarrow, flag ? GameEvent.BLOCK_ACTIVATE : GameEvent.BLOCK_DEACTIVATE, pos);
+ }
+
+ if (flag) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CactusBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CactusBlock.java.patch
new file mode 100644
index 0000000000..584e14001b
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CactusBlock.java.patch
@@ -0,0 +1,34 @@
+--- a/net/minecraft/world/level/block/CactusBlock.java
++++ b/net/minecraft/world/level/block/CactusBlock.java
+@@ -21,6 +21,7 @@
+ import net.minecraft.world.level.pathfinder.PathComputationType;
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
+
+ public class CactusBlock extends Block {
+
+@@ -67,8 +65,8 @@
+ int j = (Integer) blockstate.getValue(CactusBlock.AGE);
+
+ if (j == 15) {
+- serverlevel.setBlockAndUpdate(blockpos1, this.defaultBlockState());
+- BlockState blockstate1 = (BlockState) blockstate.setValue(CactusBlock.AGE, 0);
++ CraftEventFactory.handleBlockGrowEvent(level, blockposition1, this.defaultBlockState()); // CraftBukkit
++ IBlockData iblockdata1 = (IBlockData) state.setValue(CactusBlock.AGE, 0);
+
+ serverlevel.setBlock(blockpos, blockstate1, 4);
+ serverlevel.neighborChanged(blockstate1, blockpos1, this, blockpos, false);
+@@ -125,9 +119,10 @@
+ }
+
+ @Override
+- @Override
+- public void entityInside(BlockState blockstate, Level level, BlockPos blockpos, Entity entity) {
++ public void entityInside(IBlockData state, Level level, BlockPos pos, Entity entity) {
++ CraftEventFactory.blockDamage = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); // CraftBukkit
+ entity.hurt(level.damageSources().cactus(), 1.0F);
++ CraftEventFactory.blockDamage = null; // CraftBukkit
+ }
+
+ @Override
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CakeBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CakeBlock.java.patch
new file mode 100644
index 0000000000..8da72b433f
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CakeBlock.java.patch
@@ -0,0 +1,27 @@
+--- a/net/minecraft/world/level/block/CakeBlock.java
++++ b/net/minecraft/world/level/block/CakeBlock.java
+@@ -95,10 +92,21 @@
+ return InteractionResult.PASS;
+ } else {
+ player.awardStat(Stats.EAT_CAKE_SLICE);
+- player.getFoodData().eat(2, 0.1F);
+- int i = (Integer) blockstate.getValue(CakeBlock.BITES);
++ // CraftBukkit start
++ // entityhuman.getFoodData().eat(2, 0.1F);
++ int oldFoodLevel = player.getFoodData().foodLevel;
+
+- levelaccessor.gameEvent((Entity) player, GameEvent.EAT, blockpos);
++ org.bukkit.event.entity.FoodLevelChangeEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callFoodLevelChangeEvent(player, 2 + oldFoodLevel);
++
++ if (!event.isCancelled()) {
++ player.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, 0.1F);
++ }
++
++ ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity().sendHealthUpdate();
++ // CraftBukkit end
++ int i = (Integer) state.getValue(CakeBlock.BITES);
++
++ level.gameEvent((Entity) player, GameEvent.EAT, pos);
+ if (i < 6) {
+ levelaccessor.setBlock(blockpos, (BlockState) blockstate.setValue(CakeBlock.BITES, i + 1), 3);
+ } else {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CampfireBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CampfireBlock.java.patch
new file mode 100644
index 0000000000..1fc979024f
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CampfireBlock.java.patch
@@ -0,0 +1,43 @@
+--- a/net/minecraft/world/level/block/CampfireBlock.java
++++ b/net/minecraft/world/level/block/CampfireBlock.java
+@@ -49,6 +49,9 @@
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.Shapes;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++// CraftBukkit start
++import org.bukkit.craftbukkit.block.CraftBlock;
++// CraftBukkit end
+
+ public class CampfireBlock extends BaseEntityBlock implements SimpleWaterloggedBlock {
+
+@@ -106,10 +107,11 @@
+ }
+
+ @Override
+- @Override
+- public void entityInside(BlockState blockstate, Level level, BlockPos blockpos, Entity entity) {
+- if ((Boolean) blockstate.getValue(CampfireBlock.LIT) && entity instanceof LivingEntity && !EnchantmentHelper.hasFrostWalker((LivingEntity) entity)) {
++ public void entityInside(IBlockData state, Level level, BlockPos pos, Entity entity) {
++ if ((Boolean) state.getValue(CampfireBlock.LIT) && entity instanceof LivingEntity && !EnchantmentHelper.hasFrostWalker((LivingEntity) entity)) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = CraftBlock.at(level, pos); // CraftBukkit
+ entity.hurt(level.damageSources().inFire(), (float) this.fireDamage);
++ org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = null; // CraftBukkit
+ }
+
+ super.entityInside(blockstate, level, blockpos, entity);
+@@ -226,8 +220,13 @@
+ public void onProjectileHit(Level level, BlockState blockstate, BlockHitResult blockhitresult, Projectile projectile) {
+ BlockPos blockpos = blockhitresult.getBlockPos();
+
+- if (!level.isClientSide && projectile.isOnFire() && projectile.mayInteract(level, blockpos) && !(Boolean) blockstate.getValue(CampfireBlock.LIT) && !(Boolean) blockstate.getValue(CampfireBlock.WATERLOGGED)) {
+- level.setBlock(blockpos, (BlockState) blockstate.setValue(BlockStateProperties.LIT, true), 11);
++ if (!level.isClientSide && projectile.isOnFire() && projectile.mayInteract(level, blockposition) && !(Boolean) state.getValue(CampfireBlock.LIT) && !(Boolean) state.getValue(CampfireBlock.WATERLOGGED)) {
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(level, blockposition, projectile).isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
++ level.setBlock(blockposition, (IBlockData) state.setValue(BlockStateProperties.LIT, true), 11);
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CarvedPumpkinBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CarvedPumpkinBlock.java.patch
new file mode 100644
index 0000000000..5da2390b07
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CarvedPumpkinBlock.java.patch
@@ -0,0 +1,34 @@
+--- a/net/minecraft/world/level/block/CarvedPumpkinBlock.java
++++ b/net/minecraft/world/level/block/CarvedPumpkinBlock.java
+@@ -23,6 +23,9 @@
+ import net.minecraft.world.level.block.state.pattern.BlockPatternBuilder;
+ import net.minecraft.world.level.block.state.predicate.BlockStatePredicate;
+ import net.minecraft.world.level.block.state.properties.DirectionProperty;
++// CraftBukkit start
++import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
++// CraftBukkit end
+
+ public class CarvedPumpkinBlock extends HorizontalDirectionalBlock {
+
+@@ -87,11 +88,16 @@
+
+ }
+
+- private static void spawnGolemInWorld(Level level, BlockPattern.BlockPatternMatch blockpattern_blockpatternmatch, Entity entity, BlockPos blockpos) {
+- clearPatternBlocks(level, blockpattern_blockpatternmatch);
+- entity.moveTo((double) blockpos.getX() + 0.5D, (double) blockpos.getY() + 0.05D, (double) blockpos.getZ() + 0.5D, 0.0F, 0.0F);
+- level.addFreshEntity(entity);
+- Iterator iterator = level.getEntitiesOfClass(ServerPlayer.class, entity.getBoundingBox().inflate(5.0D)).iterator();
++ private static void spawnGolemInWorld(Level level, BlockPattern.BlockPatternMatch patternMatch, Entity golem, BlockPos pos) {
++ // clearPatternBlocks(world, shapedetector_shapedetectorcollection); // CraftBukkit - moved down
++ golem.moveTo((double) pos.getX() + 0.5D, (double) pos.getY() + 0.05D, (double) pos.getZ() + 0.5D, 0.0F, 0.0F);
++ // CraftBukkit start
++ if (!level.addFreshEntity(golem, SpawnReason.BUILD_IRONGOLEM)) {
++ return;
++ }
++ clearPatternBlocks(level, patternMatch); // CraftBukkit - from above
++ // CraftBukkit end
++ Iterator iterator = level.getEntitiesOfClass(ServerPlayer.class, golem.getBoundingBox().inflate(5.0D)).iterator();
+
+ while (iterator.hasNext()) {
+ ServerPlayer serverplayer = (ServerPlayer) iterator.next();
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CauldronBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CauldronBlock.java.patch
new file mode 100644
index 0000000000..a8eb7d75f0
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CauldronBlock.java.patch
@@ -0,0 +1,34 @@
+--- a/net/minecraft/world/level/block/CauldronBlock.java
++++ b/net/minecraft/world/level/block/CauldronBlock.java
+@@ -11,6 +11,9 @@
+ import net.minecraft.world.level.gameevent.GameEvent;
+ import net.minecraft.world.level.material.Fluid;
+ import net.minecraft.world.level.material.Fluids;
++// CraftBukkit start
++import org.bukkit.event.block.CauldronLevelChangeEvent;
++// CraftBukkit end
+
+ public class CauldronBlock extends AbstractCauldronBlock {
+
+@@ -65,15 +63,13 @@
+ BlockState blockstate1;
+
+ if (fluid == Fluids.WATER) {
+- blockstate1 = Blocks.WATER_CAULDRON.defaultBlockState();
+- level.setBlockAndUpdate(blockpos, blockstate1);
+- level.gameEvent(GameEvent.BLOCK_CHANGE, blockpos, GameEvent.Context.of(blockstate1));
+- level.levelEvent(1047, blockpos, 0);
++ iblockdata1 = Blocks.WATER_CAULDRON.defaultBlockState();
++ LayeredCauldronBlock.changeLevel(state, level, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL); // CraftBukkit
++ level.levelEvent(1047, pos, 0);
+ } else if (fluid == Fluids.LAVA) {
+- blockstate1 = Blocks.LAVA_CAULDRON.defaultBlockState();
+- level.setBlockAndUpdate(blockpos, blockstate1);
+- level.gameEvent(GameEvent.BLOCK_CHANGE, blockpos, GameEvent.Context.of(blockstate1));
+- level.levelEvent(1046, blockpos, 0);
++ iblockdata1 = Blocks.LAVA_CAULDRON.defaultBlockState();
++ LayeredCauldronBlock.changeLevel(state, level, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL); // CraftBukkit
++ level.levelEvent(1046, pos, 0);
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CaveVines.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CaveVines.java.patch
new file mode 100644
index 0000000000..8557f597ed
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CaveVines.java.patch
@@ -0,0 +1,44 @@
+--- a/net/minecraft/world/level/block/CaveVines.java
++++ b/net/minecraft/world/level/block/CaveVines.java
+@@ -18,14 +18,38 @@
+ import net.minecraft.world.level.gameevent.GameEvent;
+ import net.minecraft.world.phys.shapes.VoxelShape;
+
++// CraftBukkit start
++import java.util.Collections;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.player.PlayerHarvestBlockEvent;
++// CraftBukkit end
++
+ public interface CaveVines {
+
+ VoxelShape SHAPE = Block.box(1.0D, 0.0D, 1.0D, 15.0D, 16.0D, 15.0D);
+ BooleanProperty BERRIES = BlockStateProperties.BERRIES;
+
+- static InteractionResult use(@Nullable Entity entity, BlockState blockstate, Level level, BlockPos blockpos) {
+- if ((Boolean) blockstate.getValue(CaveVines.BERRIES)) {
+- Block.popResource(level, blockpos, new ItemStack(Items.GLOW_BERRIES, 1));
++ static InteractionResult use(@Nullable Entity entity, IBlockData state, Level level, BlockPos pos) {
++ if ((Boolean) state.getValue(CaveVines.BERRIES)) {
++ // CraftBukkit start
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, (IBlockData) state.setValue(CaveVines.BERRIES, false))) {
++ return InteractionResult.SUCCESS;
++ }
++
++ if (entity instanceof Player) {
++ PlayerHarvestBlockEvent event = CraftEventFactory.callPlayerHarvestBlockEvent(level, pos, (Player) entity, net.minecraft.world.EnumHand.MAIN_HAND, Collections.singletonList(new ItemStack(Items.GLOW_BERRIES, 1)));
++ if (event.isCancelled()) {
++ return InteractionResult.SUCCESS; // We need to return a success either way, because making it PASS or FAIL will result in a bug where cancelling while harvesting w/ block in hand places block
++ }
++ for (org.bukkit.inventory.ItemStack itemStack : event.getItemsHarvested()) {
++ Block.popResource(level, pos, CraftItemStack.asNMSCopy(itemStack));
++ }
++ } else {
++ Block.popResource(level, pos, new ItemStack(Items.GLOW_BERRIES, 1));
++ }
++ // CraftBukkit end
++
+ float f = Mth.randomBetween(level.random, 0.8F, 1.2F);
+
+ level.playSound((Player) null, blockpos, SoundEvents.CAVE_VINES_PICK_BERRIES, SoundSource.BLOCKS, 1.0F, f);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CeilingHangingSignBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CeilingHangingSignBlock.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CeilingHangingSignBlock.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ChangeOverTimeBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ChangeOverTimeBlock.java.patch
new file mode 100644
index 0000000000..bfb8e06f44
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ChangeOverTimeBlock.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/world/level/block/ChangeOverTimeBlock.java
++++ b/net/minecraft/world/level/block/ChangeOverTimeBlock.java
+@@ -19,8 +19,8 @@
+ float f = 0.05688889F;
+
+ if (randomsource.nextFloat() < 0.05688889F) {
+- this.getNextState(blockstate, serverlevel, blockpos, randomsource).ifPresent((blockstate1) -> {
+- serverlevel.setBlockAndUpdate(blockpos, blockstate1);
++ this.getNextState(iblockdata, worldserver, blockposition, randomsource).ifPresent((iblockdata1) -> {
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(worldserver, blockposition, iblockdata1); // CraftBukkit
+ });
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ChestBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ChestBlock.java.patch
new file mode 100644
index 0000000000..7682147213
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ChestBlock.java.patch
@@ -0,0 +1,71 @@
+--- a/net/minecraft/world/level/block/ChestBlock.java
++++ b/net/minecraft/world/level/block/ChestBlock.java
+@@ -92,24 +89,23 @@
+ return Optional.empty();
+ }
+ };
+- private static final DoubleBlockCombiner.Combiner<ChestBlockEntity, Optional<MenuProvider>> MENU_PROVIDER_COMBINER = new DoubleBlockCombiner.Combiner<ChestBlockEntity, Optional<MenuProvider>>() {
++ private static final DoubleBlockCombiner.Combiner<ChestBlockEntity, Optional<ITileInventory>> MENU_PROVIDER_COMBINER = new DoubleBlockCombiner.Combiner<ChestBlockEntity, Optional<ITileInventory>>() {
++ public Optional<ITileInventory> acceptDouble(final ChestBlockEntity tileentitychest, final ChestBlockEntity tileentitychest1) {
++ final CompoundContainer inventorylargechest = new CompoundContainer(tileentitychest, tileentitychest1);
++
++ return Optional.of(new DoubleInventory(tileentitychest, tileentitychest1, inventorylargechest)); // CraftBukkit
++ }
++
++ public Optional<ITileInventory> acceptSingle(ChestBlockEntity tileentitychest) {
++ return Optional.of(tileentitychest);
++ }
++
+ @Override
+ public Optional<MenuProvider> acceptDouble(final ChestBlockEntity chestblockentity, final ChestBlockEntity chestblockentity1) {
+ final CompoundContainer compoundcontainer = new CompoundContainer(chestblockentity, chestblockentity1);
+
+- return Optional.of(new MenuProvider() {
+- @Nullable
+- @Override
+- @Override
+- public AbstractContainerMenu createMenu(int i, Inventory inventory, Player player) {
+- if (chestblockentity.canOpen(player) && chestblockentity1.canOpen(player)) {
+- chestblockentity.unpackLootTable(inventory.player);
+- chestblockentity1.unpackLootTable(inventory.player);
+- return ChestMenu.sixRows(i, inventory, compoundcontainer);
+- } else {
+- return null;
+- }
+- }
++ // CraftBukkit start
++ public static class DoubleInventory implements ITileInventory {
+
+ @Override
+ @Override
+@@ -130,6 +136,7 @@
+ return Optional.empty();
+ }
+ };
++ // CraftBukkit end
+
+ @Override
+ @Override
+@@ -311,12 +308,18 @@
+
+ @Nullable
+ @Override
+- @Override
+- public MenuProvider getMenuProvider(BlockState blockstate, Level level, BlockPos blockpos) {
+- return (MenuProvider) ((Optional) this.combine(blockstate, level, blockpos, false).apply(ChestBlock.MENU_PROVIDER_COMBINER)).orElse((Object) null);
++ public ITileInventory getMenuProvider(IBlockData state, Level level, BlockPos pos) {
++ // CraftBukkit start
++ return getMenuProvider(state, level, pos, false);
+ }
+
+- public static DoubleBlockCombiner.Combiner<ChestBlockEntity, Float2FloatFunction> opennessCombiner(final LidBlockEntity lidblockentity) {
++ @Nullable
++ public ITileInventory getMenuProvider(IBlockData iblockdata, Level world, BlockPos blockposition, boolean ignoreObstructions) {
++ return (ITileInventory) ((Optional) this.combine(iblockdata, world, blockposition, ignoreObstructions).apply(ChestBlock.MENU_PROVIDER_COMBINER)).orElse((Object) null);
++ // CraftBukkit end
++ }
++
++ public static DoubleBlockCombiner.Combiner<ChestBlockEntity, Float2FloatFunction> opennessCombiner(final LidBlockEntity lid) {
+ return new DoubleBlockCombiner.Combiner<ChestBlockEntity, Float2FloatFunction>() {
+ @Override
+ public Float2FloatFunction acceptDouble(ChestBlockEntity chestblockentity, ChestBlockEntity chestblockentity1) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ChorusFlowerBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ChorusFlowerBlock.java.patch
new file mode 100644
index 0000000000..7b2eb2ebfa
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ChorusFlowerBlock.java.patch
@@ -0,0 +1,81 @@
+--- a/net/minecraft/world/level/block/ChorusFlowerBlock.java
++++ b/net/minecraft/world/level/block/ChorusFlowerBlock.java
+@@ -22,6 +22,8 @@
+ import net.minecraft.world.phys.BlockHitResult;
+ import net.minecraft.world.phys.shapes.VoxelShape;
+
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
++
+ public class ChorusFlowerBlock extends Block {
+
+ public static final MapCodec<ChorusFlowerBlock> CODEC = RecordCodecBuilder.mapCodec((instance) -> {
+@@ -106,9 +103,13 @@
+ flag = true;
+ }
+
+- if (flag && allNeighborsEmpty(serverlevel, blockpos1, (Direction) null) && serverlevel.isEmptyBlock(blockpos.above(2))) {
+- serverlevel.setBlock(blockpos, ChorusPlantBlock.getStateWithConnections(serverlevel, blockpos, this.plant.defaultBlockState()), 2);
+- this.placeGrownFlower(serverlevel, blockpos1, i);
++ if (flag && allNeighborsEmpty(level, blockposition1, (Direction) null) && level.isEmptyBlock(pos.above(2))) {
++ // CraftBukkit start - add event
++ if (CraftEventFactory.handleBlockSpreadEvent(level, pos, blockposition1, this.defaultBlockState().setValue(ChorusFlowerBlock.AGE, Integer.valueOf(i)), 2)) {
++ level.setBlock(pos, ChorusPlantBlock.getStateWithConnections(level, pos, this.plant.defaultBlockState()), 2);
++ this.placeGrownFlower(level, blockposition1, i);
++ }
++ // CraftBukkit end
+ } else if (i < 4) {
+ j = randomsource.nextInt(4);
+ if (flag1) {
+@@ -121,19 +122,31 @@
+ Direction direction = Direction.Plane.HORIZONTAL.getRandomDirection(randomsource);
+ BlockPos blockpos2 = blockpos.relative(direction);
+
+- if (serverlevel.isEmptyBlock(blockpos2) && serverlevel.isEmptyBlock(blockpos2.below()) && allNeighborsEmpty(serverlevel, blockpos2, direction.getOpposite())) {
+- this.placeGrownFlower(serverlevel, blockpos2, i + 1);
+- flag2 = true;
++ if (level.isEmptyBlock(blockposition2) && level.isEmptyBlock(blockposition2.below()) && allNeighborsEmpty(level, blockposition2, enumdirection.getOpposite())) {
++ // CraftBukkit start - add event
++ if (CraftEventFactory.handleBlockSpreadEvent(level, pos, blockposition2, this.defaultBlockState().setValue(ChorusFlowerBlock.AGE, Integer.valueOf(i + 1)), 2)) {
++ this.placeGrownFlower(level, blockposition2, i + 1);
++ flag2 = true;
++ }
++ // CraftBukkit end
+ }
+ }
+
+ if (flag2) {
+ serverlevel.setBlock(blockpos, ChorusPlantBlock.getStateWithConnections(serverlevel, blockpos, this.plant.defaultBlockState()), 2);
+ } else {
+- this.placeDeadFlower(serverlevel, blockpos);
++ // CraftBukkit start - add event
++ if (CraftEventFactory.handleBlockGrowEvent(level, pos, this.defaultBlockState().setValue(ChorusFlowerBlock.AGE, Integer.valueOf(5)), 2)) {
++ this.placeDeadFlower(level, pos);
++ }
++ // CraftBukkit end
+ }
+ } else {
+- this.placeDeadFlower(serverlevel, blockpos);
++ // CraftBukkit start - add event
++ if (CraftEventFactory.handleBlockGrowEvent(level, pos, this.defaultBlockState().setValue(ChorusFlowerBlock.AGE, Integer.valueOf(5)), 2)) {
++ this.placeDeadFlower(level, pos);
++ }
++ // CraftBukkit end
+ }
+
+ }
+@@ -273,8 +282,13 @@
+ public void onProjectileHit(Level level, BlockState blockstate, BlockHitResult blockhitresult, Projectile projectile) {
+ BlockPos blockpos = blockhitresult.getBlockPos();
+
+- if (!level.isClientSide && projectile.mayInteract(level, blockpos) && projectile.mayBreak(level)) {
+- level.destroyBlock(blockpos, true, projectile);
++ if (!level.isClientSide && projectile.mayInteract(level, blockposition) && projectile.mayBreak(level)) {
++ // CraftBukkit
++ if (!CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, Blocks.AIR.defaultBlockState())) {
++ return;
++ }
++ // CraftBukkit end
++ level.destroyBlock(blockposition, true, projectile);
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CocoaBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CocoaBlock.java.patch
new file mode 100644
index 0000000000..bb5d756304
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CocoaBlock.java.patch
@@ -0,0 +1,22 @@
+--- a/net/minecraft/world/level/block/CocoaBlock.java
++++ b/net/minecraft/world/level/block/CocoaBlock.java
+@@ -20,6 +20,7 @@
+ import net.minecraft.world.level.pathfinder.PathComputationType;
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
+
+ public class CocoaBlock extends HorizontalDirectionalBlock implements BonemealableBlock {
+
+@@ -139,9 +131,8 @@
+ }
+
+ @Override
+- @Override
+- public void performBonemeal(ServerLevel serverlevel, RandomSource randomsource, BlockPos blockpos, BlockState blockstate) {
+- serverlevel.setBlock(blockpos, (BlockState) blockstate.setValue(CocoaBlock.AGE, (Integer) blockstate.getValue(CocoaBlock.AGE) + 1), 2);
++ public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, IBlockData state) {
++ CraftEventFactory.handleBlockGrowEvent(level, pos, (IBlockData) state.setValue(CocoaBlock.AGE, (Integer) state.getValue(CocoaBlock.AGE) + 1), 2); // CraftBukkit
+ }
+
+ @Override
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CommandBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CommandBlock.java.patch
new file mode 100644
index 0000000000..93f0237c6f
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CommandBlock.java.patch
@@ -0,0 +1,40 @@
+--- a/net/minecraft/world/level/block/CommandBlock.java
++++ b/net/minecraft/world/level/block/CommandBlock.java
+@@ -30,6 +30,8 @@
+ import net.minecraft.world.phys.BlockHitResult;
+ import org.slf4j.Logger;
+
++import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit
++
+ public class CommandBlock extends BaseEntityBlock implements GameMasterBlock {
+
+ public static final MapCodec<CommandBlock> CODEC = RecordCodecBuilder.mapCodec((instance) -> {
+@@ -69,13 +68,22 @@
+ if (!level.isClientSide) {
+ BlockEntity blockentity = level.getBlockEntity(blockpos);
+
+- if (blockentity instanceof CommandBlockEntity) {
+- CommandBlockEntity commandblockentity = (CommandBlockEntity) blockentity;
+- boolean flag1 = level.hasNeighborSignal(blockpos);
+- boolean flag2 = commandblockentity.isPowered();
++ if (tileentity instanceof CommandBlockEntity) {
++ CommandBlockEntity tileentitycommand = (CommandBlockEntity) tileentity;
++ boolean flag1 = level.hasNeighborSignal(pos);
++ boolean flag2 = tileentitycommand.isPowered();
++ // CraftBukkit start
++ org.bukkit.block.Block bukkitBlock = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++ int old = flag2 ? 15 : 0;
++ int current = flag1 ? 15 : 0;
+
+- commandblockentity.setPowered(flag1);
+- if (!flag2 && !commandblockentity.isAutomatic() && commandblockentity.getMode() != CommandBlockEntity.Mode.SEQUENCE) {
++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(bukkitBlock, old, current);
++ level.getCraftServer().getPluginManager().callEvent(eventRedstone);
++ flag1 = eventRedstone.getNewCurrent() > 0;
++ // CraftBukkit end
++
++ tileentitycommand.setPowered(flag1);
++ if (!flag2 && !tileentitycommand.isAutomatic() && tileentitycommand.getMode() != CommandBlockEntity.Type.SEQUENCE) {
+ if (flag1) {
+ commandblockentity.markConditionMet();
+ level.scheduleTick(blockpos, (Block) this, 1);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ComparatorBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ComparatorBlock.java.patch
new file mode 100644
index 0000000000..9be8474075
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ComparatorBlock.java.patch
@@ -0,0 +1,32 @@
+--- a/net/minecraft/world/level/block/ComparatorBlock.java
++++ b/net/minecraft/world/level/block/ComparatorBlock.java
+@@ -27,6 +27,7 @@
+ import net.minecraft.world.phys.AABB;
+ import net.minecraft.world.phys.BlockHitResult;
+ import net.minecraft.world.ticks.TickPriority;
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
+
+ public class ComparatorBlock extends DiodeBlock implements EntityBlock {
+
+@@ -173,9 +167,19 @@
+ boolean flag1 = (Boolean) blockstate.getValue(ComparatorBlock.POWERED);
+
+ if (flag1 && !flag) {
+- level.setBlock(blockpos, (BlockState) blockstate.setValue(ComparatorBlock.POWERED, false), 2);
++ // CraftBukkit start
++ if (CraftEventFactory.callRedstoneChange(level, pos, 15, 0).getNewCurrent() != 0) {
++ return;
++ }
++ // CraftBukkit end
++ level.setBlock(pos, (IBlockData) state.setValue(ComparatorBlock.POWERED, false), 2);
+ } else if (!flag1 && flag) {
+- level.setBlock(blockpos, (BlockState) blockstate.setValue(ComparatorBlock.POWERED, true), 2);
++ // CraftBukkit start
++ if (CraftEventFactory.callRedstoneChange(level, pos, 0, 15).getNewCurrent() != 15) {
++ return;
++ }
++ // CraftBukkit end
++ level.setBlock(pos, (IBlockData) state.setValue(ComparatorBlock.POWERED, true), 2);
+ }
+
+ this.updateNeighborsInFront(level, blockpos, blockstate);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ComposterBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ComposterBlock.java.patch
new file mode 100644
index 0000000000..5b5ee06ef8
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ComposterBlock.java.patch
@@ -0,0 +1,134 @@
+--- a/net/minecraft/world/level/block/ComposterBlock.java
++++ b/net/minecraft/world/level/block/ComposterBlock.java
+@@ -40,6 +40,10 @@
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.Shapes;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++// CraftBukkit start
++import org.bukkit.craftbukkit.inventory.CraftBlockInventoryHolder;
++import org.bukkit.craftbukkit.util.DummyGeneratorAccess;
++// CraftBukkit end
+
+ public class ComposterBlock extends Block implements WorldlyContainerHolder {
+
+@@ -260,8 +258,15 @@
+ public static BlockState insertItem(Entity entity, BlockState blockstate, ServerLevel serverlevel, ItemStack itemstack, BlockPos blockpos) {
+ int i = (Integer) blockstate.getValue(ComposterBlock.LEVEL);
+
+- if (i < 7 && ComposterBlock.COMPOSTABLES.containsKey(itemstack.getItem())) {
+- BlockState blockstate1 = addItem(entity, blockstate, serverlevel, blockpos, itemstack);
++ if (i < 7 && ComposterBlock.COMPOSTABLES.containsKey(stack.getItem())) {
++ // CraftBukkit start
++ double rand = level.getRandom().nextDouble();
++ IBlockData iblockdata1 = addItem(entity, state, DummyGeneratorAccess.INSTANCE, pos, stack, rand);
++ if (state == iblockdata1 || !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, iblockdata1)) {
++ return state;
++ }
++ iblockdata1 = addItem(entity, state, level, pos, stack, rand);
++ // CraftBukkit end
+
+ itemstack.shrink(1);
+ return blockstate1;
+@@ -270,7 +275,15 @@
+ }
+ }
+
+- public static BlockState extractProduce(Entity entity, BlockState blockstate, Level level, BlockPos blockpos) {
++ public static IBlockData extractProduce(Entity entity, IBlockData state, Level level, BlockPos pos) {
++ // CraftBukkit start
++ if (entity != null && !(entity instanceof Player)) {
++ IBlockData iblockdata1 = empty(entity, state, DummyGeneratorAccess.INSTANCE, pos);
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, iblockdata1)) {
++ return state;
++ }
++ }
++ // CraftBukkit end
+ if (!level.isClientSide) {
+ Vec3 vec3 = Vec3.atLowerCornerWithOffset(blockpos, 0.5D, 1.01D, 0.5D).offsetRandom(level.random, 0.7F);
+ ItemEntity itementity = new ItemEntity(level, vec3.x(), vec3.y(), vec3.z(), new ItemStack(Items.BONE_MEAL));
+@@ -293,8 +306,14 @@
+ return blockstate1;
+ }
+
+- static BlockState addItem(@Nullable Entity entity, BlockState blockstate, LevelAccessor levelaccessor, BlockPos blockpos, ItemStack itemstack) {
+- int i = (Integer) blockstate.getValue(ComposterBlock.LEVEL);
++ static IBlockData addItem(@Nullable Entity entity, IBlockData state, LevelAccessor level, BlockPos pos, ItemStack stack) {
++ // CraftBukkit start
++ return addItem(entity, state, level, pos, stack, level.getRandom().nextDouble());
++ }
++
++ static IBlockData addItem(@Nullable Entity entity, IBlockData iblockdata, LevelAccessor generatoraccess, BlockPos blockposition, ItemStack itemstack, double rand) {
++ // CraftBukkit end
++ int i = (Integer) iblockdata.getValue(ComposterBlock.LEVEL);
+ float f = ComposterBlock.COMPOSTABLES.getFloat(itemstack.getItem());
+
+ if ((i != 0 || f <= 0.0F) && levelaccessor.getRandom().nextDouble() >= (double) f) {
+@@ -352,7 +365,8 @@
+ public WorldlyContainer getContainer(BlockState blockstate, LevelAccessor levelaccessor, BlockPos blockpos) {
+ int i = (Integer) blockstate.getValue(ComposterBlock.LEVEL);
+
+- return (WorldlyContainer) (i == 8 ? new ComposterBlock.OutputContainer(blockstate, levelaccessor, blockpos, new ItemStack(Items.BONE_MEAL)) : (i < 7 ? new ComposterBlock.InputContainer(blockstate, levelaccessor, blockpos) : new ComposterBlock.EmptyContainer()));
++ // CraftBukkit - empty generatoraccess, blockposition
++ return (WorldlyContainer) (i == 8 ? new ComposterBlock.OutputContainer(state, level, pos, new ItemStack(Items.BONE_MEAL)) : (i < 7 ? new ComposterBlock.InputContainer(state, level, pos) : new ComposterBlock.EmptyContainer(level, pos)));
+ }
+
+ private static class OutputContainer extends SimpleContainer implements WorldlyContainer {
+@@ -362,11 +376,12 @@
+ private final BlockPos pos;
+ private boolean changed;
+
+- public OutputContainer(BlockState blockstate, LevelAccessor levelaccessor, BlockPos blockpos, ItemStack itemstack) {
+- super(itemstack);
+- this.state = blockstate;
+- this.level = levelaccessor;
+- this.pos = blockpos;
++ public OutputContainer(IBlockData state, LevelAccessor level, BlockPos pos, ItemStack stack) {
++ super(stack);
++ this.state = state;
++ this.level = level;
++ this.pos = pos;
++ this.bukkitOwner = new CraftBlockInventoryHolder(level, pos, this); // CraftBukkit
+ }
+
+ @Override
+@@ -396,8 +406,15 @@
+ @Override
+ @Override
+ public void setChanged() {
++ // CraftBukkit start - allow putting items back (eg cancelled InventoryMoveItemEvent)
++ if (this.isEmpty()) {
+ ComposterBlock.empty((Entity) null, this.state, this.level, this.pos);
+ this.changed = true;
++ } else {
++ this.level.setBlock(this.pos, this.state, 3);
++ this.changed = false;
++ }
++ // CraftBukkit end
+ }
+ }
+
+@@ -410,9 +427,10 @@
+
+ public InputContainer(BlockState blockstate, LevelAccessor levelaccessor, BlockPos blockpos) {
+ super(1);
+- this.state = blockstate;
+- this.level = levelaccessor;
+- this.pos = blockpos;
++ this.bukkitOwner = new CraftBlockInventoryHolder(level, pos, this); // CraftBukkit
++ this.state = state;
++ this.level = level;
++ this.pos = pos;
+ }
+
+ @Override
+@@ -457,8 +470,9 @@
+
+ private static class EmptyContainer extends SimpleContainer implements WorldlyContainer {
+
+- public EmptyContainer() {
++ public EmptyContainer(LevelAccessor generatoraccess, BlockPos blockposition) { // CraftBukkit
+ super(0);
++ this.bukkitOwner = new CraftBlockInventoryHolder(generatoraccess, blockposition, this); // CraftBukkit
+ }
+
+ @Override
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ConcretePowderBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ConcretePowderBlock.java.patch
new file mode 100644
index 0000000000..755f032236
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ConcretePowderBlock.java.patch
@@ -0,0 +1,85 @@
+--- a/net/minecraft/world/level/block/ConcretePowderBlock.java
++++ b/net/minecraft/world/level/block/ConcretePowderBlock.java
+@@ -14,6 +14,12 @@
+ import net.minecraft.world.level.block.state.BlockBehaviour;
+ import net.minecraft.world.level.block.state.BlockState;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.block.CraftBlockState;
++import org.bukkit.craftbukkit.block.CraftBlockStates;
++import org.bukkit.event.block.BlockFormEvent;
++// CraftBukkit end
++
+ public class ConcretePowderBlock extends FallingBlock {
+
+ public static final MapCodec<ConcretePowderBlock> CODEC = RecordCodecBuilder.mapCodec((instance) -> {
+@@ -35,10 +40,9 @@
+ }
+
+ @Override
+- @Override
+- public void onLand(Level level, BlockPos blockpos, BlockState blockstate, BlockState blockstate1, FallingBlockEntity fallingblockentity) {
+- if (shouldSolidify(level, blockpos, blockstate1)) {
+- level.setBlock(blockpos, this.concrete.defaultBlockState(), 3);
++ public void onLand(Level level, BlockPos pos, IBlockData state, IBlockData replaceableState, FallingBlockEntity fallingBlock) {
++ if (shouldSolidify(level, pos, replaceableState)) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(level, pos, this.concrete.defaultBlockState(), 3); // CraftBukkit
+ }
+
+ }
+@@ -50,7 +53,24 @@
+ BlockPos blockpos = blockplacecontext.getClickedPos();
+ BlockState blockstate = level.getBlockState(blockpos);
+
+- return shouldSolidify(level, blockpos, blockstate) ? this.concrete.defaultBlockState() : super.getStateForPlacement(blockplacecontext);
++ // CraftBukkit start
++ if (!shouldSolidify(world, blockposition, iblockdata)) {
++ return super.getStateForPlacement(context);
++ }
++
++ // TODO: An event factory call for methods like this
++ CraftBlockState blockState = CraftBlockStates.getBlockState(world, blockposition);
++ blockState.setData(this.concrete.defaultBlockState());
++
++ BlockFormEvent event = new BlockFormEvent(blockState.getBlock(), blockState);
++ world.getServer().server.getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ return blockState.getHandle();
++ }
++
++ return super.getStateForPlacement(context);
++ // CraftBukkit end
+ }
+
+ private static boolean shouldSolidify(BlockGetter blockgetter, BlockPos blockpos, BlockState blockstate) {
+@@ -85,9 +105,26 @@
+ }
+
+ @Override
+- @Override
+- public BlockState updateShape(BlockState blockstate, Direction direction, BlockState blockstate1, LevelAccessor levelaccessor, BlockPos blockpos, BlockPos blockpos1) {
+- return touchesLiquid(levelaccessor, blockpos) ? this.concrete.defaultBlockState() : super.updateShape(blockstate, direction, blockstate1, levelaccessor, blockpos, blockpos1);
++ public IBlockData updateShape(IBlockData state, Direction facing, IBlockData facingState, LevelAccessor level, BlockPos currentPos, BlockPos facingPos) {
++ // CraftBukkit start
++ if (touchesLiquid(level, currentPos)) {
++ // Suppress during worldgen
++ if (!(level instanceof Level)) {
++ return this.concrete.defaultBlockState();
++ }
++ CraftBlockState blockState = CraftBlockStates.getBlockState(level, currentPos);
++ blockState.setData(this.concrete.defaultBlockState());
++
++ BlockFormEvent event = new BlockFormEvent(blockState.getBlock(), blockState);
++ ((Level) level).getCraftServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ return blockState.getHandle();
++ }
++ }
++
++ return super.updateShape(state, facing, facingState, level, currentPos, facingPos);
++ // CraftBukkit end
+ }
+
+ @Override
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CoralBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CoralBlock.java.patch
new file mode 100644
index 0000000000..fcfdef93a4
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CoralBlock.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/level/block/CoralBlock.java
++++ b/net/minecraft/world/level/block/CoralBlock.java
+@@ -38,10 +37,14 @@
+ }
+
+ @Override
+- @Override
+- public void tick(BlockState blockstate, ServerLevel serverlevel, BlockPos blockpos, RandomSource randomsource) {
+- if (!this.scanForWater(serverlevel, blockpos)) {
+- serverlevel.setBlock(blockpos, this.deadBlock.defaultBlockState(), 2);
++ public void tick(IBlockData state, ServerLevel level, BlockPos pos, RandomSource random) {
++ if (!this.scanForWater(level, pos)) {
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(level, pos, this.deadBlock.defaultBlockState()).isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
++ level.setBlock(pos, this.deadBlock.defaultBlockState(), 2);
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CoralFanBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CoralFanBlock.java.patch
new file mode 100644
index 0000000000..bf2ed0b28c
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CoralFanBlock.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/level/block/CoralFanBlock.java
++++ b/net/minecraft/world/level/block/CoralFanBlock.java
+@@ -40,10 +38,14 @@
+ }
+
+ @Override
+- @Override
+- public void tick(BlockState blockstate, ServerLevel serverlevel, BlockPos blockpos, RandomSource randomsource) {
+- if (!scanForWater(blockstate, serverlevel, blockpos)) {
+- serverlevel.setBlock(blockpos, (BlockState) this.deadBlock.defaultBlockState().setValue(CoralFanBlock.WATERLOGGED, false), 2);
++ public void tick(IBlockData state, ServerLevel level, BlockPos pos, RandomSource random) {
++ if (!scanForWater(state, level, pos)) {
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(level, pos, this.deadBlock.defaultBlockState().setValue(CoralFanBlock.WATERLOGGED, false)).isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
++ level.setBlock(pos, (IBlockData) this.deadBlock.defaultBlockState().setValue(CoralFanBlock.WATERLOGGED, false), 2);
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CoralPlantBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CoralPlantBlock.java.patch
new file mode 100644
index 0000000000..f686b4752c
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CoralPlantBlock.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/level/block/CoralPlantBlock.java
++++ b/net/minecraft/world/level/block/CoralPlantBlock.java
+@@ -45,10 +43,14 @@
+ }
+
+ @Override
+- @Override
+- public void tick(BlockState blockstate, ServerLevel serverlevel, BlockPos blockpos, RandomSource randomsource) {
+- if (!scanForWater(blockstate, serverlevel, blockpos)) {
+- serverlevel.setBlock(blockpos, (BlockState) this.deadBlock.defaultBlockState().setValue(CoralPlantBlock.WATERLOGGED, false), 2);
++ public void tick(IBlockData state, ServerLevel level, BlockPos pos, RandomSource random) {
++ if (!scanForWater(state, level, pos)) {
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(level, pos, this.deadBlock.defaultBlockState().setValue(CoralPlantBlock.WATERLOGGED, false)).isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
++ level.setBlock(pos, (IBlockData) this.deadBlock.defaultBlockState().setValue(CoralPlantBlock.WATERLOGGED, false), 2);
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CoralWallFanBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CoralWallFanBlock.java.patch
new file mode 100644
index 0000000000..f422c2075e
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CoralWallFanBlock.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/level/block/CoralWallFanBlock.java
++++ b/net/minecraft/world/level/block/CoralWallFanBlock.java
+@@ -40,10 +38,14 @@
+ }
+
+ @Override
+- @Override
+- public void tick(BlockState blockstate, ServerLevel serverlevel, BlockPos blockpos, RandomSource randomsource) {
+- if (!scanForWater(blockstate, serverlevel, blockpos)) {
+- serverlevel.setBlock(blockpos, (BlockState) ((BlockState) this.deadBlock.defaultBlockState().setValue(CoralWallFanBlock.WATERLOGGED, false)).setValue(CoralWallFanBlock.FACING, (Direction) blockstate.getValue(CoralWallFanBlock.FACING)), 2);
++ public void tick(IBlockData state, ServerLevel level, BlockPos pos, RandomSource random) {
++ if (!scanForWater(state, level, pos)) {
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(level, pos, this.deadBlock.defaultBlockState().setValue(CoralWallFanBlock.WATERLOGGED, false).setValue(CoralWallFanBlock.FACING, state.getValue(CoralWallFanBlock.FACING))).isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
++ level.setBlock(pos, (IBlockData) ((IBlockData) this.deadBlock.defaultBlockState().setValue(CoralWallFanBlock.WATERLOGGED, false)).setValue(CoralWallFanBlock.FACING, (Direction) state.getValue(CoralWallFanBlock.FACING)), 2);
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CropBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CropBlock.java.patch
new file mode 100644
index 0000000000..3514aeeca0
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/CropBlock.java.patch
@@ -0,0 +1,44 @@
+--- a/net/minecraft/world/level/block/CropBlock.java
++++ b/net/minecraft/world/level/block/CropBlock.java
+@@ -21,6 +21,7 @@
+ import net.minecraft.world.level.block.state.properties.IntegerProperty;
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
+
+ public class CropBlock extends BushBlock implements BonemealableBlock {
+
+@@ -87,8 +83,8 @@
+ if (i < this.getMaxAge()) {
+ float f = getGrowthSpeed(this, serverlevel, blockpos);
+
+- if (randomsource.nextInt((int) (25.0F / f) + 1) == 0) {
+- serverlevel.setBlock(blockpos, this.getStateForAge(i + 1), 2);
++ if (random.nextInt((int) (25.0F / f) + 1) == 0) {
++ CraftEventFactory.handleBlockGrowEvent(level, pos, this.getStateForAge(i + 1), 2); // CraftBukkit
+ }
+ }
+ }
+@@ -103,7 +99,7 @@
+ i = j;
+ }
+
+- level.setBlock(blockpos, this.getStateForAge(i), 2);
++ CraftEventFactory.handleBlockGrowEvent(level, pos, this.getStateForAge(i), 2); // CraftBukkit
+ }
+
+ protected int getBonemealAgeIncrease(Level level) {
+@@ -165,10 +160,9 @@
+ }
+
+ @Override
+- @Override
+- public void entityInside(BlockState blockstate, Level level, BlockPos blockpos, Entity entity) {
+- if (entity instanceof Ravager && level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+- level.destroyBlock(blockpos, true, entity);
++ public void entityInside(IBlockData state, Level level, BlockPos pos, Entity entity) {
++ if (entity instanceof Ravager && CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit
++ level.destroyBlock(pos, true, entity);
+ }
+
+ super.entityInside(blockstate, level, blockpos, entity);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DaylightDetectorBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DaylightDetectorBlock.java.patch
new file mode 100644
index 0000000000..8e990550cb
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DaylightDetectorBlock.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/world/level/block/DaylightDetectorBlock.java
++++ b/net/minecraft/world/level/block/DaylightDetectorBlock.java
+@@ -77,8 +73,9 @@
+ }
+
+ i = Mth.clamp(i, 0, 15);
+- if ((Integer) blockstate.getValue(DaylightDetectorBlock.POWER) != i) {
+- level.setBlock(blockpos, (BlockState) blockstate.setValue(DaylightDetectorBlock.POWER, i), 3);
++ if ((Integer) state.getValue(DaylightDetectorBlock.POWER) != i) {
++ i = org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(level, pos, ((Integer) state.getValue(POWER)), i).getNewCurrent(); // CraftBukkit - Call BlockRedstoneEvent
++ level.setBlock(pos, (IBlockData) state.setValue(DaylightDetectorBlock.POWER, i), 3);
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DetectorRailBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DetectorRailBlock.java.patch
new file mode 100644
index 0000000000..f28eaa5935
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DetectorRailBlock.java.patch
@@ -0,0 +1,30 @@
+--- a/net/minecraft/world/level/block/DetectorRailBlock.java
++++ b/net/minecraft/world/level/block/DetectorRailBlock.java
+@@ -25,6 +25,7 @@
+ import net.minecraft.world.level.block.state.properties.Property;
+ import net.minecraft.world.level.block.state.properties.RailShape;
+ import net.minecraft.world.phys.AABB;
++import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit
+
+ public class DetectorRailBlock extends BaseRailBlock {
+
+@@ -92,8 +87,18 @@
+ flag1 = true;
+ }
+
+- BlockState blockstate1;
++ IBlockData iblockdata1;
++ // CraftBukkit start
++ if (flag != flag1) {
++ org.bukkit.block.Block block = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
+
++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(block, flag ? 15 : 0, flag1 ? 15 : 0);
++ level.getCraftServer().getPluginManager().callEvent(eventRedstone);
++
++ flag1 = eventRedstone.getNewCurrent() > 0;
++ }
++ // CraftBukkit end
++
+ if (flag1 && !flag) {
+ blockstate1 = (BlockState) blockstate.setValue(DetectorRailBlock.POWERED, true);
+ level.setBlock(blockpos, blockstate1, 3);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DiodeBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DiodeBlock.java.patch
new file mode 100644
index 0000000000..6a6f9087df
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DiodeBlock.java.patch
@@ -0,0 +1,32 @@
+--- a/net/minecraft/world/level/block/DiodeBlock.java
++++ b/net/minecraft/world/level/block/DiodeBlock.java
+@@ -20,6 +20,7 @@
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.VoxelShape;
+ import net.minecraft.world.ticks.TickPriority;
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
+
+ public abstract class DiodeBlock extends HorizontalDirectionalBlock {
+
+@@ -60,9 +57,19 @@
+ boolean flag1 = this.shouldTurnOn(serverlevel, blockpos, blockstate);
+
+ if (flag && !flag1) {
+- serverlevel.setBlock(blockpos, (BlockState) blockstate.setValue(DiodeBlock.POWERED, false), 2);
++ // CraftBukkit start
++ if (CraftEventFactory.callRedstoneChange(level, pos, 15, 0).getNewCurrent() != 0) {
++ return;
++ }
++ // CraftBukkit end
++ level.setBlock(pos, (IBlockData) state.setValue(DiodeBlock.POWERED, false), 2);
+ } else if (!flag) {
+- serverlevel.setBlock(blockpos, (BlockState) blockstate.setValue(DiodeBlock.POWERED, true), 2);
++ // CraftBukkit start
++ if (CraftEventFactory.callRedstoneChange(level, pos, 0, 15).getNewCurrent() != 15) {
++ return;
++ }
++ // CraftBukkit end
++ level.setBlock(pos, (IBlockData) state.setValue(DiodeBlock.POWERED, true), 2);
+ if (!flag1) {
+ serverlevel.scheduleTick(blockpos, (Block) this, this.getDelay(blockstate), TickPriority.VERY_HIGH);
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DispenserBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DispenserBlock.java.patch
new file mode 100644
index 0000000000..1386fa28c8
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DispenserBlock.java.patch
@@ -0,0 +1,22 @@
+--- a/net/minecraft/world/level/block/DispenserBlock.java
++++ b/net/minecraft/world/level/block/DispenserBlock.java
+@@ -49,6 +49,7 @@
+ object2objectopenhashmap.defaultReturnValue(new DefaultDispenseItemBehavior());
+ });
+ private static final int TRIGGER_DURATION = 4;
++ public static boolean eventFired = false; // CraftBukkit
+
+ @Override
+ @Override
+@@ -102,8 +101,9 @@
+ ItemStack itemstack = dispenserblockentity.getItem(i);
+ DispenseItemBehavior dispenseitembehavior = this.getDispenseMethod(itemstack);
+
+- if (dispenseitembehavior != DispenseItemBehavior.NOOP) {
+- dispenserblockentity.setItem(i, dispenseitembehavior.dispense(blocksource, itemstack));
++ if (idispensebehavior != DispenseItemBehavior.NOOP) {
++ eventFired = false; // CraftBukkit - reset event status
++ tileentitydispenser.setItem(i, idispensebehavior.dispense(sourceblock, itemstack));
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DoorBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DoorBlock.java.patch
new file mode 100644
index 0000000000..642d1c57c5
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DoorBlock.java.patch
@@ -0,0 +1,46 @@
+--- a/net/minecraft/world/level/block/DoorBlock.java
++++ b/net/minecraft/world/level/block/DoorBlock.java
+@@ -36,6 +36,7 @@
+ import net.minecraft.world.phys.Vec3;
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit
+
+ public class DoorBlock extends Block {
+
+@@ -228,14 +220,28 @@
+ }
+
+ @Override
+- @Override
+- public void neighborChanged(BlockState blockstate, Level level, BlockPos blockpos, Block block, BlockPos blockpos1, boolean flag) {
+- boolean flag1 = level.hasNeighborSignal(blockpos) || level.hasNeighborSignal(blockpos.relative(blockstate.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN));
++ public void neighborChanged(IBlockData state, Level level, BlockPos pos, Block block, BlockPos fromPos, boolean isMoving) {
++ // CraftBukkit start
++ BlockPos otherHalf = pos.relative(state.getValue(DoorBlock.HALF) == BlockPropertyDoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN);
+
+- if (!this.defaultBlockState().is(block) && flag1 != (Boolean) blockstate.getValue(DoorBlock.POWERED)) {
+- if (flag1 != (Boolean) blockstate.getValue(DoorBlock.OPEN)) {
+- this.playSound((Entity) null, level, blockpos, flag1);
+- level.gameEvent((Entity) null, flag1 ? GameEvent.BLOCK_OPEN : GameEvent.BLOCK_CLOSE, blockpos);
++ org.bukkit.World bworld = level.getWorld();
++ org.bukkit.block.Block bukkitBlock = bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++ org.bukkit.block.Block blockTop = bworld.getBlockAt(otherHalf.getX(), otherHalf.getY(), otherHalf.getZ());
++
++ int power = bukkitBlock.getBlockPower();
++ int powerTop = blockTop.getBlockPower();
++ if (powerTop > power) power = powerTop;
++ int oldPower = (Boolean) state.getValue(DoorBlock.POWERED) ? 15 : 0;
++
++ if (oldPower == 0 ^ power == 0) {
++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(bukkitBlock, oldPower, power);
++ level.getCraftServer().getPluginManager().callEvent(eventRedstone);
++
++ boolean flag1 = eventRedstone.getNewCurrent() > 0;
++ // CraftBukkit end
++ if (flag1 != (Boolean) state.getValue(DoorBlock.OPEN)) {
++ this.playSound((Entity) null, level, pos, flag1);
++ level.gameEvent((Entity) null, flag1 ? GameEvent.BLOCK_OPEN : GameEvent.BLOCK_CLOSE, pos);
+ }
+
+ level.setBlock(blockpos, (BlockState) ((BlockState) blockstate.setValue(DoorBlock.POWERED, flag1)).setValue(DoorBlock.OPEN, flag1), 2);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DoublePlantBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DoublePlantBlock.java.patch
new file mode 100644
index 0000000000..1f0897e3b3
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DoublePlantBlock.java.patch
@@ -0,0 +1,18 @@
+--- a/net/minecraft/world/level/block/DoublePlantBlock.java
++++ b/net/minecraft/world/level/block/DoublePlantBlock.java
+@@ -107,8 +100,13 @@
+ super.playerDestroy(level, player, blockpos, Blocks.AIR.defaultBlockState(), blockentity, itemstack);
+ }
+
+- protected static void preventDropFromBottomPart(Level level, BlockPos blockpos, BlockState blockstate, Player player) {
+- DoubleBlockHalf doubleblockhalf = (DoubleBlockHalf) blockstate.getValue(DoublePlantBlock.HALF);
++ protected static void preventDropFromBottomPart(Level world, BlockPos blockposition, IBlockData iblockdata, Player entityhuman) {
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPhysicsEvent(world, blockposition).isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
++ BlockPropertyDoubleBlockHalf blockpropertydoubleblockhalf = (BlockPropertyDoubleBlockHalf) iblockdata.getValue(DoublePlantBlock.HALF);
+
+ if (doubleblockhalf == DoubleBlockHalf.UPPER) {
+ BlockPos blockpos1 = blockpos.below();
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DragonEggBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DragonEggBlock.java.patch
new file mode 100644
index 0000000000..ba8684303f
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DragonEggBlock.java.patch
@@ -0,0 +1,31 @@
+--- a/net/minecraft/world/level/block/DragonEggBlock.java
++++ b/net/minecraft/world/level/block/DragonEggBlock.java
+@@ -16,6 +16,7 @@
+ import net.minecraft.world.phys.BlockHitResult;
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++import org.bukkit.event.block.BlockFromToEvent; // CraftBukkit
+
+ public class DragonEggBlock extends FallingBlock {
+
+@@ -57,7 +54,19 @@
+ for (int i = 0; i < 1000; ++i) {
+ BlockPos blockpos1 = blockpos.offset(level.random.nextInt(16) - level.random.nextInt(16), level.random.nextInt(8) - level.random.nextInt(8), level.random.nextInt(16) - level.random.nextInt(16));
+
+- if (level.getBlockState(blockpos1).isAir() && worldborder.isWithinBounds(blockpos1)) {
++ if (level.getBlockState(blockposition1).isAir() && worldborder.isWithinBounds(blockposition1)) {
++ // CraftBukkit start
++ org.bukkit.block.Block from = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++ org.bukkit.block.Block to = level.getWorld().getBlockAt(blockposition1.getX(), blockposition1.getY(), blockposition1.getZ());
++ BlockFromToEvent event = new BlockFromToEvent(from, to);
++ org.bukkit.Bukkit.getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return;
++ }
++
++ blockposition1 = new BlockPos(event.getToBlock().getX(), event.getToBlock().getY(), event.getToBlock().getZ());
++ // CraftBukkit end
+ if (level.isClientSide) {
+ for (int j = 0; j < 128; ++j) {
+ double d0 = level.random.nextDouble();
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DropExperienceBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DropExperienceBlock.java.patch
new file mode 100644
index 0000000000..24d445e184
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DropExperienceBlock.java.patch
@@ -0,0 +1,22 @@
+--- a/net/minecraft/world/level/block/DropExperienceBlock.java
++++ b/net/minecraft/world/level/block/DropExperienceBlock.java
+@@ -30,6 +29,11 @@
+ }
+
+ @Override
++ public void spawnAfterBreak(IBlockData state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) {
++ super.spawnAfterBreak(state, level, pos, stack, dropExperience);
++ // CraftBukkit start - Delegate to getExpDrop
++ }
++
+ @Override
+ public void spawnAfterBreak(BlockState blockstate, ServerLevel serverlevel, BlockPos blockpos, ItemStack itemstack, boolean flag) {
+ super.spawnAfterBreak(blockstate, serverlevel, blockpos, itemstack, flag);
+@@ -37,5 +40,7 @@
+ this.tryDropExperience(serverlevel, blockpos, itemstack, this.xpRange);
+ }
+
++ return 0;
++ // CraftBukkit end
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DropperBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DropperBlock.java.patch
new file mode 100644
index 0000000000..537883288f
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/DropperBlock.java.patch
@@ -0,0 +1,47 @@
+--- a/net/minecraft/world/level/block/DropperBlock.java
++++ b/net/minecraft/world/level/block/DropperBlock.java
+@@ -18,12 +19,15 @@
+ import net.minecraft.world.level.block.state.BlockBehaviour;
+ import net.minecraft.world.level.block.state.BlockState;
+ import org.slf4j.Logger;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.inventory.InventoryMoveItemEvent;
++// CraftBukkit end
+
+ public class DropperBlock extends DispenserBlock {
+
+ private static final Logger LOGGER = LogUtils.getLogger();
+ public static final MapCodec<DropperBlock> CODEC = simpleCodec(DropperBlock::new);
+- private static final DispenseItemBehavior DISPENSE_BEHAVIOUR = new DefaultDispenseItemBehavior();
++ private static final DispenseItemBehavior DISPENSE_BEHAVIOUR = new DefaultDispenseItemBehavior(true); // CraftBukkit
+
+ @Override
+ @Override
+@@ -71,8 +71,25 @@
+ if (container == null) {
+ itemstack1 = DropperBlock.DISPENSE_BEHAVIOUR.dispense(blocksource, itemstack);
+ } else {
+- itemstack1 = HopperBlockEntity.addItem(dispenserblockentity, container, itemstack.copy().split(1), direction.getOpposite());
+- if (itemstack1.isEmpty()) {
++ // CraftBukkit start - Fire event when pushing items into other inventories
++ CraftItemStack oitemstack = CraftItemStack.asCraftMirror(itemstack.copy().split(1));
++
++ org.bukkit.inventory.Inventory destinationInventory;
++ // Have to special case large chests as they work oddly
++ if (iinventory instanceof CompoundContainer) {
++ destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory);
++ } else {
++ destinationInventory = iinventory.getOwner().getInventory();
++ }
++
++ InventoryMoveItemEvent event = new InventoryMoveItemEvent(tileentitydispenser.getOwner().getInventory(), oitemstack.clone(), destinationInventory, true);
++ worldserver.getCraftServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ return;
++ }
++ itemstack1 = HopperBlockEntity.addItem(tileentitydispenser, iinventory, CraftItemStack.asNMSCopy(event.getItem()), enumdirection.getOpposite());
++ if (event.getItem().equals(oitemstack) && itemstack1.isEmpty()) {
++ // CraftBukkit end
+ itemstack1 = itemstack.copy();
+ itemstack1.shrink(1);
+ } else {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/EndPortalBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/EndPortalBlock.java.patch
new file mode 100644
index 0000000000..5d28ce54a3
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/EndPortalBlock.java.patch
@@ -0,0 +1,46 @@
+--- a/net/minecraft/world/level/block/EndPortalBlock.java
++++ b/net/minecraft/world/level/block/EndPortalBlock.java
+@@ -20,6 +22,9 @@
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.Shapes;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++import org.bukkit.event.entity.EntityPortalEnterEvent;
++import org.bukkit.event.player.PlayerTeleportEvent;
++// CraftBukkit end
+
+ public class EndPortalBlock extends BaseEntityBlock {
+
+@@ -49,17 +51,25 @@
+ }
+
+ @Override
+- @Override
+- public void entityInside(BlockState blockstate, Level level, BlockPos blockpos, Entity entity) {
+- if (level instanceof ServerLevel && entity.canChangeDimensions() && Shapes.joinIsNotEmpty(Shapes.create(entity.getBoundingBox().move((double) (-blockpos.getX()), (double) (-blockpos.getY()), (double) (-blockpos.getZ()))), blockstate.getShape(level, blockpos), BooleanOp.AND)) {
+- ResourceKey<Level> resourcekey = level.dimension() == Level.END ? Level.OVERWORLD : Level.END;
+- ServerLevel serverlevel = ((ServerLevel) level).getServer().getLevel(resourcekey);
++ public void entityInside(IBlockData state, Level level, BlockPos pos, Entity entity) {
++ if (level instanceof ServerLevel && entity.canChangeDimensions() && Shapes.joinIsNotEmpty(Shapes.create(entity.getBoundingBox().move((double) (-pos.getX()), (double) (-pos.getY()), (double) (-pos.getZ()))), state.getShape(level, pos), BooleanOp.AND)) {
++ ResourceKey<Level> resourcekey = level.getTypeKey() == LevelStem.END ? Level.OVERWORLD : Level.END; // CraftBukkit - SPIGOT-6152: send back to main overworld in custom ends
++ ServerLevel worldserver = ((ServerLevel) level).getServer().getLevel(resourcekey);
+
+- if (serverlevel == null) {
+- return;
++ if (worldserver == null) {
++ // return; // CraftBukkit - always fire event in case plugins wish to change it
+ }
+
+- entity.changeDimension(serverlevel);
++ // CraftBukkit start - Entity in portal
++ EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(level.getWorld(), pos.getX(), pos.getY(), pos.getZ()));
++ level.getCraftServer().getPluginManager().callEvent(event);
++
++ if (entity instanceof ServerPlayer) {
++ ((ServerPlayer) entity).changeDimension(worldserver, PlayerTeleportEvent.TeleportCause.END_PORTAL);
++ return;
++ }
++ // CraftBukkit end
++ entity.changeDimension(worldserver);
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/FarmBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/FarmBlock.java.patch
new file mode 100644
index 0000000000..08770f993f
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/FarmBlock.java.patch
@@ -0,0 +1,75 @@
+--- a/net/minecraft/world/level/block/FarmBlock.java
++++ b/net/minecraft/world/level/block/FarmBlock.java
+@@ -28,6 +28,10 @@
+ import net.minecraft.world.level.pathfinder.PathComputationType;
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++// CraftBukkit start
++import org.bukkit.event.entity.EntityInteractEvent;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++// CraftBukkit end
+
+ public class FarmBlock extends Block {
+
+@@ -99,28 +95,50 @@
+
+ if (!isNearWater(serverlevel, blockpos) && !serverlevel.isRainingAt(blockpos.above())) {
+ if (i > 0) {
+- serverlevel.setBlock(blockpos, (BlockState) blockstate.setValue(FarmBlock.MOISTURE, i - 1), 2);
+- } else if (!shouldMaintainFarmland(serverlevel, blockpos)) {
+- turnToDirt((Entity) null, blockstate, serverlevel, blockpos);
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleMoistureChangeEvent(level, pos, (IBlockData) state.setValue(FarmBlock.MOISTURE, i - 1), 2); // CraftBukkit
++ } else if (!shouldMaintainFarmland(level, pos)) {
++ turnToDirt((Entity) null, state, level, pos);
+ }
+ } else if (i < 7) {
+- serverlevel.setBlock(blockpos, (BlockState) blockstate.setValue(FarmBlock.MOISTURE, 7), 2);
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleMoistureChangeEvent(level, pos, (IBlockData) state.setValue(FarmBlock.MOISTURE, 7), 2); // CraftBukkit
+ }
+
+ }
+
+ @Override
+- @Override
+- public void fallOn(Level level, BlockState blockstate, BlockPos blockpos, Entity entity, float f) {
+- if (!level.isClientSide && level.random.nextFloat() < f - 0.5F && entity instanceof LivingEntity && (entity instanceof Player || level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && entity.getBbWidth() * entity.getBbWidth() * entity.getBbHeight() > 0.512F) {
+- turnToDirt(entity, blockstate, level, blockpos);
++ public void fallOn(Level level, IBlockData state, BlockPos pos, Entity entity, float fallDistance) {
++ super.fallOn(level, state, pos, entity, fallDistance); // CraftBukkit - moved here as game rules / events shouldn't affect fall damage.
++ if (!level.isClientSide && level.random.nextFloat() < fallDistance - 0.5F && entity instanceof LivingEntity && (entity instanceof Player || level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && entity.getBbWidth() * entity.getBbWidth() * entity.getBbHeight() > 0.512F) {
++ // CraftBukkit start - Interact soil
++ org.bukkit.event.Cancellable cancellable;
++ if (entity instanceof Player) {
++ cancellable = CraftEventFactory.callPlayerInteractEvent((Player) entity, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null);
++ } else {
++ cancellable = new EntityInteractEvent(entity.getBukkitEntity(), level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()));
++ level.getCraftServer().getPluginManager().callEvent((EntityInteractEvent) cancellable);
++ }
++
++ if (cancellable.isCancelled()) {
++ return;
++ }
++
++ if (!CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.DIRT.defaultBlockState())) {
++ return;
++ }
++ // CraftBukkit end
++ turnToDirt(entity, state, level, pos);
+ }
+
+- super.fallOn(level, blockstate, blockpos, entity, f);
++ // super.fallOn(world, iblockdata, blockposition, entity, f); // CraftBukkit - moved up
+ }
+
+- public static void turnToDirt(@Nullable Entity entity, BlockState blockstate, Level level, BlockPos blockpos) {
+- BlockState blockstate1 = pushEntitiesUp(blockstate, Blocks.DIRT.defaultBlockState(), level, blockpos);
++ public static void turnToDirt(@Nullable Entity entity, IBlockData state, Level level, BlockPos pos) {
++ // CraftBukkit start
++ if (CraftEventFactory.callBlockFadeEvent(level, pos, Blocks.DIRT.defaultBlockState()).isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
++ IBlockData iblockdata1 = pushEntitiesUp(state, Blocks.DIRT.defaultBlockState(), level, pos);
+
+ level.setBlockAndUpdate(blockpos, blockstate1);
+ level.gameEvent(GameEvent.BLOCK_CHANGE, blockpos, GameEvent.Context.of(entity, blockstate1));
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/FenceGateBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/FenceGateBlock.java.patch
new file mode 100644
index 0000000000..09f55ee7bd
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/FenceGateBlock.java.patch
@@ -0,0 +1,22 @@
+--- a/net/minecraft/world/level/block/FenceGateBlock.java
++++ b/net/minecraft/world/level/block/FenceGateBlock.java
+@@ -178,7 +167,18 @@
+ @Override
+ public void neighborChanged(BlockState blockstate, Level level, BlockPos blockpos, Block block, BlockPos blockpos1, boolean flag) {
+ if (!level.isClientSide) {
+- boolean flag1 = level.hasNeighborSignal(blockpos);
++ boolean flag1 = level.hasNeighborSignal(pos);
++ // CraftBukkit start
++ boolean oldPowered = state.getValue(FenceGateBlock.POWERED);
++ if (oldPowered != flag1) {
++ int newPower = flag1 ? 15 : 0;
++ int oldPower = oldPowered ? 15 : 0;
++ org.bukkit.block.Block bukkitBlock = org.bukkit.craftbukkit.block.CraftBlock.at(level, pos);
++ org.bukkit.event.block.BlockRedstoneEvent eventRedstone = new org.bukkit.event.block.BlockRedstoneEvent(bukkitBlock, oldPower, newPower);
++ level.getCraftServer().getPluginManager().callEvent(eventRedstone);
++ flag1 = eventRedstone.getNewCurrent() > 0;
++ }
++ // CraftBukkit end
+
+ if ((Boolean) blockstate.getValue(FenceGateBlock.POWERED) != flag1) {
+ level.setBlock(blockpos, (BlockState) ((BlockState) blockstate.setValue(FenceGateBlock.POWERED, flag1)).setValue(FenceGateBlock.OPEN, flag1), 2);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/FireBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/FireBlock.java.patch
new file mode 100644
index 0000000000..02a611b44e
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/FireBlock.java.patch
@@ -0,0 +1,166 @@
+--- a/net/minecraft/world/level/block/FireBlock.java
++++ b/net/minecraft/world/level/block/FireBlock.java
+@@ -28,6 +28,13 @@
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.Shapes;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++// CraftBukkit start
++import org.bukkit.craftbukkit.block.CraftBlockState;
++import org.bukkit.craftbukkit.block.CraftBlockStates;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.block.BlockBurnEvent;
++import org.bukkit.event.block.BlockFadeEvent;
++// CraftBukkit end
+
+ public class FireBlock extends BaseFireBlock {
+
+@@ -100,9 +106,25 @@
+ }
+
+ @Override
+- @Override
+- public BlockState updateShape(BlockState blockstate, Direction direction, BlockState blockstate1, LevelAccessor levelaccessor, BlockPos blockpos, BlockPos blockpos1) {
+- return this.canSurvive(blockstate, levelaccessor, blockpos) ? this.getStateWithAge(levelaccessor, blockpos, (Integer) blockstate.getValue(FireBlock.AGE)) : Blocks.AIR.defaultBlockState();
++ public IBlockData updateShape(IBlockData state, Direction facing, IBlockData facingState, LevelAccessor level, BlockPos currentPos, BlockPos facingPos) {
++ // CraftBukkit start
++ if (!this.canSurvive(state, level, currentPos)) {
++ // Suppress during worldgen
++ if (!(level instanceof Level)) {
++ return Blocks.AIR.defaultBlockState();
++ }
++ CraftBlockState blockState = CraftBlockStates.getBlockState(level, currentPos);
++ blockState.setData(Blocks.AIR.defaultBlockState());
++
++ BlockFadeEvent event = new BlockFadeEvent(blockState.getBlock(), blockState);
++ ((Level) level).getCraftServer().getPluginManager().callEvent(event);
++
++ if (!event.isCancelled()) {
++ return blockState.getHandle();
++ }
++ }
++ return this.getStateWithAge(level, currentPos, (Integer) state.getValue(FireBlock.AGE));
++ // CraftBukkit end
+ }
+
+ @Override
+@@ -150,20 +169,19 @@
+ }
+
+ @Override
+- @Override
+- public void tick(BlockState blockstate, ServerLevel serverlevel, BlockPos blockpos, RandomSource randomsource) {
+- serverlevel.scheduleTick(blockpos, (Block) this, getFireTickDelay(serverlevel.random));
+- if (serverlevel.getGameRules().getBoolean(GameRules.RULE_DOFIRETICK)) {
+- if (!blockstate.canSurvive(serverlevel, blockpos)) {
+- serverlevel.removeBlock(blockpos, false);
++ public void tick(IBlockData state, ServerLevel level, BlockPos pos, RandomSource random) {
++ level.scheduleTick(pos, (Block) this, getFireTickDelay(level.random));
++ if (level.getGameRules().getBoolean(GameRules.RULE_DOFIRETICK)) {
++ if (!state.canSurvive(level, pos)) {
++ fireExtinguished(level, pos); // CraftBukkit - invalid place location
+ }
+
+ BlockState blockstate1 = serverlevel.getBlockState(blockpos.below());
+ boolean flag = blockstate1.is(serverlevel.dimensionType().infiniburn());
+ int i = (Integer) blockstate.getValue(FireBlock.AGE);
+
+- if (!flag && serverlevel.isRaining() && this.isNearRain(serverlevel, blockpos) && randomsource.nextFloat() < 0.2F + (float) i * 0.03F) {
+- serverlevel.removeBlock(blockpos, false);
++ if (!flag && level.isRaining() && this.isNearRain(level, pos) && random.nextFloat() < 0.2F + (float) i * 0.03F) {
++ fireExtinguished(level, pos); // CraftBukkit - extinguished by rain
+ } else {
+ int j = Math.min(15, i + randomsource.nextInt(3) / 2);
+
+@@ -176,15 +194,15 @@
+ if (!this.isValidFireLocation(serverlevel, blockpos)) {
+ BlockPos blockpos1 = blockpos.below();
+
+- if (!serverlevel.getBlockState(blockpos1).isFaceSturdy(serverlevel, blockpos1, Direction.UP) || i > 3) {
+- serverlevel.removeBlock(blockpos, false);
++ if (!level.getBlockState(blockposition1).isFaceSturdy(level, blockposition1, Direction.UP) || i > 3) {
++ fireExtinguished(level, pos); // CraftBukkit
+ }
+
+ return;
+ }
+
+- if (i == 15 && randomsource.nextInt(4) == 0 && !this.canBurn(serverlevel.getBlockState(blockpos.below()))) {
+- serverlevel.removeBlock(blockpos, false);
++ if (i == 15 && random.nextInt(4) == 0 && !this.canBurn(level.getBlockState(pos.below()))) {
++ fireExtinguished(level, pos); // CraftBukkit
+ return;
+ }
+ }
+@@ -192,13 +210,15 @@
+ boolean flag1 = serverlevel.getBiome(blockpos).is(BiomeTags.INCREASED_FIRE_BURNOUT);
+ int k = flag1 ? -50 : 0;
+
+- this.checkBurnOut(serverlevel, blockpos.east(), 300 + k, randomsource, i);
+- this.checkBurnOut(serverlevel, blockpos.west(), 300 + k, randomsource, i);
+- this.checkBurnOut(serverlevel, blockpos.below(), 250 + k, randomsource, i);
+- this.checkBurnOut(serverlevel, blockpos.above(), 250 + k, randomsource, i);
+- this.checkBurnOut(serverlevel, blockpos.north(), 300 + k, randomsource, i);
+- this.checkBurnOut(serverlevel, blockpos.south(), 300 + k, randomsource, i);
+- BlockPos.MutableBlockPos blockpos_mutableblockpos = new BlockPos.MutableBlockPos();
++ // CraftBukkit start - add source blockposition to burn calls
++ this.trySpread(level, pos.east(), 300 + k, random, i, pos);
++ this.trySpread(level, pos.west(), 300 + k, random, i, pos);
++ this.trySpread(level, pos.below(), 250 + k, random, i, pos);
++ this.trySpread(level, pos.above(), 250 + k, random, i, pos);
++ this.trySpread(level, pos.north(), 300 + k, random, i, pos);
++ this.trySpread(level, pos.south(), 300 + k, random, i, pos);
++ // CraftBukkit end
++ BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos();
+
+ for (int l = -1; l <= 1; ++l) {
+ for (int i1 = -1; i1 <= 1; ++i1) {
+@@ -223,7 +243,15 @@
+ if (i2 > 0 && randomsource.nextInt(k1) <= i2 && (!serverlevel.isRaining() || !this.isNearRain(serverlevel, blockpos_mutableblockpos))) {
+ int j2 = Math.min(15, i + randomsource.nextInt(5) / 4);
+
+- serverlevel.setBlock(blockpos_mutableblockpos, this.getStateWithAge(serverlevel, blockpos_mutableblockpos, j2), 3);
++ // CraftBukkit start - Call to stop spread of fire
++ if (level.getBlockState(blockposition_mutableblockposition).getBlock() != Blocks.FIRE) {
++ if (CraftEventFactory.callBlockIgniteEvent(level, blockposition_mutableblockposition, pos).isCancelled()) {
++ continue;
++ }
++
++ CraftEventFactory.handleBlockSpreadEvent(level, pos, blockposition_mutableblockposition, this.getStateWithAge(level, blockposition_mutableblockposition, j2), 3); // CraftBukkit
++ }
++ // CraftBukkit end
+ }
+ }
+ }
+@@ -247,13 +275,29 @@
+ return blockstate.hasProperty(BlockStateProperties.WATERLOGGED) && (Boolean) blockstate.getValue(BlockStateProperties.WATERLOGGED) ? 0 : this.igniteOdds.getInt(blockstate.getBlock());
+ }
+
+- private void checkBurnOut(Level level, BlockPos blockpos, int i, RandomSource randomsource, int j) {
+- int k = this.getBurnOdds(level.getBlockState(blockpos));
++ private void trySpread(Level world, BlockPos blockposition, int i, RandomSource randomsource, int j, BlockPos sourceposition) { // CraftBukkit add sourceposition
++ int k = this.getBurnOdds(world.getBlockState(blockposition));
+
+ if (randomsource.nextInt(i) < k) {
+ BlockState blockstate = level.getBlockState(blockpos);
+
+- if (randomsource.nextInt(j + 10) < 5 && !level.isRainingAt(blockpos)) {
++ // CraftBukkit start
++ org.bukkit.block.Block theBlock = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ org.bukkit.block.Block sourceBlock = world.getWorld().getBlockAt(sourceposition.getX(), sourceposition.getY(), sourceposition.getZ());
++
++ BlockBurnEvent event = new BlockBurnEvent(theBlock, sourceBlock);
++ world.getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return;
++ }
++
++ if (iblockdata.getBlock() instanceof TntBlock && !CraftEventFactory.callTNTPrimeEvent(world, blockposition, org.bukkit.event.block.TNTPrimeEvent.PrimeCause.FIRE, null, sourceposition)) {
++ return;
++ }
++ // CraftBukkit end
++
++ if (randomsource.nextInt(j + 10) < 5 && !world.isRainingAt(blockposition)) {
+ int l = Math.min(j + randomsource.nextInt(5) / 4, 15);
+
+ level.setBlock(blockpos, this.getStateWithAge(level, blockpos, l), 3);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/FungusBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/FungusBlock.java.patch
new file mode 100644
index 0000000000..d0ae076543
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/FungusBlock.java.patch
@@ -0,0 +1,23 @@
+--- a/net/minecraft/world/level/block/FungusBlock.java
++++ b/net/minecraft/world/level/block/FungusBlock.java
+@@ -77,10 +72,16 @@
+ }
+
+ @Override
+- @Override
+- public void performBonemeal(ServerLevel serverlevel, RandomSource randomsource, BlockPos blockpos, BlockState blockstate) {
+- this.getFeature(serverlevel).ifPresent((holder) -> {
+- ((ConfiguredFeature) holder.value()).place(serverlevel, serverlevel.getChunkSource().getGenerator(), randomsource, blockpos);
++ public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, IBlockData state) {
++ this.getFeature(level).ifPresent((holder) -> {
++ // CraftBukkit start
++ if (this == Blocks.WARPED_FUNGUS) {
++ SaplingBlock.treeType = org.bukkit.TreeType.WARPED_FUNGUS;
++ } else if (this == Blocks.CRIMSON_FUNGUS) {
++ SaplingBlock.treeType = org.bukkit.TreeType.CRIMSON_FUNGUS;
++ }
++ // CraftBukkit end
++ ((ConfiguredFeature) holder.value()).place(level, level.getChunkSource().getGenerator(), random, pos);
+ });
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/GrowingPlantHeadBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/GrowingPlantHeadBlock.java.patch
new file mode 100644
index 0000000000..4fe796fa43
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/GrowingPlantHeadBlock.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/world/level/block/GrowingPlantHeadBlock.java
++++ b/net/minecraft/world/level/block/GrowingPlantHeadBlock.java
+@@ -51,8 +47,8 @@
+ if ((Integer) blockstate.getValue(GrowingPlantHeadBlock.AGE) < 25 && randomsource.nextDouble() < this.growPerTickProbability) {
+ BlockPos blockpos1 = blockpos.relative(this.growthDirection);
+
+- if (this.canGrowInto(serverlevel.getBlockState(blockpos1))) {
+- serverlevel.setBlockAndUpdate(blockpos1, this.getGrowIntoState(blockstate, serverlevel.random));
++ if (this.canGrowInto(level.getBlockState(blockposition1))) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, blockposition1, this.getGrowIntoState(state, level.random)); // CraftBukkit
+ }
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/IceBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/IceBlock.java.patch
new file mode 100644
index 0000000000..67c1c37c89
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/IceBlock.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/world/level/block/IceBlock.java
++++ b/net/minecraft/world/level/block/IceBlock.java
+@@ -61,7 +58,12 @@
+
+ }
+
+- protected void melt(BlockState blockstate, Level level, BlockPos blockpos) {
++ protected void melt(IBlockData state, Level level, BlockPos pos) {
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(level, pos, level.dimensionType().ultraWarm() ? Blocks.AIR.defaultBlockState() : Blocks.WATER.defaultBlockState()).isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
+ if (level.dimensionType().ultraWarm()) {
+ level.removeBlock(blockpos, false);
+ } else {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/InfestedBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/InfestedBlock.java.patch
new file mode 100644
index 0000000000..9e735c66d2
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/InfestedBlock.java.patch
@@ -0,0 +1,25 @@
+--- a/net/minecraft/world/level/block/InfestedBlock.java
++++ b/net/minecraft/world/level/block/InfestedBlock.java
+@@ -18,6 +18,7 @@
+ import net.minecraft.world.level.block.state.BlockBehaviour;
+ import net.minecraft.world.level.block.state.BlockState;
+ import net.minecraft.world.level.block.state.properties.Property;
++import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; // CraftBukkit
+
+ public class InfestedBlock extends Block {
+
+@@ -52,10 +52,10 @@
+ private void spawnInfestation(ServerLevel serverlevel, BlockPos blockpos) {
+ Silverfish silverfish = (Silverfish) EntityType.SILVERFISH.create(serverlevel);
+
+- if (silverfish != null) {
+- silverfish.moveTo((double) blockpos.getX() + 0.5D, (double) blockpos.getY(), (double) blockpos.getZ() + 0.5D, 0.0F, 0.0F);
+- serverlevel.addFreshEntity(silverfish);
+- silverfish.spawnAnim();
++ if (entitysilverfish != null) {
++ entitysilverfish.moveTo((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D, 0.0F, 0.0F);
++ level.addFreshEntity(entitysilverfish, SpawnReason.SILVERFISH_BLOCK); // CraftBukkit - add SpawnReason
++ entitysilverfish.spawnAnim();
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/LayeredCauldronBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/LayeredCauldronBlock.java.patch
new file mode 100644
index 0000000000..62f7ac2b1c
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/LayeredCauldronBlock.java.patch
@@ -0,0 +1,100 @@
+--- a/net/minecraft/world/level/block/LayeredCauldronBlock.java
++++ b/net/minecraft/world/level/block/LayeredCauldronBlock.java
+@@ -15,6 +15,11 @@
+ import net.minecraft.world.level.gameevent.GameEvent;
+ import net.minecraft.world.level.material.Fluid;
+ import net.minecraft.world.level.material.Fluids;
++// CraftBukkit start
++import org.bukkit.craftbukkit.block.CraftBlockState;
++import org.bukkit.craftbukkit.block.CraftBlockStates;
++import org.bukkit.event.block.CauldronLevelChangeEvent;
++// CraftBukkit end
+
+ public class LayeredCauldronBlock extends AbstractCauldronBlock {
+
+@@ -63,13 +64,16 @@
+ }
+
+ @Override
+- @Override
+- public void entityInside(BlockState blockstate, Level level, BlockPos blockpos, Entity entity) {
+- if (!level.isClientSide && entity.isOnFire() && this.isEntityInsideContent(blockstate, blockpos, entity)) {
+- entity.clearFire();
+- if (entity.mayInteract(level, blockpos)) {
+- this.handleEntityOnFireInside(blockstate, level, blockpos);
++ public void entityInside(IBlockData state, Level level, BlockPos pos, Entity entity) {
++ if (!level.isClientSide && entity.isOnFire() && this.isEntityInsideContent(state, pos, entity)) {
++ // CraftBukkit start
++ if (entity.mayInteract(level, pos)) {
++ if (!lowerFillLevel(state, level, pos, entity, CauldronLevelChangeEvent.ChangeReason.EXTINGUISH)) {
++ return;
++ }
+ }
++ entity.clearFire();
++ // CraftBukkit end
+ }
+
+ }
+@@ -83,22 +87,41 @@
+
+ }
+
+- public static void lowerFillLevel(BlockState blockstate, Level level, BlockPos blockpos) {
+- int i = (Integer) blockstate.getValue(LayeredCauldronBlock.LEVEL) - 1;
+- BlockState blockstate1 = i == 0 ? Blocks.CAULDRON.defaultBlockState() : (BlockState) blockstate.setValue(LayeredCauldronBlock.LEVEL, i);
++ public static void lowerFillLevel(IBlockData state, Level level, BlockPos pos) {
++ // CraftBukkit start
++ lowerFillLevel(state, level, pos, null, CauldronLevelChangeEvent.ChangeReason.UNKNOWN);
++ }
+
+ level.setBlockAndUpdate(blockpos, blockstate1);
+ level.gameEvent(GameEvent.BLOCK_CHANGE, blockpos, GameEvent.Context.of(blockstate1));
+ }
+
++ // CraftBukkit start
++ public static boolean changeLevel(IBlockData iblockdata, Level world, BlockPos blockposition, IBlockData newBlock, Entity entity, CauldronLevelChangeEvent.ChangeReason reason) {
++ CraftBlockState newState = CraftBlockStates.getBlockState(world, blockposition);
++ newState.setData(newBlock);
++
++ CauldronLevelChangeEvent event = new CauldronLevelChangeEvent(
++ world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()),
++ (entity == null) ? null : entity.getBukkitEntity(), reason, newState
++ );
++ world.getCraftServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ return false;
++ }
++ newState.update(true);
++ world.gameEvent(GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of(newBlock));
++ return true;
++ }
++ // CraftBukkit end
++
+ @Override
+ @Override
+ public void handlePrecipitation(BlockState blockstate, Level level, BlockPos blockpos, Biome.Precipitation biome_precipitation) {
+ if (CauldronBlock.shouldHandlePrecipitation(level, biome_precipitation) && (Integer) blockstate.getValue(LayeredCauldronBlock.LEVEL) != 3 && biome_precipitation == this.precipitationType) {
+ BlockState blockstate1 = (BlockState) blockstate.cycle(LayeredCauldronBlock.LEVEL);
+
+- level.setBlockAndUpdate(blockpos, blockstate1);
+- level.gameEvent(GameEvent.BLOCK_CHANGE, blockpos, GameEvent.Context.of(blockstate1));
++ changeLevel(state, level, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL); // CraftBukkit
+ }
+ }
+
+@@ -120,9 +142,12 @@
+ if (!this.isFull(blockstate)) {
+ BlockState blockstate1 = (BlockState) blockstate.setValue(LayeredCauldronBlock.LEVEL, (Integer) blockstate.getValue(LayeredCauldronBlock.LEVEL) + 1);
+
+- level.setBlockAndUpdate(blockpos, blockstate1);
+- level.gameEvent(GameEvent.BLOCK_CHANGE, blockpos, GameEvent.Context.of(blockstate1));
+- level.levelEvent(1047, blockpos, 0);
++ // CraftBukkit start
++ if (!changeLevel(state, level, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) {
++ return;
++ }
++ // CraftBukkit end
++ level.levelEvent(1047, pos, 0);
+ }
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/LeavesBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/LeavesBlock.java.patch
new file mode 100644
index 0000000000..84f1ba0a95
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/LeavesBlock.java.patch
@@ -0,0 +1,34 @@
+--- a/net/minecraft/world/level/block/LeavesBlock.java
++++ b/net/minecraft/world/level/block/LeavesBlock.java
+@@ -24,6 +24,7 @@
+ import net.minecraft.world.level.material.Fluids;
+ import net.minecraft.world.phys.shapes.Shapes;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++import org.bukkit.event.block.LeavesDecayEvent; // CraftBukkit
+
+ public class LeavesBlock extends Block implements SimpleWaterloggedBlock {
+
+@@ -58,11 +56,18 @@
+ }
+
+ @Override
+- @Override
+- public void randomTick(BlockState blockstate, ServerLevel serverlevel, BlockPos blockpos, RandomSource randomsource) {
+- if (this.decaying(blockstate)) {
+- dropResources(blockstate, serverlevel, blockpos);
+- serverlevel.removeBlock(blockpos, false);
++ public void randomTick(IBlockData state, ServerLevel level, BlockPos pos, RandomSource random) {
++ if (this.decaying(state)) {
++ // CraftBukkit start
++ LeavesDecayEvent event = new LeavesDecayEvent(level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()));
++ level.getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled() || level.getBlockState(pos).getBlock() != this) {
++ return;
++ }
++ // CraftBukkit end
++ dropResources(state, level, pos);
++ level.removeBlock(pos, false);
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/LecternBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/LecternBlock.java.patch
new file mode 100644
index 0000000000..8c4d06e92f
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/LecternBlock.java.patch
@@ -0,0 +1,29 @@
+--- a/net/minecraft/world/level/block/LecternBlock.java
++++ b/net/minecraft/world/level/block/LecternBlock.java
+@@ -219,16 +206,17 @@
+ }
+ }
+
+- private void popBook(BlockState blockstate, Level level, BlockPos blockpos) {
+- BlockEntity blockentity = level.getBlockEntity(blockpos);
++ private void popBook(IBlockData state, Level level, BlockPos pos) {
++ BlockEntity tileentity = level.getBlockEntity(pos, false); // CraftBukkit - don't validate, type may be changed already
+
+- if (blockentity instanceof LecternBlockEntity) {
+- LecternBlockEntity lecternblockentity = (LecternBlockEntity) blockentity;
+- Direction direction = (Direction) blockstate.getValue(LecternBlock.FACING);
+- ItemStack itemstack = lecternblockentity.getBook().copy();
+- float f = 0.25F * (float) direction.getStepX();
+- float f1 = 0.25F * (float) direction.getStepZ();
+- ItemEntity itementity = new ItemEntity(level, (double) blockpos.getX() + 0.5D + (double) f, (double) (blockpos.getY() + 1), (double) blockpos.getZ() + 0.5D + (double) f1, itemstack);
++ if (tileentity instanceof LecternBlockEntity) {
++ LecternBlockEntity tileentitylectern = (LecternBlockEntity) tileentity;
++ Direction enumdirection = (Direction) state.getValue(LecternBlock.FACING);
++ ItemStack itemstack = tileentitylectern.getBook().copy();
++ if (itemstack.isEmpty()) return; // CraftBukkit - SPIGOT-5500
++ float f = 0.25F * (float) enumdirection.getStepX();
++ float f1 = 0.25F * (float) enumdirection.getStepZ();
++ ItemEntity entityitem = new ItemEntity(level, (double) pos.getX() + 0.5D + (double) f, (double) (pos.getY() + 1), (double) pos.getZ() + 0.5D + (double) f1, itemstack);
+
+ itementity.setDefaultPickUpDelay();
+ level.addFreshEntity(itementity);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/LeverBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/LeverBlock.java.patch
new file mode 100644
index 0000000000..fe17dcb9ed
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/LeverBlock.java.patch
@@ -0,0 +1,40 @@
+--- a/net/minecraft/world/level/block/LeverBlock.java
++++ b/net/minecraft/world/level/block/LeverBlock.java
+@@ -27,6 +27,7 @@
+ import net.minecraft.world.phys.BlockHitResult;
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit
+
+ public class LeverBlock extends FaceAttachedHorizontalDirectionalBlock {
+
+@@ -104,11 +102,25 @@
+
+ return InteractionResult.SUCCESS;
+ } else {
+- blockstate1 = this.pull(blockstate, level, blockpos);
+- float f = (Boolean) blockstate1.getValue(LeverBlock.POWERED) ? 0.6F : 0.5F;
++ // CraftBukkit start - Interact Lever
++ boolean powered = state.getValue(LeverBlock.POWERED); // Old powered state
++ org.bukkit.block.Block block = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++ int old = (powered) ? 15 : 0;
++ int current = (!powered) ? 15 : 0;
+
+- level.playSound((Player) null, blockpos, SoundEvents.LEVER_CLICK, SoundSource.BLOCKS, 0.3F, f);
+- level.gameEvent((Entity) player, (Boolean) blockstate1.getValue(LeverBlock.POWERED) ? GameEvent.BLOCK_ACTIVATE : GameEvent.BLOCK_DEACTIVATE, blockpos);
++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(block, old, current);
++ level.getCraftServer().getPluginManager().callEvent(eventRedstone);
++
++ if ((eventRedstone.getNewCurrent() > 0) != (!powered)) {
++ return InteractionResult.SUCCESS;
++ }
++ // CraftBukkit end
++
++ iblockdata1 = this.pull(state, level, pos);
++ float f = (Boolean) iblockdata1.getValue(LeverBlock.POWERED) ? 0.6F : 0.5F;
++
++ level.playSound((Player) null, pos, SoundEvents.LEVER_CLICK, SoundSource.BLOCKS, 0.3F, f);
++ level.gameEvent((Entity) player, (Boolean) iblockdata1.getValue(LeverBlock.POWERED) ? GameEvent.BLOCK_ACTIVATE : GameEvent.BLOCK_DEACTIVATE, pos);
+ return InteractionResult.CONSUME;
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/LightningRodBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/LightningRodBlock.java.patch
new file mode 100644
index 0000000000..41efe02dd4
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/LightningRodBlock.java.patch
@@ -0,0 +1,53 @@
+--- a/net/minecraft/world/level/block/LightningRodBlock.java
++++ b/net/minecraft/world/level/block/LightningRodBlock.java
+@@ -32,6 +32,10 @@
+ import net.minecraft.world.level.material.Fluids;
+ import net.minecraft.world.phys.BlockHitResult;
+ import net.minecraft.world.phys.Vec3;
++// CraftBukkit start
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.event.block.BlockRedstoneEvent;
++// CraftBukkit end
+
+ public class LightningRodBlock extends RodBlock implements SimpleWaterloggedBlock {
+
+@@ -90,11 +88,23 @@
+ return (Boolean) blockstate.getValue(LightningRodBlock.POWERED) && blockstate.getValue(LightningRodBlock.FACING) == direction ? 15 : 0;
+ }
+
+- public void onLightningStrike(BlockState blockstate, Level level, BlockPos blockpos) {
+- level.setBlock(blockpos, (BlockState) blockstate.setValue(LightningRodBlock.POWERED, true), 3);
+- this.updateNeighbours(blockstate, level, blockpos);
+- level.scheduleTick(blockpos, (Block) this, 8);
+- level.levelEvent(3002, blockpos, ((Direction) blockstate.getValue(LightningRodBlock.FACING)).getAxis().ordinal());
++ public void onLightningStrike(IBlockData state, Level level, BlockPos pos) {
++ // CraftBukkit start
++ boolean powered = state.getValue(LightningRodBlock.POWERED);
++ int old = (powered) ? 15 : 0;
++ int current = (!powered) ? 15 : 0;
++
++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(CraftBlock.at(level, pos), old, current);
++ level.getCraftServer().getPluginManager().callEvent(eventRedstone);
++
++ if (eventRedstone.getNewCurrent() <= 0) {
++ return;
++ }
++ // CraftBukkit end
++ level.setBlock(pos, (IBlockData) state.setValue(LightningRodBlock.POWERED, true), 3);
++ this.updateNeighbours(state, level, pos);
++ level.scheduleTick(pos, (Block) this, 8);
++ level.levelEvent(3002, pos, ((Direction) state.getValue(LightningRodBlock.FACING)).getAxis().ordinal());
+ }
+
+ private void updateNeighbours(BlockState blockstate, Level level, BlockPos blockpos) {
+@@ -152,8 +157,8 @@
+ lightningbolt.moveTo(Vec3.atBottomCenterOf(blockpos.above()));
+ Entity entity = projectile.getOwner();
+
+- lightningbolt.setCause(entity instanceof ServerPlayer ? (ServerPlayer) entity : null);
+- level.addFreshEntity(lightningbolt);
++ entitylightning.setCause(entity instanceof ServerPlayer ? (ServerPlayer) entity : null);
++ ((ServerLevel) level).strikeLightning(entitylightning, org.bukkit.event.weather.LightningStrikeEvent.Cause.TRIDENT); // CraftBukkit
+ }
+
+ level.playSound((Player) null, blockpos, SoundEvents.TRIDENT_THUNDER, SoundSource.WEATHER, 5.0F, 1.0F);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/LiquidBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/LiquidBlock.java.patch
new file mode 100644
index 0000000000..bf57f95334
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/LiquidBlock.java.patch
@@ -0,0 +1,28 @@
+--- a/net/minecraft/world/level/block/LiquidBlock.java
++++ b/net/minecraft/world/level/block/LiquidBlock.java
+@@ -188,14 +174,20 @@
+ if (level.getFluidState(blockpos1).is(FluidTags.WATER)) {
+ Block block = level.getFluidState(blockpos).isSource() ? Blocks.OBSIDIAN : Blocks.COBBLESTONE;
+
+- level.setBlockAndUpdate(blockpos, block.defaultBlockState());
+- this.fizz(level, blockpos);
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(level, pos, block.defaultBlockState())) {
++ this.fizz(level, pos);
++ }
++ // CraftBukkit end
+ return false;
+ }
+
+- if (flag && level.getBlockState(blockpos1).is(Blocks.BLUE_ICE)) {
+- level.setBlockAndUpdate(blockpos, Blocks.BASALT.defaultBlockState());
+- this.fizz(level, blockpos);
++ if (flag && level.getBlockState(blockposition1).is(Blocks.BLUE_ICE)) {
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(level, pos, Blocks.BASALT.defaultBlockState())) {
++ this.fizz(level, pos);
++ }
++ // CraftBukkit end
+ return false;
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/MagmaBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/MagmaBlock.java.patch
new file mode 100644
index 0000000000..95f3de5de8
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/MagmaBlock.java.patch
@@ -0,0 +1,12 @@
+--- a/net/minecraft/world/level/block/MagmaBlock.java
++++ b/net/minecraft/world/level/block/MagmaBlock.java
+@@ -32,7 +30,9 @@
+ @Override
+ public void stepOn(Level level, BlockPos blockpos, BlockState blockstate, Entity entity) {
+ if (!entity.isSteppingCarefully() && entity instanceof LivingEntity && !EnchantmentHelper.hasFrostWalker((LivingEntity) entity)) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); // CraftBukkit
+ entity.hurt(level.damageSources().hotFloor(), 1.0F);
++ org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = null; // CraftBukkit
+ }
+
+ super.stepOn(level, blockpos, blockstate, entity);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/MultifaceSpreader.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/MultifaceSpreader.java.patch
new file mode 100644
index 0000000000..0582fb0d0e
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/MultifaceSpreader.java.patch
@@ -0,0 +1,50 @@
+--- a/net/minecraft/world/level/block/MultifaceSpreader.java
++++ b/net/minecraft/world/level/block/MultifaceSpreader.java
+@@ -158,7 +156,7 @@
+ levelaccessor.getChunk(multifacespreader_spreadpos.pos()).markPosForPostprocessing(multifacespreader_spreadpos.pos());
+ }
+
+- return levelaccessor.setBlock(multifacespreader_spreadpos.pos(), blockstate1, 2);
++ return org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos.source(), pos.pos(), iblockdata1, 2); // CraftBukkit
+ } else {
+ return false;
+ }
+@@ -175,23 +173,20 @@
+
+ SAME_POSITION {
+ @Override
+- @Override
+- public MultifaceSpreader.SpreadPos getSpreadPos(BlockPos blockpos, Direction direction, Direction direction1) {
+- return new MultifaceSpreader.SpreadPos(blockpos, direction);
++ public MultifaceSpreader.c getSpreadPos(BlockPos pos, Direction face, Direction spreadDirection) {
++ return new MultifaceSpreader.c(pos, face, pos); // CraftBukkit
+ }
+ },
+ SAME_PLANE {
+ @Override
+- @Override
+- public MultifaceSpreader.SpreadPos getSpreadPos(BlockPos blockpos, Direction direction, Direction direction1) {
+- return new MultifaceSpreader.SpreadPos(blockpos.relative(direction), direction1);
++ public MultifaceSpreader.c getSpreadPos(BlockPos pos, Direction face, Direction spreadDirection) {
++ return new MultifaceSpreader.c(pos.relative(face), spreadDirection, pos); // CraftBukkit
+ }
+ },
+ WRAP_AROUND {
+ @Override
+- @Override
+- public MultifaceSpreader.SpreadPos getSpreadPos(BlockPos blockpos, Direction direction, Direction direction1) {
+- return new MultifaceSpreader.SpreadPos(blockpos.relative(direction).relative(direction1), direction.getOpposite());
++ public MultifaceSpreader.c getSpreadPos(BlockPos pos, Direction face, Direction spreadDirection) {
++ return new MultifaceSpreader.c(pos.relative(face).relative(spreadDirection), face.getOpposite(), pos); // CraftBukkit
+ }
+ };
+
+@@ -200,7 +195,7 @@
+ public abstract MultifaceSpreader.SpreadPos getSpreadPos(BlockPos pos, Direction face, Direction spreadDirection);
+ }
+
+- public static record SpreadPos(BlockPos pos, Direction face) {
++ public static record c(BlockPos pos, Direction face, BlockPos source) { // CraftBukkit
+
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/MushroomBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/MushroomBlock.java.patch
new file mode 100644
index 0000000000..1898945308
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/MushroomBlock.java.patch
@@ -0,0 +1,35 @@
+--- a/net/minecraft/world/level/block/MushroomBlock.java
++++ b/net/minecraft/world/level/block/MushroomBlock.java
+@@ -19,6 +19,9 @@
+ import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++// CraftBukkit start
++import org.bukkit.TreeType;
++// CraftBukkit end
+
+ public class MushroomBlock extends BushBlock implements BonemealableBlock {
+
+@@ -77,8 +77,8 @@
+ blockpos2 = blockpos.offset(randomsource.nextInt(3) - 1, randomsource.nextInt(2) - randomsource.nextInt(2), randomsource.nextInt(3) - 1);
+ }
+
+- if (serverlevel.isEmptyBlock(blockpos2) && blockstate.canSurvive(serverlevel, blockpos2)) {
+- serverlevel.setBlock(blockpos2, blockstate, 2);
++ if (level.isEmptyBlock(blockposition2) && state.canSurvive(level, blockposition2)) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, blockposition2, state, 2); // CraftBukkit
+ }
+ }
+
+@@ -105,8 +103,9 @@
+ if (optional.isEmpty()) {
+ return false;
+ } else {
+- serverlevel.removeBlock(blockpos, false);
+- if (((ConfiguredFeature) ((Holder) optional.get()).value()).place(serverlevel, serverlevel.getChunkSource().getGenerator(), randomsource, blockpos)) {
++ level.removeBlock(pos, false);
++ SaplingBlock.treeType = (this == Blocks.BROWN_MUSHROOM) ? TreeType.BROWN_MUSHROOM : TreeType.BROWN_MUSHROOM; // CraftBukkit
++ if (((ConfiguredFeature) ((Holder) optional.get()).value()).place(level, level.getChunkSource().getGenerator(), random, pos)) {
+ return true;
+ } else {
+ serverlevel.setBlock(blockpos, blockstate, 3);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/NetherPortalBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/NetherPortalBlock.java.patch
new file mode 100644
index 0000000000..c3a45fdb2b
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/NetherPortalBlock.java.patch
@@ -0,0 +1,37 @@
+--- a/net/minecraft/world/level/block/NetherPortalBlock.java
++++ b/net/minecraft/world/level/block/NetherPortalBlock.java
+@@ -25,6 +25,9 @@
+ import net.minecraft.world.level.portal.PortalShape;
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++// CraftBukkit start
++import org.bukkit.event.entity.EntityPortalEnterEvent;
++// CraftBukkit end
+
+ public class NetherPortalBlock extends Block {
+
+@@ -65,8 +65,9 @@
+ blockpos = blockpos.below();
+ }
+
+- if (serverlevel.getBlockState(blockpos).isValidSpawn(serverlevel, blockpos, EntityType.ZOMBIFIED_PIGLIN)) {
+- Entity entity = EntityType.ZOMBIFIED_PIGLIN.spawn(serverlevel, blockpos.above(), MobSpawnType.STRUCTURE);
++ if (level.getBlockState(pos).isValidSpawn(level, pos, EntityType.ZOMBIFIED_PIGLIN)) {
++ // CraftBukkit - set spawn reason to NETHER_PORTAL
++ Entity entity = EntityType.ZOMBIFIED_PIGLIN.spawn(level, pos.above(), EnumMobSpawn.STRUCTURE, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NETHER_PORTAL);
+
+ if (entity != null) {
+ entity.setPortalCooldown();
+@@ -90,7 +89,11 @@
+ @Override
+ public void entityInside(BlockState blockstate, Level level, BlockPos blockpos, Entity entity) {
+ if (entity.canChangeDimensions()) {
+- entity.handleInsidePortal(blockpos);
++ // CraftBukkit start - Entity in portal
++ EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(level.getWorld(), pos.getX(), pos.getY(), pos.getZ()));
++ level.getCraftServer().getPluginManager().callEvent(event);
++ // CraftBukkit end
++ entity.handleInsidePortal(pos);
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/NetherWartBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/NetherWartBlock.java.patch
new file mode 100644
index 0000000000..07d93631f7
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/NetherWartBlock.java.patch
@@ -0,0 +1,15 @@
+--- a/net/minecraft/world/level/block/NetherWartBlock.java
++++ b/net/minecraft/world/level/block/NetherWartBlock.java
+@@ -57,9 +52,9 @@
+ public void randomTick(BlockState blockstate, ServerLevel serverlevel, BlockPos blockpos, RandomSource randomsource) {
+ int i = (Integer) blockstate.getValue(NetherWartBlock.AGE);
+
+- if (i < 3 && randomsource.nextInt(10) == 0) {
+- blockstate = (BlockState) blockstate.setValue(NetherWartBlock.AGE, i + 1);
+- serverlevel.setBlock(blockpos, blockstate, 2);
++ if (i < 3 && random.nextInt(10) == 0) {
++ state = (IBlockData) state.setValue(NetherWartBlock.AGE, i + 1);
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos, state, 2); // CraftBukkit
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/NoteBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/NoteBlock.java.patch
new file mode 100644
index 0000000000..cee86bd747
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/NoteBlock.java.patch
@@ -0,0 +1,33 @@
+--- a/net/minecraft/world/level/block/NoteBlock.java
++++ b/net/minecraft/world/level/block/NoteBlock.java
+@@ -85,7 +81,8 @@
+
+ if (flag1 != (Boolean) blockstate.getValue(NoteBlock.POWERED)) {
+ if (flag1) {
+- this.playNote((Entity) null, blockstate, level, blockpos);
++ this.playNote((Entity) null, state, level, pos);
++ state = level.getBlockState(pos); // CraftBukkit - SPIGOT-5617: update in case changed in event
+ }
+
+ level.setBlock(blockpos, (BlockState) blockstate.setValue(NoteBlock.POWERED, flag1), 3);
+@@ -93,10 +90,16 @@
+
+ }
+
+- private void playNote(@Nullable Entity entity, BlockState blockstate, Level level, BlockPos blockpos) {
+- if (((NoteBlockInstrument) blockstate.getValue(NoteBlock.INSTRUMENT)).worksAboveNoteBlock() || level.getBlockState(blockpos.above()).isAir()) {
+- level.blockEvent(blockpos, this, 0, 0);
+- level.gameEvent(entity, GameEvent.NOTE_BLOCK_PLAY, blockpos);
++ private void playNote(@Nullable Entity entity, IBlockData state, Level level, BlockPos pos) {
++ if (((NoteBlockInstrument) state.getValue(NoteBlock.INSTRUMENT)).worksAboveNoteBlock() || level.getBlockState(pos.above()).isAir()) {
++ // CraftBukkit start
++ org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(level, pos, state.getValue(NoteBlock.INSTRUMENT), state.getValue(NoteBlock.NOTE));
++ if (event.isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
++ level.blockEvent(pos, this, 0, 0);
++ level.gameEvent(entity, GameEvent.NOTE_BLOCK_PLAY, pos);
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/NyliumBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/NyliumBlock.java.patch
new file mode 100644
index 0000000000..8d555c9360
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/NyliumBlock.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/level/block/NyliumBlock.java
++++ b/net/minecraft/world/level/block/NyliumBlock.java
+@@ -40,10 +39,14 @@
+ }
+
+ @Override
+- @Override
+- public void randomTick(BlockState blockstate, ServerLevel serverlevel, BlockPos blockpos, RandomSource randomsource) {
+- if (!canBeNylium(blockstate, serverlevel, blockpos)) {
+- serverlevel.setBlockAndUpdate(blockpos, Blocks.NETHERRACK.defaultBlockState());
++ public void randomTick(IBlockData state, ServerLevel level, BlockPos pos, RandomSource random) {
++ if (!canBeNylium(state, level, pos)) {
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(level, pos, Blocks.NETHERRACK.defaultBlockState()).isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
++ level.setBlockAndUpdate(pos, Blocks.NETHERRACK.defaultBlockState());
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ObserverBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ObserverBlock.java.patch
new file mode 100644
index 0000000000..6d4737b916
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ObserverBlock.java.patch
@@ -0,0 +1,39 @@
+--- a/net/minecraft/world/level/block/ObserverBlock.java
++++ b/net/minecraft/world/level/block/ObserverBlock.java
+@@ -14,6 +14,7 @@
+ import net.minecraft.world.level.block.state.StateDefinition;
+ import net.minecraft.world.level.block.state.properties.BlockStateProperties;
+ import net.minecraft.world.level.block.state.properties.BooleanProperty;
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
+
+ public class ObserverBlock extends DirectionalBlock {
+
+@@ -50,13 +47,22 @@
+ }
+
+ @Override
+- @Override
+- public void tick(BlockState blockstate, ServerLevel serverlevel, BlockPos blockpos, RandomSource randomsource) {
+- if ((Boolean) blockstate.getValue(ObserverBlock.POWERED)) {
+- serverlevel.setBlock(blockpos, (BlockState) blockstate.setValue(ObserverBlock.POWERED, false), 2);
++ public void tick(IBlockData state, ServerLevel level, BlockPos pos, RandomSource random) {
++ if ((Boolean) state.getValue(ObserverBlock.POWERED)) {
++ // CraftBukkit start
++ if (CraftEventFactory.callRedstoneChange(level, pos, 15, 0).getNewCurrent() != 0) {
++ return;
++ }
++ // CraftBukkit end
++ level.setBlock(pos, (IBlockData) state.setValue(ObserverBlock.POWERED, false), 2);
+ } else {
+- serverlevel.setBlock(blockpos, (BlockState) blockstate.setValue(ObserverBlock.POWERED, true), 2);
+- serverlevel.scheduleTick(blockpos, (Block) this, 2);
++ // CraftBukkit start
++ if (CraftEventFactory.callRedstoneChange(level, pos, 0, 15).getNewCurrent() != 15) {
++ return;
++ }
++ // CraftBukkit end
++ level.setBlock(pos, (IBlockData) state.setValue(ObserverBlock.POWERED, true), 2);
++ level.scheduleTick(pos, (Block) this, 2);
+ }
+
+ this.updateNeighborsInFront(serverlevel, blockpos, blockstate);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/PointedDripstoneBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/PointedDripstoneBlock.java.patch
new file mode 100644
index 0000000000..5a04d1bb18
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/PointedDripstoneBlock.java.patch
@@ -0,0 +1,81 @@
+--- a/net/minecraft/world/level/block/PointedDripstoneBlock.java
++++ b/net/minecraft/world/level/block/PointedDripstoneBlock.java
+@@ -42,6 +42,10 @@
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.Shapes;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++// CraftBukkit start
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++// CraftBukkit end
+
+ public class PointedDripstoneBlock extends Block implements Fallable, SimpleWaterloggedBlock {
+
+@@ -136,18 +135,24 @@
+ if (!level.isClientSide) {
+ BlockPos blockpos = blockhitresult.getBlockPos();
+
+- if (projectile.mayInteract(level, blockpos) && projectile.mayBreak(level) && projectile instanceof ThrownTrident && projectile.getDeltaMovement().length() > 0.6D) {
+- level.destroyBlock(blockpos, true);
++ if (projectile.mayInteract(level, blockposition) && projectile.mayBreak(level) && projectile instanceof ThrownTrident && projectile.getDeltaMovement().length() > 0.6D) {
++ // CraftBukkit start
++ if (!CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, Blocks.AIR.defaultBlockState())) {
++ return;
++ }
++ // CraftBukkit end
++ level.destroyBlock(blockposition, true);
+ }
+
+ }
+ }
+
+ @Override
+- @Override
+- public void fallOn(Level level, BlockState blockstate, BlockPos blockpos, Entity entity, float f) {
+- if (blockstate.getValue(PointedDripstoneBlock.TIP_DIRECTION) == Direction.UP && blockstate.getValue(PointedDripstoneBlock.THICKNESS) == DripstoneThickness.TIP) {
+- entity.causeFallDamage(f + 2.0F, 2.0F, level.damageSources().stalagmite());
++ public void fallOn(Level level, IBlockData state, BlockPos pos, Entity entity, float fallDistance) {
++ if (state.getValue(PointedDripstoneBlock.TIP_DIRECTION) == Direction.UP && state.getValue(PointedDripstoneBlock.THICKNESS) == DripstoneThickness.TIP) {
++ CraftEventFactory.blockDamage = CraftBlock.at(level, pos); // CraftBukkit
++ entity.causeFallDamage(fallDistance + 2.0F, 2.0F, level.damageSources().stalagmite());
++ CraftEventFactory.blockDamage = null; // CraftBukkit
+ } else {
+ super.fallOn(level, blockstate, blockpos, entity, f);
+ }
+@@ -400,18 +394,18 @@
+ BlockPos blockpos1 = blockpos.relative(direction);
+ BlockState blockstate = serverlevel.getBlockState(blockpos1);
+
+- if (isUnmergedTipWithDirection(blockstate, direction.getOpposite())) {
+- createMergedTips(blockstate, serverlevel, blockpos1);
+- } else if (blockstate.isAir() || blockstate.is(Blocks.WATER)) {
+- createDripstone(serverlevel, blockpos1, direction, DripstoneThickness.TIP);
++ if (isUnmergedTipWithDirection(iblockdata, direction.getOpposite())) {
++ createMergedTips(iblockdata, server, blockposition1);
++ } else if (iblockdata.isAir() || iblockdata.is(Blocks.WATER)) {
++ createDripstone(server, blockposition1, direction, DripstoneThickness.TIP, pos); // CraftBukkit
+ }
+
+ }
+
+- private static void createDripstone(LevelAccessor levelaccessor, BlockPos blockpos, Direction direction, DripstoneThickness dripstonethickness) {
+- BlockState blockstate = (BlockState) ((BlockState) ((BlockState) Blocks.POINTED_DRIPSTONE.defaultBlockState().setValue(PointedDripstoneBlock.TIP_DIRECTION, direction)).setValue(PointedDripstoneBlock.THICKNESS, dripstonethickness)).setValue(PointedDripstoneBlock.WATERLOGGED, levelaccessor.getFluidState(blockpos).getType() == Fluids.WATER);
++ private static void createDripstone(LevelAccessor generatoraccess, BlockPos blockposition, Direction enumdirection, DripstoneThickness dripstonethickness, BlockPos source) { // CraftBukkit
++ IBlockData iblockdata = (IBlockData) ((IBlockData) ((IBlockData) Blocks.POINTED_DRIPSTONE.defaultBlockState().setValue(PointedDripstoneBlock.TIP_DIRECTION, enumdirection)).setValue(PointedDripstoneBlock.THICKNESS, dripstonethickness)).setValue(PointedDripstoneBlock.WATERLOGGED, generatoraccess.getFluidState(blockposition).getType() == Fluids.WATER);
+
+- levelaccessor.setBlock(blockpos, blockstate, 3);
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(generatoraccess, source, blockposition, iblockdata, 3); // CraftBukkit
+ }
+
+ private static void createMergedTips(BlockState blockstate, LevelAccessor levelaccessor, BlockPos blockpos) {
+@@ -426,8 +420,8 @@
+ blockpos1 = blockpos.below();
+ }
+
+- createDripstone(levelaccessor, blockpos2, Direction.DOWN, DripstoneThickness.TIP_MERGE);
+- createDripstone(levelaccessor, blockpos1, Direction.UP, DripstoneThickness.TIP_MERGE);
++ createDripstone(level, blockposition2, Direction.DOWN, DripstoneThickness.TIP_MERGE, pos); // CraftBukkit
++ createDripstone(level, blockposition1, Direction.UP, DripstoneThickness.TIP_MERGE, pos); // CraftBukkit
+ }
+
+ public static void spawnDripParticle(Level level, BlockPos blockpos, BlockState blockstate) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/PowderSnowBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/PowderSnowBlock.java.patch
new file mode 100644
index 0000000000..25407902e0
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/PowderSnowBlock.java.patch
@@ -0,0 +1,18 @@
+--- a/net/minecraft/world/level/block/PowderSnowBlock.java
++++ b/net/minecraft/world/level/block/PowderSnowBlock.java
+@@ -81,8 +77,13 @@
+
+ entity.setIsInPowderSnow(true);
+ if (!level.isClientSide) {
+- if (entity.isOnFire() && (level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) || entity instanceof Player) && entity.mayInteract(level, blockpos)) {
+- level.destroyBlock(blockpos, false);
++ // CraftBukkit start
++ if (entity.isOnFire() && entity.mayInteract(level, pos)) {
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !(level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) || entity instanceof Player))) {
++ return;
++ }
++ // CraftBukkit end
++ level.destroyBlock(pos, false);
+ }
+
+ entity.setSharedFlagOnFire(false);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/PoweredRailBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/PoweredRailBlock.java.patch
new file mode 100644
index 0000000000..3649b9bccc
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/PoweredRailBlock.java.patch
@@ -0,0 +1,33 @@
+--- a/net/minecraft/world/level/block/PoweredRailBlock.java
++++ b/net/minecraft/world/level/block/PoweredRailBlock.java
+@@ -11,6 +11,7 @@
+ import net.minecraft.world.level.block.state.properties.EnumProperty;
+ import net.minecraft.world.level.block.state.properties.Property;
+ import net.minecraft.world.level.block.state.properties.RailShape;
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
+
+ public class PoweredRailBlock extends BaseRailBlock {
+
+@@ -122,11 +121,18 @@
+ boolean flag1 = level.hasNeighborSignal(blockpos) || this.findPoweredRailSignal(level, blockpos, blockstate, true, 0) || this.findPoweredRailSignal(level, blockpos, blockstate, false, 0);
+
+ if (flag1 != flag) {
+- level.setBlock(blockpos, (BlockState) blockstate.setValue(PoweredRailBlock.POWERED, flag1), 3);
+- level.updateNeighborsAt(blockpos.below(), this);
+- if (((RailShape) blockstate.getValue(PoweredRailBlock.SHAPE)).isAscending()) {
+- level.updateNeighborsAt(blockpos.above(), this);
++ // CraftBukkit start
++ int power = flag ? 15 : 0;
++ int newPower = CraftEventFactory.callRedstoneChange(level, pos, power, 15 - power).getNewCurrent();
++ if (newPower == power) {
++ return;
+ }
++ // CraftBukkit end
++ level.setBlock(pos, (IBlockData) state.setValue(PoweredRailBlock.POWERED, flag1), 3);
++ level.updateNeighborsAt(pos.below(), this);
++ if (((RailShape) state.getValue(PoweredRailBlock.SHAPE)).isAscending()) {
++ level.updateNeighborsAt(pos.above(), this);
++ }
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/PressurePlateBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/PressurePlateBlock.java.patch
new file mode 100644
index 0000000000..6af2035dc2
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/PressurePlateBlock.java.patch
@@ -0,0 +1,56 @@
+--- a/net/minecraft/world/level/block/PressurePlateBlock.java
++++ b/net/minecraft/world/level/block/PressurePlateBlock.java
+@@ -12,6 +13,8 @@
+ import net.minecraft.world.level.block.state.properties.BlockSetType;
+ import net.minecraft.world.level.block.state.properties.BlockStateProperties;
+ import net.minecraft.world.level.block.state.properties.BooleanProperty;
++import org.bukkit.event.entity.EntityInteractEvent;
++// CraftBukkit end
+
+ public class PressurePlateBlock extends BasePressurePlateBlock {
+
+@@ -46,9 +46,8 @@
+ }
+
+ @Override
+- @Override
+- protected int getSignalStrength(Level level, BlockPos blockpos) {
+- Class oclass;
++ protected int getSignalStrength(Level level, BlockPos pos) {
++ Class<? extends Entity> oclass; // CraftBukkit
+
+ switch (this.type.pressurePlateSensitivity()) {
+ case EVERYTHING:
+@@ -63,7 +62,31 @@
+
+ Class<? extends Entity> oclass1 = oclass;
+
+- return getEntityCount(level, PressurePlateBlock.TOUCH_AABB.move(blockpos), oclass1) > 0 ? 15 : 0;
++ // CraftBukkit start - Call interact event when turning on a pressure plate
++ for (Entity entity : getEntities(level, PressurePlateBlock.TOUCH_AABB.move(pos), oclass)) {
++ if (this.getSignalForState(level.getBlockState(pos)) == 0) {
++ org.bukkit.World bworld = level.getWorld();
++ org.bukkit.plugin.PluginManager manager = level.getCraftServer().getPluginManager();
++ org.bukkit.event.Cancellable cancellable;
++
++ if (entity instanceof Player) {
++ cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((Player) entity, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null);
++ } else {
++ cancellable = new EntityInteractEvent(entity.getBukkitEntity(), bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()));
++ manager.callEvent((EntityInteractEvent) cancellable);
++ }
++
++ // We only want to block turning the plate on if all events are cancelled
++ if (cancellable.isCancelled()) {
++ continue;
++ }
++ }
++
++ return 15;
++ }
++
++ return 0;
++ // CraftBukkit end
+ }
+
+ @Override
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/RedStoneOreBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/RedStoneOreBlock.java.patch
new file mode 100644
index 0000000000..c648573fe2
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/RedStoneOreBlock.java.patch
@@ -0,0 +1,115 @@
+--- a/net/minecraft/world/level/block/RedStoneOreBlock.java
++++ b/net/minecraft/world/level/block/RedStoneOreBlock.java
+@@ -21,6 +21,10 @@
+ import net.minecraft.world.level.block.state.StateDefinition;
+ import net.minecraft.world.level.block.state.properties.BooleanProperty;
+ import net.minecraft.world.phys.BlockHitResult;
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.entity.EntityInteractEvent;
++// CraftBukkit end
+
+ public class RedStoneOreBlock extends Block {
+
+@@ -39,17 +42,29 @@
+ }
+
+ @Override
+- @Override
+- public void attack(BlockState blockstate, Level level, BlockPos blockpos, Player player) {
+- interact(blockstate, level, blockpos);
+- super.attack(blockstate, level, blockpos, player);
++ public void attack(IBlockData state, Level level, BlockPos pos, Player player) {
++ interact(state, level, pos, player); // CraftBukkit - add entityhuman
++ super.attack(state, level, pos, player);
+ }
+
+ @Override
+ @Override
+ public void stepOn(Level level, BlockPos blockpos, BlockState blockstate, Entity entity) {
+ if (!entity.isSteppingCarefully()) {
+- interact(blockstate, level, blockpos);
++ // CraftBukkit start
++ if (entity instanceof Player) {
++ org.bukkit.event.player.PlayerInteractEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((Player) entity, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null);
++ if (!event.isCancelled()) {
++ interact(level.getBlockState(pos), level, pos, entity); // add entity
++ }
++ } else {
++ EntityInteractEvent event = new EntityInteractEvent(entity.getBukkitEntity(), level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()));
++ level.getCraftServer().getPluginManager().callEvent(event);
++ if (!event.isCancelled()) {
++ interact(level.getBlockState(pos), level, pos, entity); // add entity
++ }
++ }
++ // CraftBukkit end
+ }
+
+ super.stepOn(level, blockpos, blockstate, entity);
+@@ -61,7 +74,7 @@
+ if (level.isClientSide) {
+ spawnParticles(level, blockpos);
+ } else {
+- interact(blockstate, level, blockpos);
++ interact(state, level, pos, player); // CraftBukkit - add entityhuman
+ }
+
+ ItemStack itemstack = player.getItemInHand(interactionhand);
+@@ -69,10 +82,15 @@
+ return itemstack.getItem() instanceof BlockItem && (new BlockPlaceContext(player, interactionhand, itemstack, blockhitresult)).canPlace() ? InteractionResult.PASS : InteractionResult.SUCCESS;
+ }
+
+- private static void interact(BlockState blockstate, Level level, BlockPos blockpos) {
+- spawnParticles(level, blockpos);
+- if (!(Boolean) blockstate.getValue(RedStoneOreBlock.LIT)) {
+- level.setBlock(blockpos, (BlockState) blockstate.setValue(RedStoneOreBlock.LIT, true), 3);
++ private static void interact(IBlockData iblockdata, Level world, BlockPos blockposition, Entity entity) { // CraftBukkit - add Entity
++ spawnParticles(world, blockposition);
++ if (!(Boolean) iblockdata.getValue(RedStoneOreBlock.LIT)) {
++ // CraftBukkit start
++ if (!CraftEventFactory.callEntityChangeBlockEvent(entity, blockposition, iblockdata.setValue(RedStoneOreBlock.LIT, true))) {
++ return;
++ }
++ // CraftBukkit end
++ world.setBlock(blockposition, (IBlockData) iblockdata.setValue(RedStoneOreBlock.LIT, true), 3);
+ }
+
+ }
+@@ -84,15 +101,24 @@
+ }
+
+ @Override
+- @Override
+- public void randomTick(BlockState blockstate, ServerLevel serverlevel, BlockPos blockpos, RandomSource randomsource) {
+- if ((Boolean) blockstate.getValue(RedStoneOreBlock.LIT)) {
+- serverlevel.setBlock(blockpos, (BlockState) blockstate.setValue(RedStoneOreBlock.LIT, false), 3);
++ public void randomTick(IBlockData state, ServerLevel level, BlockPos pos, RandomSource random) {
++ if ((Boolean) state.getValue(RedStoneOreBlock.LIT)) {
++ // CraftBukkit start
++ if (CraftEventFactory.callBlockFadeEvent(level, pos, state.setValue(RedStoneOreBlock.LIT, false)).isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
++ level.setBlock(pos, (IBlockData) state.setValue(RedStoneOreBlock.LIT, false), 3);
+ }
+
+ }
+
+ @Override
++ public void spawnAfterBreak(IBlockData state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) {
++ super.spawnAfterBreak(state, level, pos, stack, dropExperience);
++ // CraftBukkit start - Delegated to getExpDrop
++ }
++
+ @Override
+ public void spawnAfterBreak(BlockState blockstate, ServerLevel serverlevel, BlockPos blockpos, ItemStack itemstack, boolean flag) {
+ super.spawnAfterBreak(blockstate, serverlevel, blockpos, itemstack, flag);
+@@ -102,6 +128,8 @@
+ this.popExperience(serverlevel, blockpos, i);
+ }
+
++ return 0;
++ // CraftBukkit end
+ }
+
+ @Override
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/RedStoneWireBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/RedStoneWireBlock.java.patch
new file mode 100644
index 0000000000..19688d2738
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/RedStoneWireBlock.java.patch
@@ -0,0 +1,32 @@
+--- a/net/minecraft/world/level/block/RedStoneWireBlock.java
++++ b/net/minecraft/world/level/block/RedStoneWireBlock.java
+@@ -36,6 +36,7 @@
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.Shapes;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit
+
+ public class RedStoneWireBlock extends Block {
+
+@@ -267,9 +262,18 @@
+ private void updatePowerStrength(Level level, BlockPos blockpos, BlockState blockstate) {
+ int i = this.calculateTargetStrength(level, blockpos);
+
+- if ((Integer) blockstate.getValue(RedStoneWireBlock.POWER) != i) {
+- if (level.getBlockState(blockpos) == blockstate) {
+- level.setBlock(blockpos, (BlockState) blockstate.setValue(RedStoneWireBlock.POWER, i), 2);
++ // CraftBukkit start
++ int oldPower = (Integer) state.getValue(RedStoneWireBlock.POWER);
++ if (oldPower != i) {
++ BlockRedstoneEvent event = new BlockRedstoneEvent(level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()), oldPower, i);
++ level.getCraftServer().getPluginManager().callEvent(event);
++
++ i = event.getNewCurrent();
++ }
++ if (oldPower != i) {
++ // CraftBukkit end
++ if (level.getBlockState(pos) == state) {
++ level.setBlock(pos, (IBlockData) state.setValue(RedStoneWireBlock.POWER, i), 2);
+ }
+
+ Set<BlockPos> set = Sets.newHashSet();
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/RedstoneLampBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/RedstoneLampBlock.java.patch
new file mode 100644
index 0000000000..0e78eed83b
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/RedstoneLampBlock.java.patch
@@ -0,0 +1,43 @@
+--- a/net/minecraft/world/level/block/RedstoneLampBlock.java
++++ b/net/minecraft/world/level/block/RedstoneLampBlock.java
+@@ -11,6 +11,7 @@
+ import net.minecraft.world.level.block.state.BlockState;
+ import net.minecraft.world.level.block.state.StateDefinition;
+ import net.minecraft.world.level.block.state.properties.BooleanProperty;
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
+
+ public class RedstoneLampBlock extends Block {
+
+@@ -45,7 +43,12 @@
+ if (flag1) {
+ level.scheduleTick(blockpos, (Block) this, 4);
+ } else {
+- level.setBlock(blockpos, (BlockState) blockstate.cycle(RedstoneLampBlock.LIT), 2);
++ // CraftBukkit start
++ if (CraftEventFactory.callRedstoneChange(level, pos, 0, 15).getNewCurrent() != 15) {
++ return;
++ }
++ // CraftBukkit end
++ level.setBlock(pos, (IBlockData) state.cycle(RedstoneLampBlock.LIT), 2);
+ }
+ }
+
+@@ -53,10 +56,14 @@
+ }
+
+ @Override
+- @Override
+- public void tick(BlockState blockstate, ServerLevel serverlevel, BlockPos blockpos, RandomSource randomsource) {
+- if ((Boolean) blockstate.getValue(RedstoneLampBlock.LIT) && !serverlevel.hasNeighborSignal(blockpos)) {
+- serverlevel.setBlock(blockpos, (BlockState) blockstate.cycle(RedstoneLampBlock.LIT), 2);
++ public void tick(IBlockData state, ServerLevel level, BlockPos pos, RandomSource random) {
++ if ((Boolean) state.getValue(RedstoneLampBlock.LIT) && !level.hasNeighborSignal(pos)) {
++ // CraftBukkit start
++ if (CraftEventFactory.callRedstoneChange(level, pos, 15, 0).getNewCurrent() != 0) {
++ return;
++ }
++ // CraftBukkit end
++ level.setBlock(pos, (IBlockData) state.cycle(RedstoneLampBlock.LIT), 2);
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/RedstoneTorchBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/RedstoneTorchBlock.java.patch
new file mode 100644
index 0000000000..4aac8d3e1b
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/RedstoneTorchBlock.java.patch
@@ -0,0 +1,59 @@
+--- a/net/minecraft/world/level/block/RedstoneTorchBlock.java
++++ b/net/minecraft/world/level/block/RedstoneTorchBlock.java
+@@ -18,6 +18,7 @@
+ import net.minecraft.world.level.block.state.StateDefinition;
+ import net.minecraft.world.level.block.state.properties.BlockStateProperties;
+ import net.minecraft.world.level.block.state.properties.BooleanProperty;
++import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit
+
+ public class RedstoneTorchBlock extends BaseTorchBlock {
+
+@@ -90,16 +86,41 @@
+ list.remove(0);
+ }
+
+- if ((Boolean) blockstate.getValue(RedstoneTorchBlock.LIT)) {
++ // CraftBukkit start
++ org.bukkit.plugin.PluginManager manager = level.getCraftServer().getPluginManager();
++ org.bukkit.block.Block block = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++ int oldCurrent = ((Boolean) state.getValue(RedstoneTorchBlock.LIT)).booleanValue() ? 15 : 0;
++
++ BlockRedstoneEvent event = new BlockRedstoneEvent(block, oldCurrent, oldCurrent);
++ // CraftBukkit end
++ if ((Boolean) state.getValue(RedstoneTorchBlock.LIT)) {
+ if (flag) {
+- serverlevel.setBlock(blockpos, (BlockState) blockstate.setValue(RedstoneTorchBlock.LIT, false), 3);
+- if (isToggledTooFrequently(serverlevel, blockpos, true)) {
+- serverlevel.levelEvent(1502, blockpos, 0);
+- serverlevel.scheduleTick(blockpos, serverlevel.getBlockState(blockpos).getBlock(), 160);
++ // CraftBukkit start
++ if (oldCurrent != 0) {
++ event.setNewCurrent(0);
++ manager.callEvent(event);
++ if (event.getNewCurrent() != 0) {
++ return;
++ }
+ }
++ // CraftBukkit end
++ level.setBlock(pos, (IBlockData) state.setValue(RedstoneTorchBlock.LIT, false), 3);
++ if (isToggledTooFrequently(level, pos, true)) {
++ level.levelEvent(1502, pos, 0);
++ level.scheduleTick(pos, level.getBlockState(pos).getBlock(), 160);
++ }
+ }
+- } else if (!flag && !isToggledTooFrequently(serverlevel, blockpos, false)) {
+- serverlevel.setBlock(blockpos, (BlockState) blockstate.setValue(RedstoneTorchBlock.LIT, true), 3);
++ } else if (!flag && !isToggledTooFrequently(level, pos, false)) {
++ // CraftBukkit start
++ if (oldCurrent != 15) {
++ event.setNewCurrent(15);
++ manager.callEvent(event);
++ if (event.getNewCurrent() != 15) {
++ return;
++ }
++ }
++ // CraftBukkit end
++ level.setBlock(pos, (IBlockData) state.setValue(RedstoneTorchBlock.LIT, true), 3);
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/RespawnAnchorBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/RespawnAnchorBlock.java.patch
new file mode 100644
index 0000000000..8f81326b5e
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/RespawnAnchorBlock.java.patch
@@ -0,0 +1,15 @@
+--- a/net/minecraft/world/level/block/RespawnAnchorBlock.java
++++ b/net/minecraft/world/level/block/RespawnAnchorBlock.java
+@@ -88,9 +86,9 @@
+ if (!level.isClientSide) {
+ ServerPlayer serverplayer = (ServerPlayer) player;
+
+- if (serverplayer.getRespawnDimension() != level.dimension() || !blockpos.equals(serverplayer.getRespawnPosition())) {
+- serverplayer.setRespawnPosition(level.dimension(), blockpos, 0.0F, false, true);
+- level.playSound((Player) null, (double) blockpos.getX() + 0.5D, (double) blockpos.getY() + 0.5D, (double) blockpos.getZ() + 0.5D, SoundEvents.RESPAWN_ANCHOR_SET_SPAWN, SoundSource.BLOCKS, 1.0F, 1.0F);
++ if (entityplayer.getRespawnDimension() != level.dimension() || !pos.equals(entityplayer.getRespawnPosition())) {
++ entityplayer.setRespawnPosition(level.dimension(), pos, 0.0F, false, true, org.bukkit.event.player.PlayerSpawnChangeEvent.Cause.RESPAWN_ANCHOR); // CraftBukkit
++ level.playSound((Player) null, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, SoundEvents.RESPAWN_ANCHOR_SET_SPAWN, SoundSource.BLOCKS, 1.0F, 1.0F);
+ return InteractionResult.SUCCESS;
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/RootedDirtBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/RootedDirtBlock.java.patch
new file mode 100644
index 0000000000..e573a620c7
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/RootedDirtBlock.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/world/level/block/RootedDirtBlock.java
++++ b/net/minecraft/world/level/block/RootedDirtBlock.java
+@@ -36,8 +33,7 @@
+ }
+
+ @Override
+- @Override
+- public void performBonemeal(ServerLevel serverlevel, RandomSource randomsource, BlockPos blockpos, BlockState blockstate) {
+- serverlevel.setBlockAndUpdate(blockpos.below(), Blocks.HANGING_ROOTS.defaultBlockState());
++ public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, IBlockData state) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, pos.below(), Blocks.HANGING_ROOTS.defaultBlockState()); // CraftBukkit
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SaplingBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SaplingBlock.java.patch
new file mode 100644
index 0000000000..d472f07842
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SaplingBlock.java.patch
@@ -0,0 +1,60 @@
+--- a/net/minecraft/world/level/block/SaplingBlock.java
++++ b/net/minecraft/world/level/block/SaplingBlock.java
+@@ -16,6 +16,13 @@
+ import net.minecraft.world.level.block.state.properties.IntegerProperty;
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++// CraftBukkit start
++import org.bukkit.Location;
++import org.bukkit.TreeType;
++import org.bukkit.block.BlockState;
++import org.bukkit.craftbukkit.util.CraftLocation;
++import org.bukkit.event.world.StructureGrowEvent;
++// CraftBukkit end
+
+ public class SaplingBlock extends BushBlock implements BonemealableBlock {
+
+@@ -27,7 +34,8 @@
+ public static final IntegerProperty STAGE = BlockStateProperties.STAGE;
+ protected static final float AABB_OFFSET = 6.0F;
+ protected static final VoxelShape SHAPE = Block.box(2.0D, 0.0D, 2.0D, 14.0D, 12.0D, 14.0D);
+- protected final TreeGrower treeGrower;
++ protected final WorldGenTreeProvider treeGrower;
++ public static TreeType treeType; // CraftBukkit
+
+ @Override
+ @Override
+@@ -60,7 +65,32 @@
+ if ((Integer) blockstate.getValue(SaplingBlock.STAGE) == 0) {
+ serverlevel.setBlock(blockpos, (BlockState) blockstate.cycle(SaplingBlock.STAGE), 4);
+ } else {
+- this.treeGrower.growTree(serverlevel, serverlevel.getChunkSource().getGenerator(), blockpos, blockstate, randomsource);
++ // CraftBukkit start
++ if (level.captureTreeGeneration) {
++ this.treeGrower.growTree(level, level.getChunkSource().getGenerator(), pos, state, random);
++ } else {
++ level.captureTreeGeneration = true;
++ this.treeGrower.growTree(level, level.getChunkSource().getGenerator(), pos, state, random);
++ level.captureTreeGeneration = false;
++ if (level.capturedBlockStates.size() > 0) {
++ TreeType treeType = SaplingBlock.treeType;
++ SaplingBlock.treeType = null;
++ Location location = CraftLocation.toBukkit(pos, level.getWorld());
++ java.util.List<BlockState> blocks = new java.util.ArrayList<>(level.capturedBlockStates.values());
++ level.capturedBlockStates.clear();
++ StructureGrowEvent event = null;
++ if (treeType != null) {
++ event = new StructureGrowEvent(location, treeType, false, null, blocks);
++ org.bukkit.Bukkit.getPluginManager().callEvent(event);
++ }
++ if (event == null || !event.isCancelled()) {
++ for (BlockState blockstate : blocks) {
++ blockstate.update(true);
++ }
++ }
++ }
++ }
++ // CraftBukkit end
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ScaffoldingBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ScaffoldingBlock.java.patch
new file mode 100644
index 0000000000..ce8ef9088c
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/ScaffoldingBlock.java.patch
@@ -0,0 +1,15 @@
+--- a/net/minecraft/world/level/block/ScaffoldingBlock.java
++++ b/net/minecraft/world/level/block/ScaffoldingBlock.java
+@@ -112,9 +103,9 @@
+ int i = getDistance(serverlevel, blockpos);
+ BlockState blockstate1 = (BlockState) ((BlockState) blockstate.setValue(ScaffoldingBlock.DISTANCE, i)).setValue(ScaffoldingBlock.BOTTOM, this.isBottom(serverlevel, blockpos, i));
+
+- if ((Integer) blockstate1.getValue(ScaffoldingBlock.DISTANCE) == 7) {
+- if ((Integer) blockstate.getValue(ScaffoldingBlock.DISTANCE) == 7) {
+- FallingBlockEntity.fall(serverlevel, blockpos, blockstate1);
++ if ((Integer) iblockdata1.getValue(ScaffoldingBlock.DISTANCE) == 7 && !org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(level, pos, Blocks.AIR.defaultBlockState()).isCancelled()) { // CraftBukkit - BlockFadeEvent
++ if ((Integer) state.getValue(ScaffoldingBlock.DISTANCE) == 7) {
++ FallingBlockEntity.fall(level, pos, iblockdata1);
+ } else {
+ serverlevel.destroyBlock(blockpos, true);
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SculkBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SculkBlock.java.patch
new file mode 100644
index 0000000000..28e71ab626
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SculkBlock.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/world/level/block/SculkBlock.java
++++ b/net/minecraft/world/level/block/SculkBlock.java
+@@ -45,8 +43,11 @@
+ BlockPos blockpos2 = blockpos1.above();
+ BlockState blockstate = this.getRandomGrowthState(levelaccessor, blockpos2, randomsource, sculkspreader.isWorldGeneration());
+
+- levelaccessor.setBlock(blockpos2, blockstate, 3);
+- levelaccessor.playSound((Player) null, blockpos1, blockstate.getSoundType().getPlaceSound(), SoundSource.BLOCKS, 1.0F, 1.0F);
++ // CraftBukkit start - Call BlockSpreadEvent
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, blockposition2, iblockdata, 3)) {
++ level.playSound((Player) null, blockposition1, iblockdata.getSoundType().getPlaceSound(), SoundSource.BLOCKS, 1.0F, 1.0F);
++ }
++ // CraftBukkit end
+ }
+
+ return Math.max(0, i - j);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SculkCatalystBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SculkCatalystBlock.java.patch
new file mode 100644
index 0000000000..3726fe60b2
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SculkCatalystBlock.java.patch
@@ -0,0 +1,23 @@
+--- a/net/minecraft/world/level/block/SculkCatalystBlock.java
++++ b/net/minecraft/world/level/block/SculkCatalystBlock.java
+@@ -66,9 +66,9 @@
+ }
+
+ @Override
+- @Override
+- public RenderShape getRenderShape(BlockState blockstate) {
+- return RenderShape.MODEL;
++ public void spawnAfterBreak(IBlockData state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) {
++ super.spawnAfterBreak(state, level, pos, stack, dropExperience);
++ // CraftBukkit start - Delegate to getExpDrop
+ }
+
+ @Override
+@@ -79,5 +77,7 @@
+ this.tryDropExperience(serverlevel, blockpos, itemstack, this.xpRange);
+ }
+
++ return 0;
++ // CraftBukkit end
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SculkSensorBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SculkSensorBlock.java.patch
new file mode 100644
index 0000000000..11dbdd5930
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SculkSensorBlock.java.patch
@@ -0,0 +1,116 @@
+--- a/net/minecraft/world/level/block/SculkSensorBlock.java
++++ b/net/minecraft/world/level/block/SculkSensorBlock.java
+@@ -41,6 +41,10 @@
+ import net.minecraft.world.level.pathfinder.PathComputationType;
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++// CraftBukkit start
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.event.block.BlockRedstoneEvent;
++// CraftBukkit end
+
+ public class SculkSensorBlock extends BaseEntityBlock implements SimpleWaterloggedBlock {
+
+@@ -104,10 +104,21 @@
+ }
+
+ @Override
+- @Override
+- public void stepOn(Level level, BlockPos blockpos, BlockState blockstate, Entity entity) {
+- if (!level.isClientSide() && canActivate(blockstate) && entity.getType() != EntityType.WARDEN) {
+- BlockEntity blockentity = level.getBlockEntity(blockpos);
++ public void stepOn(Level level, BlockPos pos, IBlockData state, Entity entity) {
++ if (!level.isClientSide() && canActivate(state) && entity.getType() != EntityType.WARDEN) {
++ // CraftBukkit start
++ org.bukkit.event.Cancellable cancellable;
++ if (entity instanceof Player) {
++ cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((Player) entity, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null);
++ } else {
++ cancellable = new org.bukkit.event.entity.EntityInteractEvent(entity.getBukkitEntity(), level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()));
++ level.getCraftServer().getPluginManager().callEvent((org.bukkit.event.entity.EntityInteractEvent) cancellable);
++ }
++ if (cancellable.isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
++ BlockEntity tileentity = level.getBlockEntity(pos);
+
+ if (blockentity instanceof SculkSensorBlockEntity) {
+ SculkSensorBlockEntity sculksensorblockentity = (SculkSensorBlockEntity) blockentity;
+@@ -219,10 +220,19 @@
+ return getPhase(blockstate) == SculkSensorPhase.INACTIVE;
+ }
+
+- public static void deactivate(Level level, BlockPos blockpos, BlockState blockstate) {
+- level.setBlock(blockpos, (BlockState) ((BlockState) blockstate.setValue(SculkSensorBlock.PHASE, SculkSensorPhase.COOLDOWN)).setValue(SculkSensorBlock.POWER, 0), 3);
+- level.scheduleTick(blockpos, blockstate.getBlock(), 10);
+- updateNeighbours(level, blockpos, blockstate);
++ public static void deactivate(Level level, BlockPos pos, IBlockData state) {
++ // CraftBukkit start
++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(CraftBlock.at(level, pos), state.getValue(SculkSensorBlock.POWER), 0);
++ level.getCraftServer().getPluginManager().callEvent(eventRedstone);
++
++ if (eventRedstone.getNewCurrent() > 0) {
++ level.setBlock(pos, state.setValue(SculkSensorBlock.POWER, eventRedstone.getNewCurrent()), 3);
++ return;
++ }
++ // CraftBukkit end
++ level.setBlock(pos, (IBlockData) ((IBlockData) state.setValue(SculkSensorBlock.PHASE, SculkSensorPhase.COOLDOWN)).setValue(SculkSensorBlock.POWER, 0), 3);
++ level.scheduleTick(pos, state.getBlock(), 10);
++ updateNeighbours(level, pos, state);
+ }
+
+ @VisibleForTesting
+@@ -230,15 +240,24 @@
+ return 30;
+ }
+
+- public void activate(@Nullable Entity entity, Level level, BlockPos blockpos, BlockState blockstate, int i, int j) {
+- level.setBlock(blockpos, (BlockState) ((BlockState) blockstate.setValue(SculkSensorBlock.PHASE, SculkSensorPhase.ACTIVE)).setValue(SculkSensorBlock.POWER, i), 3);
+- level.scheduleTick(blockpos, blockstate.getBlock(), this.getActiveTicks());
+- updateNeighbours(level, blockpos, blockstate);
+- tryResonateVibration(entity, level, blockpos, j);
+- level.gameEvent(entity, GameEvent.SCULK_SENSOR_TENDRILS_CLICKING, blockpos);
+- if (!(Boolean) blockstate.getValue(SculkSensorBlock.WATERLOGGED)) {
+- level.playSound((Player) null, (double) blockpos.getX() + 0.5D, (double) blockpos.getY() + 0.5D, (double) blockpos.getZ() + 0.5D, SoundEvents.SCULK_CLICKING, SoundSource.BLOCKS, 1.0F, level.random.nextFloat() * 0.2F + 0.8F);
++ public void activate(@Nullable Entity entity, Level level, BlockPos pos, IBlockData state, int power, int frequency) {
++ // CraftBukkit start
++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(CraftBlock.at(level, pos), state.getValue(SculkSensorBlock.POWER), power);
++ level.getCraftServer().getPluginManager().callEvent(eventRedstone);
++
++ if (eventRedstone.getNewCurrent() <= 0) {
++ return;
+ }
++ power = eventRedstone.getNewCurrent();
++ // CraftBukkit end
++ level.setBlock(pos, (IBlockData) ((IBlockData) state.setValue(SculkSensorBlock.PHASE, SculkSensorPhase.ACTIVE)).setValue(SculkSensorBlock.POWER, power), 3);
++ level.scheduleTick(pos, state.getBlock(), this.getActiveTicks());
++ updateNeighbours(level, pos, state);
++ tryResonateVibration(entity, level, pos, frequency);
++ level.gameEvent(entity, GameEvent.SCULK_SENSOR_TENDRILS_CLICKING, pos);
++ if (!(Boolean) state.getValue(SculkSensorBlock.WATERLOGGED)) {
++ level.playSound((Player) null, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, SoundEvents.SCULK_CLICKING, SoundSource.BLOCKS, 1.0F, level.random.nextFloat() * 0.2F + 0.8F);
++ }
+
+ }
+
+@@ -317,6 +330,11 @@
+ }
+
+ @Override
++ public void spawnAfterBreak(IBlockData state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) {
++ super.spawnAfterBreak(state, level, pos, stack, dropExperience);
++ // CraftBukkit start - Delegate to getExpDrop
++ }
++
+ @Override
+ public void spawnAfterBreak(BlockState blockstate, ServerLevel serverlevel, BlockPos blockpos, ItemStack itemstack, boolean flag) {
+ super.spawnAfterBreak(blockstate, serverlevel, blockpos, itemstack, flag);
+@@ -324,5 +341,7 @@
+ this.tryDropExperience(serverlevel, blockpos, itemstack, ConstantInt.of(5));
+ }
+
++ return 0;
++ // CraftBukkit end
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SculkShriekerBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SculkShriekerBlock.java.patch
new file mode 100644
index 0000000000..d73f7a4bfa
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SculkShriekerBlock.java.patch
@@ -0,0 +1,38 @@
+--- a/net/minecraft/world/level/block/SculkShriekerBlock.java
++++ b/net/minecraft/world/level/block/SculkShriekerBlock.java
+@@ -65,9 +62,10 @@
+ ServerLevel serverlevel = (ServerLevel) level;
+ ServerPlayer serverplayer = SculkShriekerBlockEntity.tryGetPlayer(entity);
+
+- if (serverplayer != null) {
+- serverlevel.getBlockEntity(blockpos, BlockEntityType.SCULK_SHRIEKER).ifPresent((sculkshriekerblockentity) -> {
+- sculkshriekerblockentity.tryShriek(serverlevel, serverplayer);
++ if (entityplayer != null) {
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent(entityplayer, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null).isCancelled()) return; // CraftBukkit
++ worldserver.getBlockEntity(pos, BlockEntityType.SCULK_SHRIEKER).ifPresent((sculkshriekerblockentity) -> {
++ sculkshriekerblockentity.tryShriek(worldserver, entityplayer);
+ });
+ }
+ }
+@@ -152,9 +146,9 @@
+ }
+
+ @Override
+- @Override
+- public FluidState getFluidState(BlockState blockstate) {
+- return (Boolean) blockstate.getValue(SculkShriekerBlock.WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(blockstate);
++ public void spawnAfterBreak(IBlockData state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) {
++ super.spawnAfterBreak(state, level, pos, stack, dropExperience);
++ // CraftBukkit start - Delegate to getExpDrop
+ }
+
+ @Override
+@@ -165,6 +157,8 @@
+ this.tryDropExperience(serverlevel, blockpos, itemstack, ConstantInt.of(5));
+ }
+
++ return 0;
++ // CraftBukkit end
+ }
+
+ @Nullable
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SculkSpreader.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SculkSpreader.java.patch
new file mode 100644
index 0000000000..422c2e4ea0
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SculkSpreader.java.patch
@@ -0,0 +1,43 @@
+--- a/net/minecraft/world/level/block/SculkSpreader.java
++++ b/net/minecraft/world/level/block/SculkSpreader.java
+@@ -40,6 +42,10 @@
+ import net.minecraft.world.level.LevelAccessor;
+ import net.minecraft.world.level.block.state.BlockState;
+ import org.slf4j.Logger;
++import org.bukkit.Bukkit;
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.event.block.SculkBloomEvent;
++// CraftBukkit end
+
+ public class SculkSpreader {
+
+@@ -56,6 +62,7 @@
+ private final int additionalDecayRate;
+ private List<SculkSpreader.ChargeCursor> cursors = new ArrayList();
+ private static final Logger LOGGER = LogUtils.getLogger();
++ public Level level; // CraftBukkit
+
+ public SculkSpreader(boolean flag, TagKey<Block> tagkey, int i, int j, int k, int l) {
+ this.isWorldGeneration = flag;
+@@ -146,7 +153,20 @@
+
+ private void addCursor(SculkSpreader.ChargeCursor sculkspreader_chargecursor) {
+ if (this.cursors.size() < 32) {
+- this.cursors.add(sculkspreader_chargecursor);
++ // CraftBukkit start
++ if (!isWorldGeneration()) { // CraftBukkit - SPIGOT-7475: Don't call event during world generation
++ CraftBlock bukkitBlock = CraftBlock.at(level, cursor.pos);
++ SculkBloomEvent event = new SculkBloomEvent(bukkitBlock, cursor.getCharge());
++ Bukkit.getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ return;
++ }
++
++ cursor.charge = event.getCharge();
++ }
++ // CraftBukkit end
++
++ this.cursors.add(cursor);
+ }
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SculkVeinBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SculkVeinBlock.java.patch
new file mode 100644
index 0000000000..65795265b7
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SculkVeinBlock.java.patch
@@ -0,0 +1,46 @@
+--- a/net/minecraft/world/level/block/SculkVeinBlock.java
++++ b/net/minecraft/world/level/block/SculkVeinBlock.java
+@@ -110,13 +107,13 @@
+ }
+
+ @Override
+- @Override
+- public int attemptUseCharge(SculkSpreader.ChargeCursor sculkspreader_chargecursor, LevelAccessor levelaccessor, BlockPos blockpos, RandomSource randomsource, SculkSpreader sculkspreader, boolean flag) {
+- return flag && this.attemptPlaceSculk(sculkspreader, levelaccessor, sculkspreader_chargecursor.getPos(), randomsource) ? sculkspreader_chargecursor.getCharge() - 1 : (randomsource.nextInt(sculkspreader.chargeDecayRate()) == 0 ? Mth.floor((float) sculkspreader_chargecursor.getCharge() * 0.5F) : sculkspreader_chargecursor.getCharge());
++ public int attemptUseCharge(SculkSpreader.ChargeCursor cursor, LevelAccessor level, BlockPos pos, RandomSource random, SculkSpreader spreader, boolean shouldConvertBlocks) {
++ // CraftBukkit - add source block
++ return shouldConvertBlocks && this.attemptPlaceSculk(spreader, level, cursor.getPos(), random, pos) ? cursor.getCharge() - 1 : (random.nextInt(spreader.chargeDecayRate()) == 0 ? Mth.floor((float) cursor.getCharge() * 0.5F) : cursor.getCharge());
+ }
+
+- private boolean attemptPlaceSculk(SculkSpreader sculkspreader, LevelAccessor levelaccessor, BlockPos blockpos, RandomSource randomsource) {
+- BlockState blockstate = levelaccessor.getBlockState(blockpos);
++ private boolean attemptPlaceSculk(SculkSpreader sculkspreader, LevelAccessor generatoraccess, BlockPos blockposition, RandomSource randomsource, BlockPos sourceBlock) { // CraftBukkit
++ IBlockData iblockdata = generatoraccess.getBlockState(blockposition);
+ TagKey<Block> tagkey = sculkspreader.replaceableBlocks();
+ Iterator iterator = Direction.allShuffled(randomsource).iterator();
+
+@@ -130,13 +127,17 @@
+ if (blockstate1.is(tagkey)) {
+ BlockState blockstate2 = Blocks.SCULK.defaultBlockState();
+
+- levelaccessor.setBlock(blockpos1, blockstate2, 3);
+- Block.pushEntitiesUp(blockstate1, blockstate2, levelaccessor, blockpos1);
+- levelaccessor.playSound((Player) null, blockpos1, SoundEvents.SCULK_BLOCK_SPREAD, SoundSource.BLOCKS, 1.0F, 1.0F);
+- this.veinSpreader.spreadAll(blockstate2, levelaccessor, blockpos1, sculkspreader.isWorldGeneration());
+- Direction direction1 = direction.getOpposite();
+- Direction[] adirection = SculkVeinBlock.DIRECTIONS;
+- int i = adirection.length;
++ // CraftBukkit start - Call BlockSpreadEvent
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(generatoraccess, sourceBlock, blockposition1, iblockdata2, 3)) {
++ return false;
++ }
++ // CraftBukkit end
++ Block.pushEntitiesUp(iblockdata1, iblockdata2, generatoraccess, blockposition1);
++ generatoraccess.playSound((Player) null, blockposition1, SoundEvents.SCULK_BLOCK_SPREAD, SoundSource.BLOCKS, 1.0F, 1.0F);
++ this.veinSpreader.spreadAll(iblockdata2, generatoraccess, blockposition1, sculkspreader.isWorldGeneration());
++ Direction enumdirection1 = enumdirection.getOpposite();
++ Direction[] aenumdirection = SculkVeinBlock.DIRECTIONS;
++ int i = aenumdirection.length;
+
+ for (int j = 0; j < i; ++j) {
+ Direction direction2 = adirection[j];
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SignBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SignBlock.java.patch
new file mode 100644
index 0000000000..6093dba163
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SignBlock.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/world/level/block/SignBlock.java
++++ b/net/minecraft/world/level/block/SignBlock.java
+@@ -123,8 +117,8 @@
+ return InteractionResult.SUCCESS;
+ } else if (flag2) {
+ return InteractionResult.SUCCESS;
+- } else if (!this.otherPlayerIsEditingSign(player, signblockentity) && player.mayBuild() && this.hasEditableText(player, signblockentity, flag1)) {
+- this.openTextEdit(player, signblockentity, flag1);
++ } else if (!this.otherPlayerIsEditingSign(player, tileentitysign) && player.mayBuild() && this.hasEditableText(player, tileentitysign, flag1)) {
++ this.openTextEdit(player, tileentitysign, flag1, org.bukkit.event.player.PlayerSignOpenEvent.Cause.INTERACT); // CraftBukkit
+ return this.getInteractionResult(flag);
+ } else {
+ return InteractionResult.PASS;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SnowLayerBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SnowLayerBlock.java.patch
new file mode 100644
index 0000000000..5aeff46aa6
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SnowLayerBlock.java.patch
@@ -0,0 +1,23 @@
+--- a/net/minecraft/world/level/block/SnowLayerBlock.java
++++ b/net/minecraft/world/level/block/SnowLayerBlock.java
+@@ -107,11 +97,15 @@
+ }
+
+ @Override
+- @Override
+- public void randomTick(BlockState blockstate, ServerLevel serverlevel, BlockPos blockpos, RandomSource randomsource) {
+- if (serverlevel.getBrightness(LightLayer.BLOCK, blockpos) > 11) {
+- dropResources(blockstate, serverlevel, blockpos);
+- serverlevel.removeBlock(blockpos, false);
++ public void randomTick(IBlockData state, ServerLevel level, BlockPos pos, RandomSource random) {
++ if (level.getBrightness(EnumSkyBlock.BLOCK, pos) > 11) {
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(level, pos, Blocks.AIR.defaultBlockState()).isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
++ dropResources(state, level, pos);
++ level.removeBlock(pos, false);
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SpawnerBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SpawnerBlock.java.patch
new file mode 100644
index 0000000000..94bcf33ce2
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SpawnerBlock.java.patch
@@ -0,0 +1,23 @@
+--- a/net/minecraft/world/level/block/SpawnerBlock.java
++++ b/net/minecraft/world/level/block/SpawnerBlock.java
+@@ -41,8 +43,9 @@
+ @Nullable
+ @Override
+ @Override
+- public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState blockstate, BlockEntityType<T> blockentitytype) {
+- return createTickerHelper(blockentitytype, BlockEntityType.MOB_SPAWNER, level.isClientSide ? SpawnerBlockEntity::clientTick : SpawnerBlockEntity::serverTick);
++ public void spawnAfterBreak(IBlockData state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) {
++ super.spawnAfterBreak(state, level, pos, stack, dropExperience);
++ // CraftBukkit start - Delegate to getExpDrop
+ }
+
+ @Override
+@@ -55,6 +57,8 @@
+ this.popExperience(serverlevel, blockpos, i);
+ }
+
++ return 0;
++ // CraftBukkit end
+ }
+
+ @Override
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SpongeBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SpongeBlock.java.patch
new file mode 100644
index 0000000000..589241706e
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SpongeBlock.java.patch
@@ -0,0 +1,116 @@
+--- a/net/minecraft/world/level/block/SpongeBlock.java
++++ b/net/minecraft/world/level/block/SpongeBlock.java
+@@ -12,6 +12,12 @@
+ import net.minecraft.world.level.block.state.BlockBehaviour;
+ import net.minecraft.world.level.block.state.BlockState;
+ import net.minecraft.world.level.material.FluidState;
++// CraftBukkit start
++import java.util.List;
++import org.bukkit.craftbukkit.block.CraftBlockState;
++import org.bukkit.craftbukkit.util.BlockStateListPopulator;
++import org.bukkit.event.block.SpongeAbsorbEvent;
++// CraftBukkit end
+
+ public class SpongeBlock extends Block {
+
+@@ -53,10 +56,11 @@
+
+ }
+
+- private boolean removeWaterBreadthFirstSearch(Level level, BlockPos blockpos) {
+- return BlockPos.breadthFirstTraversal(blockpos, 6, 65, (blockpos1, consumer) -> {
+- Direction[] adirection = SpongeBlock.ALL_DIRECTIONS;
+- int i = adirection.length;
++ private boolean removeWaterBreadthFirstSearch(Level level, BlockPos pos) {
++ BlockStateListPopulator blockList = new BlockStateListPopulator(level); // CraftBukkit - Use BlockStateListPopulator
++ BlockPos.breadthFirstTraversal(pos, 6, 65, (blockposition1, consumer) -> {
++ Direction[] aenumdirection = SpongeBlock.ALL_DIRECTIONS;
++ int i = aenumdirection.length;
+
+ for (int j = 0; j < i; ++j) {
+ Direction direction = adirection[j];
+@@ -68,8 +72,10 @@
+ if (blockpos1.equals(blockpos)) {
+ return true;
+ } else {
+- BlockState blockstate = level.getBlockState(blockpos1);
+- FluidState fluidstate = level.getFluidState(blockpos1);
++ // CraftBukkit start
++ IBlockData iblockdata = blockList.getBlockState(blockposition1);
++ FluidState fluid = blockList.getFluidState(blockposition1);
++ // CraftBukkit end
+
+ if (!fluidstate.is(FluidTags.WATER)) {
+ return false;
+@@ -79,27 +85,64 @@
+ if (block instanceof BucketPickup) {
+ BucketPickup bucketpickup = (BucketPickup) block;
+
+- if (!bucketpickup.pickupBlock((Player) null, level, blockpos1, blockstate).isEmpty()) {
++ if (!ifluidsource.pickupBlock((Player) null, blockList, blockposition1, iblockdata).isEmpty()) { // CraftBukkit
+ return true;
+ }
+ }
+
+- if (blockstate.getBlock() instanceof LiquidBlock) {
+- level.setBlock(blockpos1, Blocks.AIR.defaultBlockState(), 3);
++ if (iblockdata.getBlock() instanceof LiquidBlock) {
++ blockList.setBlock(blockposition1, Blocks.AIR.defaultBlockState(), 3); // CraftBukkit
+ } else {
+ if (!blockstate.is(Blocks.KELP) && !blockstate.is(Blocks.KELP_PLANT) && !blockstate.is(Blocks.SEAGRASS) && !blockstate.is(Blocks.TALL_SEAGRASS)) {
+ return false;
+ }
+
+- BlockEntity blockentity = blockstate.hasBlockEntity() ? level.getBlockEntity(blockpos1) : null;
++ // CraftBukkit start
++ // TileEntity tileentity = iblockdata.hasBlockEntity() ? world.getBlockEntity(blockposition1) : null;
+
+- dropResources(blockstate, level, blockpos1, blockentity);
+- level.setBlock(blockpos1, Blocks.AIR.defaultBlockState(), 3);
++ // dropResources(iblockdata, world, blockposition1, tileentity);
++ blockList.setBlock(blockposition1, Blocks.AIR.defaultBlockState(), 3);
++ // CraftBukkit end
+ }
+
+ return true;
+ }
+ }
+- }) > 1;
++ });
++ // CraftBukkit start
++ List<CraftBlockState> blocks = blockList.getList(); // Is a clone
++ if (!blocks.isEmpty()) {
++ final org.bukkit.block.Block bblock = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++
++ SpongeAbsorbEvent event = new SpongeAbsorbEvent(bblock, (List<org.bukkit.block.BlockState>) (List) blocks);
++ level.getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return false;
++ }
++
++ for (CraftBlockState block : blocks) {
++ BlockPos blockposition1 = block.getPosition();
++ IBlockData iblockdata = level.getBlockState(blockposition1);
++ FluidState fluid = level.getFluidState(blockposition1);
++
++ if (fluid.is(FluidTags.WATER)) {
++ if (iblockdata.getBlock() instanceof BucketPickup && !((BucketPickup) iblockdata.getBlock()).pickupBlock((Player) null, blockList, blockposition1, iblockdata).isEmpty()) {
++ // NOP
++ } else if (iblockdata.getBlock() instanceof LiquidBlock) {
++ // NOP
++ } else if (iblockdata.is(Blocks.KELP) || iblockdata.is(Blocks.KELP_PLANT) || iblockdata.is(Blocks.SEAGRASS) || iblockdata.is(Blocks.TALL_SEAGRASS)) {
++ BlockEntity tileentity = iblockdata.hasBlockEntity() ? level.getBlockEntity(blockposition1) : null;
++
++ dropResources(iblockdata, level, blockposition1, tileentity);
++ }
++ }
++ level.setBlock(blockposition1, block.getHandle(), block.getFlag());
++ }
++
++ return true;
++ }
++ return false;
++ // CraftBukkit end
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java.patch
new file mode 100644
index 0000000000..797b68dd95
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java.patch
@@ -0,0 +1,32 @@
+--- a/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java
++++ b/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java
+@@ -43,10 +42,14 @@
+ }
+
+ @Override
+- @Override
+- public void randomTick(BlockState blockstate, ServerLevel serverlevel, BlockPos blockpos, RandomSource randomsource) {
+- if (!canBeGrass(blockstate, serverlevel, blockpos)) {
+- serverlevel.setBlockAndUpdate(blockpos, Blocks.DIRT.defaultBlockState());
++ public void randomTick(IBlockData state, ServerLevel level, BlockPos pos, RandomSource random) {
++ if (!canBeGrass(state, level, pos)) {
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockFadeEvent(level, pos, Blocks.DIRT.defaultBlockState()).isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
++ level.setBlockAndUpdate(pos, Blocks.DIRT.defaultBlockState());
+ } else {
+ if (serverlevel.getMaxLocalRawBrightness(blockpos.above()) >= 9) {
+ BlockState blockstate1 = this.defaultBlockState();
+@@ -54,8 +57,8 @@
+ for (int i = 0; i < 4; ++i) {
+ BlockPos blockpos1 = blockpos.offset(randomsource.nextInt(3) - 1, randomsource.nextInt(5) - 3, randomsource.nextInt(3) - 1);
+
+- if (serverlevel.getBlockState(blockpos1).is(Blocks.DIRT) && canPropagate(blockstate1, serverlevel, blockpos1)) {
+- serverlevel.setBlockAndUpdate(blockpos1, (BlockState) blockstate1.setValue(SpreadingSnowyDirtBlock.SNOWY, serverlevel.getBlockState(blockpos1.above()).is(Blocks.SNOW)));
++ if (level.getBlockState(blockposition1).is(Blocks.DIRT) && canPropagate(iblockdata1, level, blockposition1)) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, blockposition1, (IBlockData) iblockdata1.setValue(SpreadingSnowyDirtBlock.SNOWY, level.getBlockState(blockposition1.above()).is(Blocks.SNOW))); // CraftBukkit
+ }
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/StemBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/StemBlock.java.patch
new file mode 100644
index 0000000000..675d69cf74
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/StemBlock.java.patch
@@ -0,0 +1,45 @@
+--- a/net/minecraft/world/level/block/StemBlock.java
++++ b/net/minecraft/world/level/block/StemBlock.java
+@@ -26,6 +26,7 @@
+ import net.minecraft.world.level.block.state.properties.IntegerProperty;
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
+
+ public class StemBlock extends BushBlock implements BonemealableBlock {
+
+@@ -82,8 +79,8 @@
+ int i = (Integer) blockstate.getValue(StemBlock.AGE);
+
+ if (i < 7) {
+- blockstate = (BlockState) blockstate.setValue(StemBlock.AGE, i + 1);
+- serverlevel.setBlock(blockpos, blockstate, 2);
++ state = (IBlockData) state.setValue(StemBlock.AGE, i + 1);
++ CraftEventFactory.handleBlockGrowEvent(level, pos, state, 2); // CraftBukkit
+ } else {
+ Direction direction = Direction.Plane.HORIZONTAL.getRandomDirection(randomsource);
+ BlockPos blockpos1 = blockpos.relative(direction);
+@@ -95,8 +92,12 @@
+ Optional<Block> optional1 = registry.getOptional(this.attachedStem);
+
+ if (optional.isPresent() && optional1.isPresent()) {
+- serverlevel.setBlockAndUpdate(blockpos1, ((Block) optional.get()).defaultBlockState());
+- serverlevel.setBlockAndUpdate(blockpos, (BlockState) ((Block) optional1.get()).defaultBlockState().setValue(HorizontalDirectionalBlock.FACING, direction));
++ // CraftBukkit start
++ if (!CraftEventFactory.handleBlockGrowEvent(level, blockposition1, ((Block) optional.get()).defaultBlockState())) {
++ return;
++ }
++ // CraftBukkit end
++ level.setBlockAndUpdate(pos, (IBlockData) ((Block) optional1.get()).defaultBlockState().setValue(HorizontalDirectionalBlock.FACING, enumdirection));
+ }
+ }
+ }
+@@ -129,7 +126,7 @@
+ int i = Math.min(7, (Integer) blockstate.getValue(StemBlock.AGE) + Mth.nextInt(serverlevel.random, 2, 5));
+ BlockState blockstate1 = (BlockState) blockstate.setValue(StemBlock.AGE, i);
+
+- serverlevel.setBlock(blockpos, blockstate1, 2);
++ CraftEventFactory.handleBlockGrowEvent(level, pos, iblockdata1, 2); // CraftBukkit
+ if (i == 7) {
+ blockstate1.randomTick(serverlevel, blockpos, serverlevel.random);
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SugarCaneBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SugarCaneBlock.java.patch
new file mode 100644
index 0000000000..eb1ae72ea2
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SugarCaneBlock.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/world/level/block/SugarCaneBlock.java
++++ b/net/minecraft/world/level/block/SugarCaneBlock.java
+@@ -67,8 +63,8 @@
+ int j = (Integer) blockstate.getValue(SugarCaneBlock.AGE);
+
+ if (j == 15) {
+- serverlevel.setBlockAndUpdate(blockpos.above(), this.defaultBlockState());
+- serverlevel.setBlock(blockpos, (BlockState) blockstate.setValue(SugarCaneBlock.AGE, 0), 4);
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos.above(), this.defaultBlockState()); // CraftBukkit
++ level.setBlock(pos, (IBlockData) state.setValue(SugarCaneBlock.AGE, 0), 4);
+ } else {
+ serverlevel.setBlock(blockpos, (BlockState) blockstate.setValue(SugarCaneBlock.AGE, j + 1), 4);
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SweetBerryBushBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SweetBerryBushBlock.java.patch
new file mode 100644
index 0000000000..d260cf1c58
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/SweetBerryBushBlock.java.patch
@@ -0,0 +1,58 @@
+--- a/net/minecraft/world/level/block/SweetBerryBushBlock.java
++++ b/net/minecraft/world/level/block/SweetBerryBushBlock.java
+@@ -27,6 +27,13 @@
+ import net.minecraft.world.phys.Vec3;
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++// CraftBukkit start
++import java.util.Collections;
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.player.PlayerHarvestBlockEvent;
++// CraftBukkit end
+
+ public class SweetBerryBushBlock extends BushBlock implements BonemealableBlock {
+
+@@ -74,8 +76,8 @@
+ if (i < 3 && randomsource.nextInt(5) == 0 && serverlevel.getRawBrightness(blockpos.above(), 0) >= 9) {
+ BlockState blockstate1 = (BlockState) blockstate.setValue(SweetBerryBushBlock.AGE, i + 1);
+
+- serverlevel.setBlock(blockpos, blockstate1, 2);
+- serverlevel.gameEvent(GameEvent.BLOCK_CHANGE, blockpos, GameEvent.Context.of(blockstate1));
++ if (!CraftEventFactory.handleBlockGrowEvent(level, pos, iblockdata1, 2)) return; // CraftBukkit
++ level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(iblockdata1));
+ }
+
+ }
+@@ -90,7 +91,9 @@
+ double d1 = Math.abs(entity.getZ() - entity.zOld);
+
+ if (d0 >= 0.003000000026077032D || d1 >= 0.003000000026077032D) {
++ CraftEventFactory.blockDamage = CraftBlock.at(level, pos); // CraftBukkit
+ entity.hurt(level.damageSources().sweetBerryBush(), 1.0F);
++ CraftEventFactory.blockDamage = null; // CraftBukkit
+ }
+ }
+
+@@ -108,9 +110,17 @@
+ } else if (i > 1) {
+ int j = 1 + level.random.nextInt(2);
+
+- popResource(level, blockpos, new ItemStack(Items.SWEET_BERRIES, j + (flag ? 1 : 0)));
+- level.playSound((Player) null, blockpos, SoundEvents.SWEET_BERRY_BUSH_PICK_BERRIES, SoundSource.BLOCKS, 1.0F, 0.8F + level.random.nextFloat() * 0.4F);
+- BlockState blockstate1 = (BlockState) blockstate.setValue(SweetBerryBushBlock.AGE, 1);
++ // CraftBukkit start
++ PlayerHarvestBlockEvent event = CraftEventFactory.callPlayerHarvestBlockEvent(level, pos, player, hand, Collections.singletonList(new ItemStack(Items.SWEET_BERRIES, j + (flag ? 1 : 0))));
++ if (event.isCancelled()) {
++ return InteractionResult.SUCCESS; // We need to return a success either way, because making it PASS or FAIL will result in a bug where cancelling while harvesting w/ block in hand places block
++ }
++ for (org.bukkit.inventory.ItemStack itemStack : event.getItemsHarvested()) {
++ popResource(level, pos, CraftItemStack.asNMSCopy(itemStack));
++ }
++ // CraftBukkit end
++ level.playSound((Player) null, pos, SoundEvents.SWEET_BERRY_BUSH_PICK_BERRIES, SoundSource.BLOCKS, 1.0F, 0.8F + level.random.nextFloat() * 0.4F);
++ IBlockData iblockdata1 = (IBlockData) state.setValue(SweetBerryBushBlock.AGE, 1);
+
+ level.setBlock(blockpos, blockstate1, 2);
+ level.gameEvent(GameEvent.BLOCK_CHANGE, blockpos, GameEvent.Context.of(player, blockstate1));
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/TntBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/TntBlock.java.patch
new file mode 100644
index 0000000000..e7df5b1fff
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/TntBlock.java.patch
@@ -0,0 +1,92 @@
+--- a/net/minecraft/world/level/block/TntBlock.java
++++ b/net/minecraft/world/level/block/TntBlock.java
+@@ -25,6 +25,10 @@
+ import net.minecraft.world.level.block.state.properties.BooleanProperty;
+ import net.minecraft.world.level.gameevent.GameEvent;
+ import net.minecraft.world.phys.BlockHitResult;
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.block.TNTPrimeEvent.PrimeCause;
++// CraftBukkit end
+
+ public class TntBlock extends Block {
+
+@@ -43,32 +46,29 @@
+ }
+
+ @Override
+- @Override
+- public void onPlace(BlockState blockstate, Level level, BlockPos blockpos, BlockState blockstate1, boolean flag) {
+- if (!blockstate1.is(blockstate.getBlock())) {
+- if (level.hasNeighborSignal(blockpos)) {
+- explode(level, blockpos);
+- level.removeBlock(blockpos, false);
++ public void onPlace(IBlockData state, Level level, BlockPos pos, IBlockData oldState, boolean isMoving) {
++ if (!oldState.is(state.getBlock())) {
++ if (level.hasNeighborSignal(pos) && CraftEventFactory.callTNTPrimeEvent(level, pos, PrimeCause.REDSTONE, null, null)) { // CraftBukkit - TNTPrimeEvent
++ explode(level, pos);
++ level.removeBlock(pos, false);
+ }
+
+ }
+ }
+
+ @Override
+- @Override
+- public void neighborChanged(BlockState blockstate, Level level, BlockPos blockpos, Block block, BlockPos blockpos1, boolean flag) {
+- if (level.hasNeighborSignal(blockpos)) {
+- explode(level, blockpos);
+- level.removeBlock(blockpos, false);
++ public void neighborChanged(IBlockData state, Level level, BlockPos pos, Block block, BlockPos fromPos, boolean isMoving) {
++ if (level.hasNeighborSignal(pos) && CraftEventFactory.callTNTPrimeEvent(level, pos, PrimeCause.REDSTONE, null, fromPos)) { // CraftBukkit - TNTPrimeEvent
++ explode(level, pos);
++ level.removeBlock(pos, false);
+ }
+
+ }
+
+ @Override
+- @Override
+- public BlockState playerWillDestroy(Level level, BlockPos blockpos, BlockState blockstate, Player player) {
+- if (!level.isClientSide() && !player.isCreative() && (Boolean) blockstate.getValue(TntBlock.UNSTABLE)) {
+- explode(level, blockpos);
++ public IBlockData playerWillDestroy(Level world, BlockPos blockposition, IBlockData iblockdata, Player entityhuman) {
++ if (!world.isClientSide() && !entityhuman.isCreative() && (Boolean) iblockdata.getValue(TntBlock.UNSTABLE) && CraftEventFactory.callTNTPrimeEvent(world, blockposition, PrimeCause.BLOCK_BREAK, entityhuman, null)) { // CraftBukkit - TNTPrimeEvent
++ explode(world, blockposition);
+ }
+
+ return super.playerWillDestroy(level, blockpos, blockstate, player);
+@@ -108,8 +106,13 @@
+ if (!itemstack.is(Items.FLINT_AND_STEEL) && !itemstack.is(Items.FIRE_CHARGE)) {
+ return super.use(blockstate, level, blockpos, player, interactionhand, blockhitresult);
+ } else {
+- explode(level, blockpos, player);
+- level.setBlock(blockpos, Blocks.AIR.defaultBlockState(), 11);
++ // CraftBukkit start - TNTPrimeEvent
++ if (!CraftEventFactory.callTNTPrimeEvent(level, pos, PrimeCause.PLAYER, player, null)) {
++ return InteractionResult.CONSUME;
++ }
++ // CraftBukkit end
++ explode(level, pos, player);
++ level.setBlock(pos, Blocks.AIR.defaultBlockState(), 11);
+ Item item = itemstack.getItem();
+
+ if (!player.isCreative()) {
+@@ -134,9 +136,14 @@
+ BlockPos blockpos = blockhitresult.getBlockPos();
+ Entity entity = projectile.getOwner();
+
+- if (projectile.isOnFire() && projectile.mayInteract(level, blockpos)) {
+- explode(level, blockpos, entity instanceof LivingEntity ? (LivingEntity) entity : null);
+- level.removeBlock(blockpos, false);
++ if (projectile.isOnFire() && projectile.mayInteract(level, blockposition)) {
++ // CraftBukkit start
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, Blocks.AIR.defaultBlockState()) || !CraftEventFactory.callTNTPrimeEvent(level, blockposition, PrimeCause.PROJECTILE, projectile, null)) {
++ return;
++ }
++ // CraftBukkit end
++ explode(level, blockposition, entity instanceof LivingEntity ? (LivingEntity) entity : null);
++ level.removeBlock(blockposition, false);
+ }
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/TrapDoorBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/TrapDoorBlock.java.patch
new file mode 100644
index 0000000000..4a84297494
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/TrapDoorBlock.java.patch
@@ -0,0 +1,39 @@
+--- a/net/minecraft/world/level/block/TrapDoorBlock.java
++++ b/net/minecraft/world/level/block/TrapDoorBlock.java
+@@ -33,6 +33,7 @@
+ import net.minecraft.world.phys.BlockHitResult;
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++import org.bukkit.event.block.BlockRedstoneEvent; // CraftBukkit
+
+ public class TrapDoorBlock extends HorizontalDirectionalBlock implements SimpleWaterloggedBlock {
+
+@@ -144,11 +139,24 @@
+ if (!level.isClientSide) {
+ boolean flag1 = level.hasNeighborSignal(blockpos);
+
+- if (flag1 != (Boolean) blockstate.getValue(TrapDoorBlock.POWERED)) {
+- if ((Boolean) blockstate.getValue(TrapDoorBlock.OPEN) != flag1) {
+- blockstate = (BlockState) blockstate.setValue(TrapDoorBlock.OPEN, flag1);
+- this.playSound((Player) null, level, blockpos, flag1);
++ if (flag1 != (Boolean) state.getValue(TrapDoorBlock.POWERED)) {
++ // CraftBukkit start
++ org.bukkit.World bworld = level.getWorld();
++ org.bukkit.block.Block bblock = bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++
++ int power = bblock.getBlockPower();
++ int oldPower = (Boolean) state.getValue(OPEN) ? 15 : 0;
++
++ if (oldPower == 0 ^ power == 0 || block.defaultBlockState().isSignalSource()) {
++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(bblock, oldPower, power);
++ level.getCraftServer().getPluginManager().callEvent(eventRedstone);
++ flag1 = eventRedstone.getNewCurrent() > 0;
+ }
++ // CraftBukkit end
++ if ((Boolean) state.getValue(TrapDoorBlock.OPEN) != flag1) {
++ state = (IBlockData) state.setValue(TrapDoorBlock.OPEN, flag1);
++ this.playSound((Player) null, level, pos, flag1);
++ }
+
+ level.setBlock(blockpos, (BlockState) blockstate.setValue(TrapDoorBlock.POWERED, flag1), 2);
+ if ((Boolean) blockstate.getValue(TrapDoorBlock.WATERLOGGED)) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/TripWireBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/TripWireBlock.java.patch
new file mode 100644
index 0000000000..3872e5202c
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/TripWireBlock.java.patch
@@ -0,0 +1,51 @@
+--- a/net/minecraft/world/level/block/TripWireBlock.java
++++ b/net/minecraft/world/level/block/TripWireBlock.java
+@@ -26,6 +26,7 @@
+ import net.minecraft.world.level.gameevent.GameEvent;
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++import org.bukkit.event.entity.EntityInteractEvent; // CraftBukkit
+
+ public class TripWireBlock extends Block {
+
+@@ -175,6 +167,40 @@
+ }
+ }
+
++ // CraftBukkit start - Call interact even when triggering connected tripwire
++ if (flag != flag1 && flag1 && (Boolean)iblockdata.getValue(ATTACHED)) {
++ org.bukkit.World bworld = level.getWorld();
++ org.bukkit.plugin.PluginManager manager = level.getCraftServer().getPluginManager();
++ org.bukkit.block.Block block = bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++ boolean allowed = false;
++
++ // If all of the events are cancelled block the tripwire trigger, else allow
++ for (Object object : list) {
++ if (object != null) {
++ org.bukkit.event.Cancellable cancellable;
++
++ if (object instanceof Player) {
++ cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((Player) object, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null);
++ } else if (object instanceof Entity) {
++ cancellable = new EntityInteractEvent(((Entity) object).getBukkitEntity(), block);
++ manager.callEvent((EntityInteractEvent) cancellable);
++ } else {
++ continue;
++ }
++
++ if (!cancellable.isCancelled()) {
++ allowed = true;
++ break;
++ }
++ }
++ }
++
++ if (!allowed) {
++ return;
++ }
++ }
++ // CraftBukkit end
++
+ if (flag1 != flag) {
+ blockstate = (BlockState) blockstate.setValue(TripWireBlock.POWERED, flag1);
+ level.setBlock(blockpos, blockstate, 3);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/TripWireHookBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/TripWireHookBlock.java.patch
new file mode 100644
index 0000000000..788243b667
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/TripWireHookBlock.java.patch
@@ -0,0 +1,39 @@
+--- a/net/minecraft/world/level/block/TripWireHookBlock.java
++++ b/net/minecraft/world/level/block/TripWireHookBlock.java
+@@ -28,6 +28,10 @@
+ import net.minecraft.world.level.gameevent.GameEvent;
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++// CraftBukkit start
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.event.block.BlockRedstoneEvent;
++// CraftBukkit end
+
+ public class TripWireHookBlock extends Block {
+
+@@ -179,11 +177,20 @@
+ emitState(level, blockpos1, flag4, flag5, flag2, flag3);
+ }
+
+- emitState(level, blockpos, flag4, flag5, flag2, flag3);
+- if (!flag) {
+- level.setBlock(blockpos, (BlockState) blockstate3.setValue(TripWireHookBlock.FACING, direction), 3);
+- if (flag1) {
+- notifyNeighbors(block, level, blockpos, direction);
++ // CraftBukkit start
++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(CraftBlock.at(world, level), 15, 0);
++ world.getCraftServer().getPluginManager().callEvent(eventRedstone);
++
++ if (eventRedstone.getNewCurrent() > 0) {
++ return;
++ }
++ // CraftBukkit end
++
++ emitState(world, level, flag4, flag5, flag2, flag3);
++ if (!hookState) {
++ world.setBlock(level, (IBlockData) iblockdata3.setValue(TripWireHookBlock.FACING, enumdirection), 3);
++ if (attaching) {
++ notifyNeighbors(block, world, level, enumdirection);
+ }
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/TurtleEggBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/TurtleEggBlock.java.patch
new file mode 100644
index 0000000000..3e7b7d98f4
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/TurtleEggBlock.java.patch
@@ -0,0 +1,83 @@
+--- a/net/minecraft/world/level/block/TurtleEggBlock.java
++++ b/net/minecraft/world/level/block/TurtleEggBlock.java
+@@ -29,6 +29,11 @@
+ import net.minecraft.world.level.gameevent.GameEvent;
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++// CraftBukkit start
++import org.bukkit.event.entity.EntityInteractEvent;
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++// CraftBukkit end
+
+ public class TurtleEggBlock extends Block {
+
+@@ -74,8 +76,21 @@
+
+ private void destroyEgg(Level level, BlockState blockstate, BlockPos blockpos, Entity entity, int i) {
+ if (this.canDestroyEgg(level, entity)) {
+- if (!level.isClientSide && level.random.nextInt(i) == 0 && blockstate.is(Blocks.TURTLE_EGG)) {
+- this.decreaseEggs(level, blockpos, blockstate);
++ if (!level.isClientSide && level.random.nextInt(chance) == 0 && state.is(Blocks.TURTLE_EGG)) {
++ // CraftBukkit start - Step on eggs
++ org.bukkit.event.Cancellable cancellable;
++ if (entity instanceof Player) {
++ cancellable = CraftEventFactory.callPlayerInteractEvent((Player) entity, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null);
++ } else {
++ cancellable = new EntityInteractEvent(entity.getBukkitEntity(), CraftBlock.at(level, pos));
++ level.getCraftServer().getPluginManager().callEvent((EntityInteractEvent) cancellable);
++ }
++
++ if (cancellable.isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
++ this.decreaseEggs(level, pos, state);
+ }
+
+ }
+@@ -102,23 +116,33 @@
+ int i = (Integer) blockstate.getValue(TurtleEggBlock.HATCH);
+
+ if (i < 2) {
+- serverlevel.playSound((Player) null, blockpos, SoundEvents.TURTLE_EGG_CRACK, SoundSource.BLOCKS, 0.7F, 0.9F + randomsource.nextFloat() * 0.2F);
+- serverlevel.setBlock(blockpos, (BlockState) blockstate.setValue(TurtleEggBlock.HATCH, i + 1), 2);
+- serverlevel.gameEvent(GameEvent.BLOCK_CHANGE, blockpos, GameEvent.Context.of(blockstate));
++ // CraftBukkit start - Call BlockGrowEvent
++ if (!CraftEventFactory.handleBlockGrowEvent(level, pos, state.setValue(TurtleEggBlock.HATCH, i + 1), 2)) {
++ return;
++ }
++ // CraftBukkit end
++ level.playSound((Player) null, pos, SoundEvents.TURTLE_EGG_CRACK, SoundSource.BLOCKS, 0.7F, 0.9F + random.nextFloat() * 0.2F);
++ // worldserver.setBlock(blockposition, (IBlockData) iblockdata.setValue(BlockTurtleEgg.HATCH, i + 1), 2); // CraftBukkit - handled above
++ level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(state));
+ } else {
+- serverlevel.playSound((Player) null, blockpos, SoundEvents.TURTLE_EGG_HATCH, SoundSource.BLOCKS, 0.7F, 0.9F + randomsource.nextFloat() * 0.2F);
+- serverlevel.removeBlock(blockpos, false);
+- serverlevel.gameEvent(GameEvent.BLOCK_DESTROY, blockpos, GameEvent.Context.of(blockstate));
++ // CraftBukkit start - Call BlockFadeEvent
++ if (CraftEventFactory.callBlockFadeEvent(level, pos, Blocks.AIR.defaultBlockState()).isCancelled()) {
++ return;
++ }
++ // CraftBukkit end
++ level.playSound((Player) null, pos, SoundEvents.TURTLE_EGG_HATCH, SoundSource.BLOCKS, 0.7F, 0.9F + random.nextFloat() * 0.2F);
++ level.removeBlock(pos, false);
++ level.gameEvent(GameEvent.BLOCK_DESTROY, pos, GameEvent.Context.of(state));
+
+ for (int j = 0; j < (Integer) blockstate.getValue(TurtleEggBlock.EGGS); ++j) {
+ serverlevel.levelEvent(2001, blockpos, Block.getId(blockstate));
+ Turtle turtle = (Turtle) EntityType.TURTLE.create(serverlevel);
+
+- if (turtle != null) {
+- turtle.setAge(-24000);
+- turtle.setHomePos(blockpos);
+- turtle.moveTo((double) blockpos.getX() + 0.3D + (double) j * 0.2D, (double) blockpos.getY(), (double) blockpos.getZ() + 0.3D, 0.0F, 0.0F);
+- serverlevel.addFreshEntity(turtle);
++ if (entityturtle != null) {
++ entityturtle.setAge(-24000);
++ entityturtle.setHomePos(pos);
++ entityturtle.moveTo((double) pos.getX() + 0.3D + (double) j * 0.2D, (double) pos.getY(), (double) pos.getZ() + 0.3D, 0.0F, 0.0F);
++ level.addFreshEntity(entityturtle, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.EGG); // CraftBukkit
+ }
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/VineBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/VineBlock.java.patch
new file mode 100644
index 0000000000..f01a593599
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/VineBlock.java.patch
@@ -0,0 +1,73 @@
+--- a/net/minecraft/world/level/block/VineBlock.java
++++ b/net/minecraft/world/level/block/VineBlock.java
+@@ -24,6 +24,7 @@
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.Shapes;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit
+
+ public class VineBlock extends Block {
+
+@@ -209,10 +204,13 @@
+ BlockPos blockpos3 = blockpos2.relative(direction1);
+ BlockPos blockpos4 = blockpos2.relative(direction2);
+
+- if (flag && isAcceptableNeighbour(serverlevel, blockpos3, direction1)) {
+- serverlevel.setBlock(blockpos2, (BlockState) this.defaultBlockState().setValue(getPropertyForFace(direction1), true), 2);
+- } else if (flag1 && isAcceptableNeighbour(serverlevel, blockpos4, direction2)) {
+- serverlevel.setBlock(blockpos2, (BlockState) this.defaultBlockState().setValue(getPropertyForFace(direction2), true), 2);
++ // CraftBukkit start - Call BlockSpreadEvent
++ BlockPos source = pos;
++
++ if (flag && isAcceptableNeighbour(level, blockposition3, enumdirection1)) {
++ CraftEventFactory.handleBlockSpreadEvent(level, source, blockposition2, (IBlockData) this.defaultBlockState().setValue(getPropertyForFace(enumdirection1), true), 2);
++ } else if (flag1 && isAcceptableNeighbour(level, blockposition4, enumdirection2)) {
++ CraftEventFactory.handleBlockSpreadEvent(level, source, blockposition2, (IBlockData) this.defaultBlockState().setValue(getPropertyForFace(enumdirection2), true), 2);
+ } else {
+ Direction direction3 = direction.getOpposite();
+
+@@ -223,16 +221,17 @@
+ } else if ((double) randomsource.nextFloat() < 0.05D && isAcceptableNeighbour(serverlevel, blockpos2.above(), Direction.UP)) {
+ serverlevel.setBlock(blockpos2, (BlockState) this.defaultBlockState().setValue(VineBlock.UP, true), 2);
+ }
++ // CraftBukkit end
+ }
+- } else if (isAcceptableNeighbour(serverlevel, blockpos2, direction)) {
+- serverlevel.setBlock(blockpos, (BlockState) blockstate.setValue(getPropertyForFace(direction), true), 2);
++ } else if (isAcceptableNeighbour(level, blockposition2, enumdirection)) {
++ CraftEventFactory.handleBlockGrowEvent(level, pos, (IBlockData) state.setValue(getPropertyForFace(enumdirection), true), 2); // CraftBukkit
+ }
+
+ }
+ } else {
+- if (direction == Direction.UP && blockpos.getY() < serverlevel.getMaxBuildHeight() - 1) {
+- if (this.canSupportAtFace(serverlevel, blockpos, direction)) {
+- serverlevel.setBlock(blockpos, (BlockState) blockstate.setValue(VineBlock.UP, true), 2);
++ if (enumdirection == Direction.UP && pos.getY() < level.getMaxBuildHeight() - 1) {
++ if (this.canSupportAtFace(level, pos, enumdirection)) {
++ CraftEventFactory.handleBlockGrowEvent(level, pos, (IBlockData) state.setValue(VineBlock.UP, true), 2); // CraftBukkit
+ return;
+ }
+
+@@ -251,8 +250,8 @@
+ }
+ }
+
+- if (this.hasHorizontalConnection(blockstate2)) {
+- serverlevel.setBlock(blockpos1, blockstate2, 2);
++ if (this.hasHorizontalConnection(iblockdata2)) {
++ CraftEventFactory.handleBlockSpreadEvent(level, pos, blockposition1, iblockdata2, 2); // CraftBukkit
+ }
+
+ return;
+@@ -266,8 +265,8 @@
+ BlockState blockstate3 = blockstate1.isAir() ? this.defaultBlockState() : blockstate1;
+ BlockState blockstate4 = this.copyRandomFaces(blockstate, blockstate3, randomsource);
+
+- if (blockstate3 != blockstate4 && this.hasHorizontalConnection(blockstate4)) {
+- serverlevel.setBlock(blockpos2, blockstate4, 2);
++ if (iblockdata3 != iblockdata4 && this.hasHorizontalConnection(iblockdata4)) {
++ CraftEventFactory.handleBlockSpreadEvent(level, pos, blockposition2, iblockdata4, 2); // CraftBukkit
+ }
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/WallHangingSignBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/WallHangingSignBlock.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/WallHangingSignBlock.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/WaterlilyBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/WaterlilyBlock.java.patch
new file mode 100644
index 0000000000..bd171643af
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/WaterlilyBlock.java.patch
@@ -0,0 +1,26 @@
+--- a/net/minecraft/world/level/block/WaterlilyBlock.java
++++ b/net/minecraft/world/level/block/WaterlilyBlock.java
+@@ -13,6 +13,9 @@
+ import net.minecraft.world.level.material.Fluids;
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++// CraftBukkit end
+
+ public class WaterlilyBlock extends BushBlock {
+
+@@ -34,7 +35,12 @@
+ public void entityInside(BlockState blockstate, Level level, BlockPos blockpos, Entity entity) {
+ super.entityInside(blockstate, level, blockpos, entity);
+ if (level instanceof ServerLevel && entity instanceof Boat) {
+- level.destroyBlock(new BlockPos(blockpos), true, entity);
++ // CraftBukkit start
++ if (!CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState())) {
++ return;
++ }
++ // CraftBukkit end
++ level.destroyBlock(new BlockPos(pos), true, entity);
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/WeightedPressurePlateBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/WeightedPressurePlateBlock.java.patch
new file mode 100644
index 0000000000..6988256183
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/WeightedPressurePlateBlock.java.patch
@@ -0,0 +1,44 @@
+--- a/net/minecraft/world/level/block/WeightedPressurePlateBlock.java
++++ b/net/minecraft/world/level/block/WeightedPressurePlateBlock.java
+@@ -13,6 +14,8 @@
+ import net.minecraft.world.level.block.state.properties.BlockSetType;
+ import net.minecraft.world.level.block.state.properties.BlockStateProperties;
+ import net.minecraft.world.level.block.state.properties.IntegerProperty;
++import org.bukkit.event.entity.EntityInteractEvent;
++// CraftBukkit end
+
+ public class WeightedPressurePlateBlock extends BasePressurePlateBlock {
+
+@@ -39,10 +41,29 @@
+ }
+
+ @Override
+- @Override
+- protected int getSignalStrength(Level level, BlockPos blockpos) {
+- int i = Math.min(getEntityCount(level, WeightedPressurePlateBlock.TOUCH_AABB.move(blockpos), Entity.class), this.maxWeight);
++ protected int getSignalStrength(Level level, BlockPos pos) {
++ // CraftBukkit start
++ // int i = Math.min(getEntityCount(world, BlockPressurePlateWeighted.TOUCH_AABB.move(blockposition), Entity.class), this.maxWeight);
++ int i = 0;
++ for (Entity entity : getEntities(level, WeightedPressurePlateBlock.TOUCH_AABB.move(pos), Entity.class)) {
++ org.bukkit.event.Cancellable cancellable;
+
++ if (entity instanceof Player) {
++ cancellable = org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerInteractEvent((Player) entity, org.bukkit.event.block.Action.PHYSICAL, pos, null, null, null);
++ } else {
++ cancellable = new EntityInteractEvent(entity.getBukkitEntity(), level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()));
++ level.getCraftServer().getPluginManager().callEvent((EntityInteractEvent) cancellable);
++ }
++
++ // We only want to block turning the plate on if all events are cancelled
++ if (!cancellable.isCancelled()) {
++ i++;
++ }
++ }
++
++ i = Math.min(i, this.maxWeight);
++ // CraftBukkit end
++
+ if (i > 0) {
+ float f = (float) Math.min(this.maxWeight, i) / (float) this.maxWeight;
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/WitherRoseBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/WitherRoseBlock.java.patch
new file mode 100644
index 0000000000..894b9d58f3
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/WitherRoseBlock.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/world/level/block/WitherRoseBlock.java
++++ b/net/minecraft/world/level/block/WitherRoseBlock.java
+@@ -69,8 +65,8 @@
+ if (entity instanceof LivingEntity) {
+ LivingEntity livingentity = (LivingEntity) entity;
+
+- if (!livingentity.isInvulnerableTo(level.damageSources().wither())) {
+- livingentity.addEffect(new MobEffectInstance(MobEffects.WITHER, 40));
++ if (!entityliving.isInvulnerableTo(level.damageSources().wither())) {
++ entityliving.addEffect(new MobEffectInstance(MobEffects.WITHER, 40), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.WITHER_ROSE); // CraftBukkit
+ }
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/WitherSkullBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/WitherSkullBlock.java.patch
new file mode 100644
index 0000000000..f646cfcc54
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/WitherSkullBlock.java.patch
@@ -0,0 +1,62 @@
+--- a/net/minecraft/world/level/block/WitherSkullBlock.java
++++ b/net/minecraft/world/level/block/WitherSkullBlock.java
+@@ -25,6 +25,10 @@
+ import net.minecraft.world.level.block.state.pattern.BlockPatternBuilder;
+ import net.minecraft.world.level.block.state.predicate.BlockStatePredicate;
+
++// CraftBukkit start
++import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
++// CraftBukkit end
++
+ public class WitherSkullBlock extends SkullBlock {
+
+ public static final MapCodec<WitherSkullBlock> CODEC = simpleCodec(WitherSkullBlock::new);
+@@ -55,7 +57,8 @@
+
+ }
+
+- public static void checkSpawn(Level level, BlockPos blockpos, SkullBlockEntity skullblockentity) {
++ public static void checkSpawn(Level level, BlockPos pos, SkullBlockEntity blockEntity) {
++ if (level.captureBlockStates) return; // CraftBukkit
+ if (!level.isClientSide) {
+ BlockState blockstate = skullblockentity.getBlockState();
+ boolean flag = blockstate.is(Blocks.WITHER_SKELETON_SKULL) || blockstate.is(Blocks.WITHER_SKELETON_WALL_SKULL);
+@@ -66,14 +69,20 @@
+ if (blockpattern_blockpatternmatch != null) {
+ WitherBoss witherboss = (WitherBoss) EntityType.WITHER.create(level);
+
+- if (witherboss != null) {
+- CarvedPumpkinBlock.clearPatternBlocks(level, blockpattern_blockpatternmatch);
+- BlockPos blockpos1 = blockpattern_blockpatternmatch.getBlock(1, 2, 0).getPos();
++ if (entitywither != null) {
++ // BlockPumpkinCarved.clearPatternBlocks(world, shapedetector_shapedetectorcollection); // CraftBukkit - move down
++ BlockPos blockposition1 = shapedetector_shapedetectorcollection.getBlock(1, 2, 0).getPos();
+
+- witherboss.moveTo((double) blockpos1.getX() + 0.5D, (double) blockpos1.getY() + 0.55D, (double) blockpos1.getZ() + 0.5D, blockpattern_blockpatternmatch.getForwards().getAxis() == Direction.Axis.X ? 0.0F : 90.0F, 0.0F);
+- witherboss.yBodyRot = blockpattern_blockpatternmatch.getForwards().getAxis() == Direction.Axis.X ? 0.0F : 90.0F;
+- witherboss.makeInvulnerable();
+- Iterator iterator = level.getEntitiesOfClass(ServerPlayer.class, witherboss.getBoundingBox().inflate(50.0D)).iterator();
++ entitywither.moveTo((double) blockposition1.getX() + 0.5D, (double) blockposition1.getY() + 0.55D, (double) blockposition1.getZ() + 0.5D, shapedetector_shapedetectorcollection.getForwards().getAxis() == Direction.Axis.X ? 0.0F : 90.0F, 0.0F);
++ entitywither.yBodyRot = shapedetector_shapedetectorcollection.getForwards().getAxis() == Direction.Axis.X ? 0.0F : 90.0F;
++ entitywither.makeInvulnerable();
++ // CraftBukkit start
++ if (!level.addFreshEntity(entitywither, SpawnReason.BUILD_WITHER)) {
++ return;
++ }
++ CarvedPumpkinBlock.clearPatternBlocks(level, shapedetector_shapedetectorcollection); // CraftBukkit - from above
++ // CraftBukkit end
++ Iterator iterator = level.getEntitiesOfClass(ServerPlayer.class, entitywither.getBoundingBox().inflate(50.0D)).iterator();
+
+ while (iterator.hasNext()) {
+ ServerPlayer serverplayer = (ServerPlayer) iterator.next();
+@@ -81,8 +90,8 @@
+ CriteriaTriggers.SUMMONED_ENTITY.trigger(serverplayer, (Entity) witherboss);
+ }
+
+- level.addFreshEntity(witherboss);
+- CarvedPumpkinBlock.updatePatternBlocks(level, blockpattern_blockpatternmatch);
++ // world.addFreshEntity(entitywither); // CraftBukkit - moved up
++ CarvedPumpkinBlock.updatePatternBlocks(level, shapedetector_shapedetectorcollection);
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch
new file mode 100644
index 0000000000..50074c84ea
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch
@@ -0,0 +1,244 @@
+--- a/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
+@@ -46,6 +45,19 @@
+ import net.minecraft.world.level.block.Blocks;
+ import net.minecraft.world.level.block.state.BlockState;
+ import net.minecraft.world.phys.Vec3;
++// CraftBukkit start
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.entity.HumanEntity;
++import org.bukkit.entity.Player;
++import org.bukkit.event.block.BlockExpEvent;
++import org.bukkit.event.inventory.FurnaceBurnEvent;
++import org.bukkit.event.inventory.FurnaceExtractEvent;
++import org.bukkit.event.inventory.FurnaceSmeltEvent;
++import org.bukkit.event.inventory.FurnaceStartSmeltEvent;
++import org.bukkit.inventory.CookingRecipe;
++// CraftBukkit end
+
+ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntity implements WorldlyContainer, RecipeCraftingHolder, StackedContentsCompatible {
+
+@@ -186,6 +195,40 @@
+ return map;
+ }
+
++ // CraftBukkit start - add fields and methods
++ private int maxStack = MAX_STACK;
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++
++ public List<ItemStack> getContents() {
++ return this.items;
++ }
++
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ @Override
++ public int getMaxStackSize() {
++ return maxStack;
++ }
++
++ public void setMaxStackSize(int size) {
++ maxStack = size;
++ }
++
++ public Object2IntOpenHashMap<ResourceLocation> getRecipesUsed() {
++ return this.recipesUsed; // PAIL private -> public
++ }
++ // CraftBukkit end
++
+ private static boolean isNeverAFurnaceFuel(Item item) {
+ return item.builtInRegistryHolder().is(ItemTags.NON_FLAMMABLE_WOOD);
+ }
+@@ -283,10 +324,21 @@
+
+ int i = abstractfurnaceblockentity.getMaxStackSize();
+
+- if (!abstractfurnaceblockentity.isLit() && canBurn(level.registryAccess(), recipeholder, abstractfurnaceblockentity.items, i)) {
+- abstractfurnaceblockentity.litTime = abstractfurnaceblockentity.getBurnDuration(itemstack);
+- abstractfurnaceblockentity.litDuration = abstractfurnaceblockentity.litTime;
+- if (abstractfurnaceblockentity.isLit()) {
++ if (!blockEntity.isLit() && canBurn(level.registryAccess(), recipeholder, blockEntity.items, i)) {
++ // CraftBukkit start
++ CraftItemStack fuel = CraftItemStack.asCraftMirror(itemstack);
++
++ FurnaceBurnEvent furnaceBurnEvent = new FurnaceBurnEvent(CraftBlock.at(level, pos), fuel, blockEntity.getBurnDuration(itemstack));
++ level.getCraftServer().getPluginManager().callEvent(furnaceBurnEvent);
++
++ if (furnaceBurnEvent.isCancelled()) {
++ return;
++ }
++
++ blockEntity.litTime = furnaceBurnEvent.getBurnTime();
++ blockEntity.litDuration = blockEntity.litTime;
++ if (blockEntity.isLit() && furnaceBurnEvent.isBurning()) {
++ // CraftBukkit end
+ flag1 = true;
+ if (flag3) {
+ Item item = itemstack.getItem();
+@@ -301,13 +353,25 @@
+ }
+ }
+
+- if (abstractfurnaceblockentity.isLit() && canBurn(level.registryAccess(), recipeholder, abstractfurnaceblockentity.items, i)) {
+- ++abstractfurnaceblockentity.cookingProgress;
+- if (abstractfurnaceblockentity.cookingProgress == abstractfurnaceblockentity.cookingTotalTime) {
+- abstractfurnaceblockentity.cookingProgress = 0;
+- abstractfurnaceblockentity.cookingTotalTime = getTotalCookTime(level, abstractfurnaceblockentity);
+- if (burn(level.registryAccess(), recipeholder, abstractfurnaceblockentity.items, i)) {
+- abstractfurnaceblockentity.setRecipeUsed(recipeholder);
++ if (blockEntity.isLit() && canBurn(level.registryAccess(), recipeholder, blockEntity.items, i)) {
++ // CraftBukkit start
++ if (recipeholder != null && blockEntity.cookingProgress == 0) {
++ CraftItemStack source = CraftItemStack.asCraftMirror(blockEntity.items.get(0));
++ CookingRecipe<?> recipe = (CookingRecipe<?>) recipeholder.toBukkitRecipe();
++
++ FurnaceStartSmeltEvent event = new FurnaceStartSmeltEvent(CraftBlock.at(level, pos), source, recipe);
++ level.getCraftServer().getPluginManager().callEvent(event);
++
++ blockEntity.cookingTotalTime = event.getTotalCookTime();
++ }
++ // CraftBukkit end
++
++ ++blockEntity.cookingProgress;
++ if (blockEntity.cookingProgress == blockEntity.cookingTotalTime) {
++ blockEntity.cookingProgress = 0;
++ blockEntity.cookingTotalTime = getTotalCookTime(level, blockEntity);
++ if (burn(blockEntity.level, blockEntity.worldPosition, level.registryAccess(), recipeholder, blockEntity.items, i)) { // CraftBukkit
++ blockEntity.setRecipeUsed(recipeholder);
+ }
+
+ flag1 = true;
+@@ -345,17 +409,44 @@
+ }
+ }
+
+- private static boolean burn(RegistryAccess registryaccess, @Nullable RecipeHolder<?> recipeholder, NonNullList<ItemStack> nonnulllist, int i) {
+- if (recipeholder != null && canBurn(registryaccess, recipeholder, nonnulllist, i)) {
++ private static boolean burn(Level world, BlockPos blockposition, RegistryAccess iregistrycustom, @Nullable RecipeHolder<?> recipeholder, NonNullList<ItemStack> nonnulllist, int i) { // CraftBukkit
++ if (recipeholder != null && canBurn(iregistrycustom, recipeholder, nonnulllist, i)) {
+ ItemStack itemstack = (ItemStack) nonnulllist.get(0);
+ ItemStack itemstack1 = recipeholder.value().getResultItem(registryaccess);
+ ItemStack itemstack2 = (ItemStack) nonnulllist.get(2);
+
++ // CraftBukkit start - fire FurnaceSmeltEvent
++ CraftItemStack source = CraftItemStack.asCraftMirror(itemstack);
++ org.bukkit.inventory.ItemStack result = CraftItemStack.asBukkitCopy(itemstack1);
++
++ FurnaceSmeltEvent furnaceSmeltEvent = new FurnaceSmeltEvent(CraftBlock.at(world, blockposition), source, result);
++ world.getCraftServer().getPluginManager().callEvent(furnaceSmeltEvent);
++
++ if (furnaceSmeltEvent.isCancelled()) {
++ return false;
++ }
++
++ result = furnaceSmeltEvent.getResult();
++ itemstack1 = CraftItemStack.asNMSCopy(result);
++
++ if (!itemstack1.isEmpty()) {
++ if (itemstack2.isEmpty()) {
++ nonnulllist.set(2, itemstack1.copy());
++ } else if (CraftItemStack.asCraftMirror(itemstack2).isSimilar(result)) {
++ itemstack2.grow(itemstack1.getCount());
++ } else {
++ return false;
++ }
++ }
++
++ /*
+ if (itemstack2.isEmpty()) {
+ nonnulllist.set(2, itemstack1.copy());
+ } else if (itemstack2.is(itemstack1.getItem())) {
+ itemstack2.grow(1);
+ }
++ */
++ // CraftBukkit end
+
+ if (itemstack.is(Blocks.WET_SPONGE.asItem()) && !((ItemStack) nonnulllist.get(1)).isEmpty() && ((ItemStack) nonnulllist.get(1)).is(Items.BUCKET)) {
+ nonnulllist.set(1, new ItemStack(Items.WATER_BUCKET));
+@@ -378,8 +469,9 @@
+ }
+ }
+
+- private static int getTotalCookTime(Level level, AbstractFurnaceBlockEntity abstractfurnaceblockentity) {
+- return (Integer) abstractfurnaceblockentity.quickCheck.getRecipeFor(abstractfurnaceblockentity, level).map((recipeholder) -> {
++ private static int getTotalCookTime(Level level, AbstractFurnaceBlockEntity blockEntity) {
++ if (level == null) return 200; // CraftBukkit - SPIGOT-4302
++ return (Integer) blockEntity.quickCheck.getRecipeFor(blockEntity, level).map((recipeholder) -> {
+ return ((AbstractCookingRecipe) recipeholder.value()).getCookingTime();
+ }).orElse(200);
+ }
+@@ -515,8 +592,8 @@
+ @Override
+ public void awardUsedRecipes(Player player, List<ItemStack> list) {}
+
+- public void awardUsedRecipesAndPopExperience(ServerPlayer serverplayer) {
+- List<RecipeHolder<?>> list = this.getRecipesToAwardAndPopExperience(serverplayer.serverLevel(), serverplayer.position());
++ public void awardUsedRecipesAndPopExperience(ServerPlayer entityplayer, ItemStack itemstack, int amount) { // CraftBukkit
++ List<RecipeHolder<?>> list = this.getRecipesToAwardAndPopExperience(entityplayer.serverLevel(), entityplayer.position(), this.worldPosition, entityplayer, itemstack, amount); // CraftBukkit
+
+ serverplayer.awardRecipes(list);
+ Iterator iterator = list.iterator();
+@@ -532,7 +609,13 @@
+ this.recipesUsed.clear();
+ }
+
+- public List<RecipeHolder<?>> getRecipesToAwardAndPopExperience(ServerLevel serverlevel, Vec3 vec3) {
++ public List<RecipeHolder<?>> getRecipesToAwardAndPopExperience(ServerLevel level, Vec3 popVec) {
++ // CraftBukkit start
++ return this.getRecipesToAwardAndPopExperience(level, popVec, this.worldPosition, null, null, 0);
++ }
++
++ public List<RecipeHolder<?>> getRecipesToAwardAndPopExperience(ServerLevel worldserver, Vec3 vec3d, BlockPos blockposition, ServerPlayer entityplayer, ItemStack itemstack, int amount) {
++ // CraftBukkit end
+ List<RecipeHolder<?>> list = Lists.newArrayList();
+ ObjectIterator objectiterator = this.recipesUsed.object2IntEntrySet().iterator();
+
+@@ -541,14 +624,14 @@
+
+ serverlevel.getRecipeManager().byKey((ResourceLocation) entry.getKey()).ifPresent((recipeholder) -> {
+ list.add(recipeholder);
+- createExperience(serverlevel, vec3, entry.getIntValue(), ((AbstractCookingRecipe) recipeholder.value()).getExperience());
++ createExperience(worldserver, vec3d, entry.getIntValue(), ((AbstractCookingRecipe) recipeholder.value()).getExperience(), blockposition, entityplayer, itemstack, amount); // CraftBukkit
+ });
+ }
+
+ return list;
+ }
+
+- private static void createExperience(ServerLevel serverlevel, Vec3 vec3, int i, float f) {
++ private static void createExperience(ServerLevel worldserver, Vec3 vec3d, int i, float f, BlockPos blockposition, net.minecraft.world.entity.player.Player entityhuman, ItemStack itemstack, int amount) { // CraftBukkit
+ int j = Mth.floor((float) i * f);
+ float f1 = Mth.frac((float) i * f);
+
+@@ -556,7 +639,18 @@
+ ++j;
+ }
+
+- ExperienceOrb.award(serverlevel, vec3, j);
++ // CraftBukkit start - fire FurnaceExtractEvent / BlockExpEvent
++ BlockExpEvent event;
++ if (amount != 0) {
++ event = new FurnaceExtractEvent((Player) entityhuman.getBukkitEntity(), CraftBlock.at(worldserver, blockposition), org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(itemstack.getItem()), amount, j);
++ } else {
++ event = new BlockExpEvent(CraftBlock.at(worldserver, blockposition), j);
++ }
++ worldserver.getCraftServer().getPluginManager().callEvent(event);
++ j = event.getExpToDrop();
++ // CraftBukkit end
++
++ ExperienceOrb.award(worldserver, vec3d, j);
+ }
+
+ @Override
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BannerBlockEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BannerBlockEntity.java.patch
new file mode 100644
index 0000000000..64cfabdde0
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BannerBlockEntity.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/world/level/block/entity/BannerBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/BannerBlockEntity.java
+@@ -105,7 +101,12 @@
+ this.name = Component.Serializer.fromJson(compoundtag.getString("CustomName"));
+ }
+
+- this.itemPatterns = compoundtag.getList("Patterns", 10);
++ this.itemPatterns = tag.getList("Patterns", 10);
++ // CraftBukkit start
++ while (this.itemPatterns.size() > 20) {
++ this.itemPatterns.remove(20);
++ }
++ // CraftBukkit end
+ this.patterns = null;
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch
new file mode 100644
index 0000000000..e17aee50b9
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch
@@ -0,0 +1,52 @@
+--- a/net/minecraft/world/level/block/entity/BarrelBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/BarrelBlockEntity.java
+@@ -20,8 +20,49 @@
+ import net.minecraft.world.level.block.BarrelBlock;
+ import net.minecraft.world.level.block.state.BlockState;
+
++// CraftBukkit start
++import java.util.ArrayList;
++import java.util.List;
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++// CraftBukkit end
++
+ public class BarrelBlockEntity extends RandomizableContainerBlockEntity {
+
++ // CraftBukkit start - add fields and methods
++ public List<HumanEntity> transaction = new ArrayList<>();
++ private int maxStack = MAX_STACK;
++
++ @Override
++ public List<ItemStack> getContents() {
++ return this.items;
++ }
++
++ @Override
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ @Override
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ @Override
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ @Override
++ public int getMaxStackSize() {
++ return maxStack;
++ }
++
++ @Override
++ public void setMaxStackSize(int i) {
++ maxStack = i;
++ }
++ // CraftBukkit end
+ private NonNullList<ItemStack> items;
+ private final ContainerOpenersCounter openersCounter;
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java.patch
new file mode 100644
index 0000000000..fc78cc82b4
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java.patch
@@ -0,0 +1,15 @@
+--- a/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java
+@@ -95,4 +89,12 @@
+ }
+
+ protected abstract AbstractContainerMenu createMenu(int containerId, Inventory inventory);
++
++ // CraftBukkit start
++ @Override
++ public org.bukkit.Location getLocation() {
++ if (level == null) return null;
++ return new org.bukkit.Location(level.getWorld(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ());
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch
new file mode 100644
index 0000000000..81e9f2bf96
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch
@@ -0,0 +1,80 @@
+--- a/net/minecraft/world/level/block/entity/BeaconBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/BeaconBlockEntity.java
+@@ -39,6 +39,10 @@
+ import net.minecraft.world.level.block.state.BlockState;
+ import net.minecraft.world.level.levelgen.Heightmap;
+ import net.minecraft.world.phys.AABB;
++// CraftBukkit start
++import org.bukkit.craftbukkit.potion.CraftPotionUtil;
++import org.bukkit.potion.PotionEffect;
++// CraftBukkit end
+
+ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Nameable {
+
+@@ -65,7 +69,16 @@
+ private Component name;
+ private LockCode lockKey;
+ private final ContainerData dataAccess;
++ // CraftBukkit start - add fields and methods
++ public PotionEffect getPrimaryEffect() {
++ return (this.primaryPower != null) ? CraftPotionUtil.toBukkit(new MobEffectInstance(this.primaryPower, getLevel(this.levels), getAmplification(levels, primaryPower, secondaryPower), true, true)) : null;
++ }
+
++ public PotionEffect getSecondaryEffect() {
++ return (hasSecondaryEffect(levels, primaryPower, secondaryPower)) ? CraftPotionUtil.toBukkit(new MobEffectInstance(this.secondaryPower, getLevel(this.levels), getAmplification(levels, primaryPower, secondaryPower), true, true)) : null;
++ }
++ // CraftBukkit end
++
+ @Nullable
+ static MobEffect filterEffect(@Nullable MobEffect mobeffect) {
+ return BeaconBlockEntity.VALID_EFFECTS.contains(mobeffect) ? mobeffect : null;
+@@ -249,9 +258,9 @@
+ super.setRemoved();
+ }
+
+- private static void applyEffects(Level level, BlockPos blockpos, int i, @Nullable MobEffect mobeffect, @Nullable MobEffect mobeffect1) {
+- if (!level.isClientSide && mobeffect != null) {
+- double d0 = (double) (i * 10 + 10);
++ // CraftBukkit start - split into components
++ private static byte getAmplification(int i, @Nullable MobEffect mobeffectlist, @Nullable MobEffect mobeffectlist1) {
++ {
+ byte b0 = 0;
+
+ if (i >= 4 && mobeffect == mobeffect1) {
+@@ -285,6 +328,7 @@
+ public static void playSound(Level level, BlockPos blockpos, SoundEvent soundevent) {
+ level.playSound((Player) null, blockpos, soundevent, SoundSource.BLOCKS, 1.0F, 1.0F);
+ }
++ // CraftBukkit end
+
+ public List<BeaconBlockEntity.BeaconBeamSection> getBeamSections() {
+ return (List) (this.levels == 0 ? ImmutableList.of() : this.beamSections);
+@@ -318,20 +364,20 @@
+ if (compoundtag.contains(s, 8)) {
+ ResourceLocation resourcelocation = ResourceLocation.tryParse(compoundtag.getString(s));
+
+- return filterEffect((MobEffect) BuiltInRegistries.MOB_EFFECT.get(resourcelocation));
++ return (MobEffect) BuiltInRegistries.MOB_EFFECT.get(minecraftkey); // CraftBukkit - persist manually set non-default beacon effects (SPIGOT-3598)
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+- @Override
+- public void load(CompoundTag compoundtag) {
+- super.load(compoundtag);
+- this.primaryPower = loadEffect(compoundtag, "primary_effect");
+- this.secondaryPower = loadEffect(compoundtag, "secondary_effect");
+- if (compoundtag.contains("CustomName", 8)) {
+- this.name = Component.Serializer.fromJson(compoundtag.getString("CustomName"));
++ public void load(CompoundTag tag) {
++ super.load(tag);
++ this.primaryPower = loadEffect(tag, "primary_effect");
++ this.secondaryPower = loadEffect(tag, "secondary_effect");
++ this.levels = tag.getInt("Levels"); // CraftBukkit - SPIGOT-5053, use where available
++ if (tag.contains("CustomName", 8)) {
++ this.name = Component.Serializer.fromJson(tag.getString("CustomName"));
+ }
+
+ this.lockKey = LockCode.fromTag(compoundtag);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java.patch
new file mode 100644
index 0000000000..754d608776
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java.patch
@@ -0,0 +1,174 @@
+--- a/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java
+@@ -42,7 +42,8 @@
+ public static final int MIN_OCCUPATION_TICKS_NECTARLESS = 600;
+ private final List<BeehiveBlockEntity.BeeData> stored = Lists.newArrayList();
+ @Nullable
+- private BlockPos savedFlowerPos;
++ public BlockPos savedFlowerPos;
++ public int maxBees = 3; // CraftBukkit - allow setting max amount of bees a hive can hold
+
+ public BeehiveBlockEntity(BlockPos blockpos, BlockState blockstate) {
+ super(BlockEntityType.BEEHIVE, blockpos, blockstate);
+@@ -83,7 +83,7 @@
+ }
+
+ public boolean isFull() {
+- return this.stored.size() == 3;
++ return this.stored.size() == this.maxBees; // CraftBukkit
+ }
+
+ public void emptyAllLivingFromHive(@Nullable Player player, BlockState blockstate, BeehiveBlockEntity.BeeReleaseStatus beehiveblockentity_beereleasestatus) {
+@@ -100,7 +100,7 @@
+
+ if (player.position().distanceToSqr(entity.position()) <= 16.0D) {
+ if (!this.isSedated()) {
+- bee.setTarget(player);
++ entitybee.setTarget(player, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER, true); // CraftBukkit
+ } else {
+ bee.setStayOutOfHiveCountdown(400);
+ }
+@@ -111,11 +111,17 @@
+
+ }
+
+- private List<Entity> releaseAllOccupants(BlockState blockstate, BeehiveBlockEntity.BeeReleaseStatus beehiveblockentity_beereleasestatus) {
++ private List<Entity> releaseAllOccupants(IBlockData state, BeehiveBlockEntity.ReleaseStatus releaseStatus) {
++ // CraftBukkit start - This allows us to bypass the night/rain/emergency check
++ return releaseBees(state, releaseStatus, false);
++ }
++
++ public List<Entity> releaseBees(IBlockData iblockdata, BeehiveBlockEntity.ReleaseStatus tileentitybeehive_releasestatus, boolean force) {
+ List<Entity> list = Lists.newArrayList();
+
+- this.stored.removeIf((beehiveblockentity_beedata) -> {
+- return releaseOccupant(this.level, this.worldPosition, blockstate, beehiveblockentity_beedata, list, beehiveblockentity_beereleasestatus, this.savedFlowerPos);
++ this.stored.removeIf((tileentitybeehive_hivebee) -> {
++ return releaseBee(this.level, this.worldPosition, iblockdata, tileentitybeehive_hivebee, list, tileentitybeehive_releasestatus, this.savedFlowerPos, force);
++ // CraftBukkit end
+ });
+ if (!list.isEmpty()) {
+ super.setChanged();
+@@ -142,11 +148,23 @@
+ return CampfireBlock.isSmokeyPos(this.level, this.getBlockPos());
+ }
+
+- public void addOccupantWithPresetTicks(Entity entity, boolean flag, int i) {
+- if (this.stored.size() < 3) {
+- entity.stopRiding();
+- entity.ejectPassengers();
+- CompoundTag compoundtag = new CompoundTag();
++ public void addOccupantWithPresetTicks(Entity occupant, boolean hasNectar, int ticksInHive) {
++ if (this.stored.size() < this.maxBees) { // CraftBukkit
++ // CraftBukkit start
++ if (this.level != null) {
++ org.bukkit.event.entity.EntityEnterBlockEvent event = new org.bukkit.event.entity.EntityEnterBlockEvent(occupant.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(level, getBlockPos()));
++ org.bukkit.Bukkit.getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ if (occupant instanceof Bee) {
++ ((Bee) occupant).setStayOutOfHiveCountdown(400);
++ }
++ return;
++ }
++ }
++ // CraftBukkit end
++ occupant.stopRiding();
++ occupant.ejectPassengers();
++ CompoundTag nbttagcompound = new CompoundTag();
+
+ entity.save(compoundtag);
+ this.storeBee(compoundtag, i, flag);
+@@ -174,8 +192,14 @@
+ this.stored.add(new BeehiveBlockEntity.BeeData(compoundtag, i, flag ? 2400 : 600));
+ }
+
+- private static boolean releaseOccupant(Level level, BlockPos blockpos, BlockState blockstate, BeehiveBlockEntity.BeeData beehiveblockentity_beedata, @Nullable List<Entity> list, BeehiveBlockEntity.BeeReleaseStatus beehiveblockentity_beereleasestatus, @Nullable BlockPos blockpos1) {
+- if ((level.isNight() || level.isRaining()) && beehiveblockentity_beereleasestatus != BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY) {
++ private static boolean releaseOccupant(Level level, BlockPos pos, IBlockData state, BeehiveBlockEntity.BeeData data, @Nullable List<Entity> storedInHives, BeehiveBlockEntity.ReleaseStatus releaseStatus, @Nullable BlockPos savedFlowerPos) {
++ // CraftBukkit start - This allows us to bypass the night/rain/emergency check
++ return releaseBee(level, pos, state, data, storedInHives, releaseStatus, savedFlowerPos, false);
++ }
++
++ private static boolean releaseBee(Level world, BlockPos blockposition, IBlockData iblockdata, BeehiveBlockEntity.BeeData tileentitybeehive_hivebee, @Nullable List<Entity> list, BeehiveBlockEntity.ReleaseStatus tileentitybeehive_releasestatus, @Nullable BlockPos blockposition1, boolean force) {
++ if (!force && (world.isNight() || world.isRaining()) && tileentitybeehive_releasestatus != BeehiveBlockEntity.ReleaseStatus.EMERGENCY) {
++ // CraftBukkit end
+ return false;
+ } else {
+ CompoundTag compoundtag = beehiveblockentity_beedata.entityData.copy();
+@@ -198,11 +222,19 @@
+ if (!entity.getType().is(EntityTypeTags.BEEHIVE_INHABITORS)) {
+ return false;
+ } else {
++ // CraftBukkit start
+ if (entity instanceof Bee) {
+ Bee bee = (Bee) entity;
+
+- if (blockpos1 != null && !bee.hasSavedFlowerPos() && level.random.nextFloat() < 0.9F) {
+- bee.setSavedFlowerPos(blockpos1);
++ entity.moveTo(d1, d2, d3, entity.getYRot(), entity.getXRot());
++ }
++ if (!world.addFreshEntity(entity, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BEEHIVE)) return false; // CraftBukkit - SpawnReason, moved from below
++ // CraftBukkit end
++ if (entity instanceof Bee) {
++ Bee entitybee = (Bee) entity;
++
++ if (blockposition1 != null && !entitybee.hasSavedFlowerPos() && world.random.nextFloat() < 0.9F) {
++ entitybee.setSavedFlowerPos(blockposition1);
+ }
+
+ if (beehiveblockentity_beereleasestatus == BeehiveBlockEntity.BeeReleaseStatus.HONEY_DELIVERED) {
+@@ -229,6 +265,7 @@
+ list.add(bee);
+ }
+
++ /* // CraftBukkit start
+ float f = entity.getBbWidth();
+ double d0 = flag ? 0.0D : 0.55D + (double) (f / 2.0F);
+ double d1 = (double) blockpos.getX() + 0.5D + d0 * (double) direction.getStepX();
+@@ -236,11 +273,12 @@
+ double d3 = (double) blockpos.getZ() + 0.5D + d0 * (double) direction.getStepZ();
+
+ entity.moveTo(d1, d2, d3, entity.getYRot(), entity.getXRot());
++ */ // CraftBukkit end
+ }
+
+- level.playSound((Player) null, blockpos, SoundEvents.BEEHIVE_EXIT, SoundSource.BLOCKS, 1.0F, 1.0F);
+- level.gameEvent(GameEvent.BLOCK_CHANGE, blockpos, GameEvent.Context.of(entity, level.getBlockState(blockpos)));
+- return level.addFreshEntity(entity);
++ world.playSound((Player) null, blockposition, SoundEvents.BEEHIVE_EXIT, SoundSource.BLOCKS, 1.0F, 1.0F);
++ world.gameEvent(GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of(entity, world.getBlockState(blockposition)));
++ return true; // return this.world.addFreshEntity(entity); // CraftBukkit - moved up
+ }
+ } else {
+ return false;
+@@ -289,6 +327,10 @@
+ if (releaseOccupant(level, blockpos, blockstate, beehiveblockentity_beedata, (List) null, beehiveblockentity_beereleasestatus, blockpos1)) {
+ flag = true;
+ iterator.remove();
++ // CraftBukkit start
++ } else {
++ tileentitybeehive_hivebee.ticksInHive = tileentitybeehive_hivebee.minOccupationTicks / 2; // Not strictly Vanilla behaviour in cases where bees cannot spawn but still reasonable
++ // CraftBukkit end
+ }
+ }
+ }
+@@ -331,6 +372,11 @@
+ this.savedFlowerPos = NbtUtils.readBlockPos(compoundtag.getCompound("FlowerPos"));
+ }
+
++ // CraftBukkit start
++ if (tag.contains("Bukkit.MaxEntities")) {
++ this.maxBees = tag.getInt("Bukkit.MaxEntities");
++ }
++ // CraftBukkit end
+ }
+
+ @Override
+@@ -341,6 +386,7 @@
+ if (this.hasSavedFlowerPos()) {
+ compoundtag.put("FlowerPos", NbtUtils.writeBlockPos(this.savedFlowerPos));
+ }
++ tag.putInt("Bukkit.MaxEntities", this.maxBees); // CraftBukkit
+
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BellBlockEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BellBlockEntity.java.patch
new file mode 100644
index 0000000000..953b49e27d
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BellBlockEntity.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/level/block/entity/BellBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/BellBlockEntity.java
+@@ -144,10 +143,14 @@
+ return true;
+ }
+
+- private static void makeRaidersGlow(Level level, BlockPos blockpos, List<LivingEntity> list) {
+- list.stream().filter((livingentity) -> {
+- return isRaiderWithinRange(blockpos, livingentity);
+- }).forEach(BellBlockEntity::glow);
++ private static void makeRaidersGlow(Level level, BlockPos pos, List<LivingEntity> raiders) {
++ List<org.bukkit.entity.LivingEntity> entities = // CraftBukkit
++ raiders.stream().filter((entityliving) -> {
++ return isRaiderWithinRange(pos, entityliving);
++ }).map((entity) -> (org.bukkit.entity.LivingEntity) entity.getBukkitEntity()).collect(java.util.stream.Collectors.toCollection(java.util.ArrayList::new)); // CraftBukkit
++
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBellResonateEvent(level, pos, entities).forEach(BellBlockEntity::glow);
++ // CraftBukkit end
+ }
+
+ private static void showBellParticles(Level level, BlockPos blockpos, List<LivingEntity> list) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BlockEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BlockEntity.java.patch
new file mode 100644
index 0000000000..b3db9595fd
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BlockEntity.java.patch
@@ -0,0 +1,70 @@
+--- a/net/minecraft/world/level/block/entity/BlockEntity.java
++++ b/net/minecraft/world/level/block/entity/BlockEntity.java
+@@ -15,8 +15,18 @@
+ import net.minecraft.world.level.block.state.BlockState;
+ import org.slf4j.Logger;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
++import org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry;
++import org.bukkit.inventory.InventoryHolder;
++// CraftBukkit end
++
+ public abstract class BlockEntity {
+
++ // CraftBukkit start - data containers
++ private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry();
++ public CraftPersistentDataContainer persistentDataContainer;
++ // CraftBukkit end
+ private static final Logger LOGGER = LogUtils.getLogger();
+ private final BlockEntityType<?> type;
+ @Nullable
+@@ -48,9 +58,16 @@
+ return this.level != null;
+ }
+
+- public void load(CompoundTag compoundtag) {}
++ // CraftBukkit start - read container
++ public void load(CompoundTag tag) {
++ this.persistentDataContainer = new CraftPersistentDataContainer(DATA_TYPE_REGISTRY);
+
+- protected void saveAdditional(CompoundTag compoundtag) {}
++ net.minecraft.nbt.Tag persistentDataTag = tag.get("PublicBukkitValues");
++ if (persistentDataTag instanceof CompoundTag) {
++ this.persistentDataContainer.putAll((CompoundTag) persistentDataTag);
++ }
++ }
++ // CraftBukkit end
+
+ public final CompoundTag saveWithFullMetadata() {
+ CompoundTag compoundtag = this.saveWithoutMetadata();
+@@ -69,8 +88,13 @@
+ public final CompoundTag saveWithoutMetadata() {
+ CompoundTag compoundtag = new CompoundTag();
+
+- this.saveAdditional(compoundtag);
+- return compoundtag;
++ this.saveAdditional(nbttagcompound);
++ // CraftBukkit start - store container
++ if (this.persistentDataContainer != null && !this.persistentDataContainer.isEmpty()) {
++ nbttagcompound.put("PublicBukkitValues", this.persistentDataContainer.toTagCompound());
++ }
++ // CraftBukkit end
++ return nbttagcompound;
+ }
+
+ private void saveId(CompoundTag compoundtag) {
+@@ -202,4 +226,13 @@
+ public void setBlockState(BlockState blockstate) {
+ this.blockState = blockstate;
+ }
++
++ // CraftBukkit start - add method
++ public InventoryHolder getOwner() {
++ if (level == null) return null;
++ org.bukkit.block.BlockState state = level.getWorld().getBlockAt(worldPosition.getX(), worldPosition.getY(), worldPosition.getZ()).getState();
++ if (state instanceof InventoryHolder) return (InventoryHolder) state;
++ return null;
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch
new file mode 100644
index 0000000000..62cf910147
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch
@@ -0,0 +1,169 @@
+--- a/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java
+@@ -25,6 +25,20 @@
+ import net.minecraft.world.level.block.BrewingStandBlock;
+ import net.minecraft.world.level.block.state.BlockState;
+
++// CraftBukkit start
++import java.util.ArrayList;
++import java.util.List;
++import net.minecraft.server.MinecraftServer;
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.entity.HumanEntity;
++import org.bukkit.event.block.BrewingStartEvent;
++import org.bukkit.event.inventory.BrewEvent;
++import org.bukkit.event.inventory.BrewingStandFuelEvent;
++import org.bukkit.inventory.InventoryHolder;
++// CraftBukkit end
++
+ public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements WorldlyContainer {
+
+ private static final int INGREDIENT_SLOT = 3;
+@@ -42,9 +56,39 @@
+ private Item ingredient;
+ int fuel;
+ protected final ContainerData dataAccess;
++ // CraftBukkit start - add fields and methods
++ private int lastTick = MinecraftServer.currentTick;
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++ private int maxStack = 64;
+
+- public BrewingStandBlockEntity(BlockPos blockpos, BlockState blockstate) {
+- super(BlockEntityType.BREWING_STAND, blockpos, blockstate);
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ public List<ItemStack> getContents() {
++ return this.items;
++ }
++
++ @Override
++ public int getMaxStackSize() {
++ return maxStack;
++ }
++
++ public void setMaxStackSize(int size) {
++ maxStack = size;
++ }
++ // CraftBukkit end
++
++ public BrewingStandBlockEntity(BlockPos pos, IBlockData state) {
++ super(BlockEntityType.BREWING_STAND, pos, state);
+ this.items = NonNullList.withSize(5, ItemStack.EMPTY);
+ this.dataAccess = new ContainerData() {
+ @Override
+@@ -114,32 +152,52 @@
+ public static void serverTick(Level level, BlockPos blockpos, BlockState blockstate, BrewingStandBlockEntity brewingstandblockentity) {
+ ItemStack itemstack = (ItemStack) brewingstandblockentity.items.get(4);
+
+- if (brewingstandblockentity.fuel <= 0 && itemstack.is(Items.BLAZE_POWDER)) {
+- brewingstandblockentity.fuel = 20;
+- itemstack.shrink(1);
+- setChanged(level, blockpos, blockstate);
++ if (blockEntity.fuel <= 0 && itemstack.is(Items.BLAZE_POWDER)) {
++ // CraftBukkit start
++ BrewingStandFuelEvent event = new BrewingStandFuelEvent(CraftBlock.at(level, pos), CraftItemStack.asCraftMirror(itemstack), 20);
++ level.getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return;
++ }
++
++ blockEntity.fuel = event.getFuelPower();
++ if (blockEntity.fuel > 0 && event.isConsuming()) {
++ itemstack.shrink(1);
++ }
++ // CraftBukkit end
++ setChanged(level, pos, state);
+ }
+
+ boolean flag = isBrewable(brewingstandblockentity.items);
+ boolean flag1 = brewingstandblockentity.brewTime > 0;
+ ItemStack itemstack1 = (ItemStack) brewingstandblockentity.items.get(3);
+
++ // CraftBukkit start - Use wall time instead of ticks for brewing
++ int elapsedTicks = MinecraftServer.currentTick - blockEntity.lastTick;
++ blockEntity.lastTick = MinecraftServer.currentTick;
++
+ if (flag1) {
+- --brewingstandblockentity.brewTime;
+- boolean flag2 = brewingstandblockentity.brewTime == 0;
++ blockEntity.brewTime -= elapsedTicks;
++ boolean flag2 = blockEntity.brewTime <= 0; // == -> <=
++ // CraftBukkit end
+
+ if (flag2 && flag) {
+- doBrew(level, blockpos, brewingstandblockentity.items);
+- setChanged(level, blockpos, blockstate);
+- } else if (!flag || !itemstack1.is(brewingstandblockentity.ingredient)) {
+- brewingstandblockentity.brewTime = 0;
+- setChanged(level, blockpos, blockstate);
++ doBrew(level, pos, blockEntity.items, blockEntity); // CraftBukkit
++ setChanged(level, pos, state);
++ } else if (!flag || !itemstack1.is(blockEntity.ingredient)) {
++ blockEntity.brewTime = 0;
++ setChanged(level, pos, state);
+ }
+- } else if (flag && brewingstandblockentity.fuel > 0) {
+- --brewingstandblockentity.fuel;
+- brewingstandblockentity.brewTime = 400;
+- brewingstandblockentity.ingredient = itemstack1.getItem();
+- setChanged(level, blockpos, blockstate);
++ } else if (flag && blockEntity.fuel > 0) {
++ --blockEntity.fuel;
++ // CraftBukkit start
++ BrewingStartEvent event = new BrewingStartEvent(CraftBlock.at(level, pos), CraftItemStack.asCraftMirror(itemstack1), 400);
++ level.getCraftServer().getPluginManager().callEvent(event);
++ blockEntity.brewTime = event.getTotalBrewTime(); // 400 -> event.getTotalBrewTime()
++ // CraftBukkit end
++ blockEntity.ingredient = itemstack1.getItem();
++ setChanged(level, pos, state);
+ }
+
+ boolean[] aboolean = brewingstandblockentity.getPotionBits();
+@@ -193,13 +251,33 @@
+ }
+ }
+
+- private static void doBrew(Level level, BlockPos blockpos, NonNullList<ItemStack> nonnulllist) {
++ // CraftBukkit start
++ private static void doBrew(Level world, BlockPos blockposition, NonNullList<ItemStack> nonnulllist, BrewingStandBlockEntity tileentitybrewingstand) {
+ ItemStack itemstack = (ItemStack) nonnulllist.get(3);
+
+ for (int i = 0; i < 3; ++i) {
+ nonnulllist.set(i, PotionBrewing.mix(itemstack, (ItemStack) nonnulllist.get(i)));
+ }
+
++ if (owner != null) {
++ BrewEvent event = new BrewEvent(CraftBlock.at(world, blockposition), (org.bukkit.inventory.BrewerInventory) owner.getInventory(), brewResults, tileentitybrewingstand.fuel);
++ org.bukkit.Bukkit.getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ return;
++ }
++ }
++ // CraftBukkit end
++
++ for (int i = 0; i < 3; ++i) {
++ // CraftBukkit start - validate index in case it is cleared by plugins
++ if (i < brewResults.size()) {
++ nonnulllist.set(i, CraftItemStack.asNMSCopy(brewResults.get(i)));
++ } else {
++ nonnulllist.set(i, ItemStack.EMPTY);
++ }
++ // CraftBukkit end
++ }
++
+ itemstack.shrink(1);
+ if (itemstack.getItem().hasCraftingRemainingItem()) {
+ ItemStack itemstack1 = new ItemStack(itemstack.getItem().getCraftingRemainingItem());
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BrushableBlockEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BrushableBlockEntity.java.patch
new file mode 100644
index 0000000000..a644f202b9
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/BrushableBlockEntity.java.patch
@@ -0,0 +1,44 @@
+--- a/net/minecraft/world/level/block/entity/BrushableBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/BrushableBlockEntity.java
+@@ -28,6 +28,12 @@
+ import net.minecraft.world.phys.Vec3;
+ import org.slf4j.Logger;
+
++// CraftBukkit start
++import java.util.Arrays;
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++// CraftBukkit end
++
+ public class BrushableBlockEntity extends BlockEntity {
+
+ private static final Logger LOGGER = LogUtils.getLogger();
+@@ -152,8 +158,11 @@
+ double d5 = (double) blockpos.getZ() + 0.5D * d1 + d2;
+ ItemEntity itementity = new ItemEntity(this.level, d3, d4, d5, this.item.split(this.level.random.nextInt(21) + 10));
+
+- itementity.setDeltaMovement(Vec3.ZERO);
+- this.level.addFreshEntity(itementity);
++ entityitem.setDeltaMovement(Vec3.ZERO);
++ // CraftBukkit start
++ org.bukkit.block.Block bblock = CraftBlock.at(this.level, this.worldPosition);
++ CraftEventFactory.handleBlockDropItemEvent(bblock, bblock.getState(), (ServerPlayer) player, Arrays.asList(entityitem));
++ // CraftBukkit end
+ this.item = ItemStack.EMPTY;
+ }
+
+@@ -231,10 +238,10 @@
+ }
+
+ @Override
+- @Override
+- public void load(CompoundTag compoundtag) {
+- if (!this.tryLoadLootTable(compoundtag) && compoundtag.contains("item")) {
+- this.item = ItemStack.of(compoundtag.getCompound("item"));
++ public void load(CompoundTag tag) {
++ super.load(tag); // CraftBukkit - SPIGOT-7393: Load super Bukkit data
++ if (!this.tryLoadLootTable(tag) && tag.contains("item")) {
++ this.item = ItemStack.of(tag.getCompound("item"));
+ }
+
+ if (compoundtag.contains("hit_direction")) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/CampfireBlockEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/CampfireBlockEntity.java.patch
new file mode 100644
index 0000000000..e9805e979c
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/CampfireBlockEntity.java.patch
@@ -0,0 +1,59 @@
+--- a/net/minecraft/world/level/block/entity/CampfireBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/CampfireBlockEntity.java
+@@ -26,6 +26,14 @@
+ import net.minecraft.world.level.block.state.BlockState;
+ import net.minecraft.world.level.gameevent.GameEvent;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.block.BlockCookEvent;
++import org.bukkit.event.block.CampfireStartEvent;
++import org.bukkit.inventory.CampfireRecipe;
++// CraftBukkit end
++
+ public class CampfireBlockEntity extends BlockEntity implements Clearable {
+
+ private static final int BURN_COOL_SPEED = 2;
+@@ -60,10 +68,24 @@
+ }).orElse(itemstack);
+
+ if (itemstack1.isItemEnabled(level.enabledFeatures())) {
+- Containers.dropItemStack(level, (double) blockpos.getX(), (double) blockpos.getY(), (double) blockpos.getZ(), itemstack1);
+- campfireblockentity.items.set(i, ItemStack.EMPTY);
+- level.sendBlockUpdated(blockpos, blockstate, blockstate, 3);
+- level.gameEvent(GameEvent.BLOCK_CHANGE, blockpos, GameEvent.Context.of(blockstate));
++ // CraftBukkit start - fire BlockCookEvent
++ CraftItemStack source = CraftItemStack.asCraftMirror(itemstack);
++ org.bukkit.inventory.ItemStack result = CraftItemStack.asBukkitCopy(itemstack1);
++
++ BlockCookEvent blockCookEvent = new BlockCookEvent(CraftBlock.at(level, pos), source, result);
++ level.getCraftServer().getPluginManager().callEvent(blockCookEvent);
++
++ if (blockCookEvent.isCancelled()) {
++ return;
++ }
++
++ result = blockCookEvent.getResult();
++ itemstack1 = CraftItemStack.asNMSCopy(result);
++ // CraftBukkit end
++ Containers.dropItemStack(level, (double) pos.getX(), (double) pos.getY(), (double) pos.getZ(), itemstack1);
++ blockEntity.items.set(i, ItemStack.EMPTY);
++ level.sendBlockUpdated(pos, state, state, 3);
++ level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(state));
+ }
+ }
+ }
+@@ -176,7 +194,11 @@
+ ItemStack itemstack1 = (ItemStack) this.items.get(j);
+
+ if (itemstack1.isEmpty()) {
+- this.cookingTime[j] = i;
++ // CraftBukkit start
++ CampfireStartEvent event = new CampfireStartEvent(CraftBlock.at(this.level,this.worldPosition), CraftItemStack.asCraftMirror(stack), (CampfireRecipe) getCookableRecipe(stack).get().toBukkitRecipe());
++ this.level.getCraftServer().getPluginManager().callEvent(event);
++ this.cookingTime[j] = event.getTotalCookTime(); // i -> event.getTotalCookTime()
++ // CraftBukkit end
+ this.cookingProgress[j] = 0;
+ this.items.set(j, itemstack.split(1));
+ this.level.gameEvent(GameEvent.BLOCK_CHANGE, this.getBlockPos(), GameEvent.Context.of(entity, this.getBlockState()));
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/ChestBlockEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/ChestBlockEntity.java.patch
new file mode 100644
index 0000000000..d46de1b7ad
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/ChestBlockEntity.java.patch
@@ -0,0 +1,67 @@
+--- a/net/minecraft/world/level/block/entity/ChestBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/ChestBlockEntity.java
+@@ -22,6 +22,11 @@
+ import net.minecraft.world.level.block.ChestBlock;
+ import net.minecraft.world.level.block.state.BlockState;
+ import net.minecraft.world.level.block.state.properties.ChestType;
++// CraftBukkit start
++import java.util.List;
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++// CraftBukkit end
+
+ public class ChestBlockEntity extends RandomizableContainerBlockEntity implements LidBlockEntity {
+
+@@ -30,8 +35,38 @@
+ private final ContainerOpenersCounter openersCounter;
+ private final ChestLidController chestLidController;
+
+- protected ChestBlockEntity(BlockEntityType<?> blockentitytype, BlockPos blockpos, BlockState blockstate) {
+- super(blockentitytype, blockpos, blockstate);
++ // CraftBukkit start - add fields and methods
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++ private int maxStack = MAX_STACK;
++
++ public List<ItemStack> getContents() {
++ return this.items;
++ }
++
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ @Override
++ public int getMaxStackSize() {
++ return maxStack;
++ }
++
++ public void setMaxStackSize(int size) {
++ maxStack = size;
++ }
++ // CraftBukkit end
++
++ protected ChestBlockEntity(BlockEntityType<?> type, BlockPos pos, IBlockData blockState) {
++ super(type, pos, blockState);
+ this.items = NonNullList.withSize(27, ItemStack.EMPTY);
+ this.openersCounter = new ContainerOpenersCounter() {
+ @Override
+@@ -213,4 +233,11 @@
+
+ level.blockEvent(blockpos, block, 1, j);
+ }
++
++ // CraftBukkit start
++ @Override
++ public boolean onlyOpCanSetNbt() {
++ return true;
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch
new file mode 100644
index 0000000000..7478505f4f
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch
@@ -0,0 +1,112 @@
+--- a/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java
+@@ -18,15 +18,57 @@
+ import net.minecraft.world.level.gameevent.GameEvent;
+ import org.slf4j.Logger;
+
++// CraftBukkit start
++import java.util.List;
++import org.bukkit.Location;
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++// CraftBukkit end
++
+ public class ChiseledBookShelfBlockEntity extends BlockEntity implements Container {
+
+ public static final int MAX_BOOKS_IN_STORAGE = 6;
+ private static final Logger LOGGER = LogUtils.getLogger();
+ private final NonNullList<ItemStack> items;
+- private int lastInteractedSlot;
++ public int lastInteractedSlot;
++ // CraftBukkit start - add fields and methods
++ public List<HumanEntity> transaction = new java.util.ArrayList<>();
++ private int maxStack = 1;
+
+- public ChiseledBookShelfBlockEntity(BlockPos blockpos, BlockState blockstate) {
+- super(BlockEntityType.CHISELED_BOOKSHELF, blockpos, blockstate);
++ @Override
++ public List<ItemStack> getContents() {
++ return this.items;
++ }
++
++ @Override
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ @Override
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ @Override
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ @Override
++ public void setMaxStackSize(int size) {
++ maxStack = size;
++ }
++
++ @Override
++ public Location getLocation() {
++ if (level == null) return null;
++ return new org.bukkit.Location(level.getWorld(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ());
++ }
++ // CraftBukkit end
++
++ public ChiseledBookShelfBlockEntity(BlockPos pos, IBlockData state) {
++ super(BlockEntityType.CHISELED_BOOKSHELF, pos, state);
+ this.items = NonNullList.withSize(6, ItemStack.EMPTY);
+ this.lastInteractedSlot = -1;
+ }
+@@ -51,8 +93,8 @@
+ }
+
+ @Override
+- @Override
+- public void load(CompoundTag compoundtag) {
++ public void load(CompoundTag tag) {
++ super.load(tag); // CraftBukkit - SPIGOT-7393: Load super Bukkit data
+ this.items.clear();
+ ContainerHelper.loadAllItems(compoundtag, this.items);
+ this.lastInteractedSlot = compoundtag.getInt("last_interacted_slot");
+@@ -100,7 +136,7 @@
+
+ this.items.set(i, ItemStack.EMPTY);
+ if (!itemstack.isEmpty()) {
+- this.updateState(i);
++ if (level != null) this.updateState(slot); // CraftBukkit - SPIGOT-7381: check for null world
+ }
+
+ return itemstack;
+@@ -113,13 +148,12 @@
+ }
+
+ @Override
+- @Override
+- public void setItem(int i, ItemStack itemstack) {
+- if (itemstack.is(ItemTags.BOOKSHELF_BOOKS)) {
+- this.items.set(i, itemstack);
+- this.updateState(i);
+- } else if (itemstack.isEmpty()) {
+- this.removeItem(i, 1);
++ public void setItem(int slot, ItemStack stack) {
++ if (stack.is(ItemTags.BOOKSHELF_BOOKS)) {
++ this.items.set(slot, stack);
++ if (level != null) this.updateState(slot); // CraftBukkit - SPIGOT-7381: check for null world
++ } else if (stack.isEmpty()) {
++ this.removeItem(slot, 1);
+ }
+
+ }
+@@ -135,7 +167,7 @@
+ @Override
+ @Override
+ public int getMaxStackSize() {
+- return 1;
++ return maxStack; // CraftBukkit
+ }
+
+ @Override
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/CommandBlockEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/CommandBlockEntity.java.patch
new file mode 100644
index 0000000000..0995672902
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/CommandBlockEntity.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/world/level/block/entity/CommandBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/CommandBlockEntity.java
+@@ -20,7 +20,13 @@
+ private boolean auto;
+ private boolean conditionMet;
+ private final BaseCommandBlock commandBlock = new BaseCommandBlock() {
++ // CraftBukkit start
+ @Override
++ public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) {
++ return new org.bukkit.craftbukkit.command.CraftBlockCommandSender(wrapper, CommandBlockEntity.this);
++ }
++ // CraftBukkit end
++
+ @Override
+ public void setCommand(String s) {
+ super.setCommand(s);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch
new file mode 100644
index 0000000000..5d79703bb1
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch
@@ -0,0 +1,42 @@
+--- a/net/minecraft/world/level/block/entity/ConduitBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/ConduitBlockEntity.java
+@@ -26,6 +26,10 @@
+ import net.minecraft.world.level.block.state.BlockState;
+ import net.minecraft.world.phys.AABB;
+ import net.minecraft.world.phys.Vec3;
++// CraftBukkit start
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++// CraftBukkit end
+
+ public class ConduitBlockEntity extends BlockEntity {
+
+@@ -204,8 +204,8 @@
+ while (iterator.hasNext()) {
+ Player player = (Player) iterator.next();
+
+- if (blockpos.closerThan(player.blockPosition(), (double) j) && player.isInWaterOrRain()) {
+- player.addEffect(new MobEffectInstance(MobEffects.CONDUIT_POWER, 260, 0, true, true));
++ if (pos.closerThan(entityhuman.blockPosition(), (double) j) && entityhuman.isInWaterOrRain()) {
++ entityhuman.addEffect(new MobEffectInstance(MobEffects.CONDUIT_POWER, 260, 0, true, true), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONDUIT); // CraftBukkit
+ }
+ }
+
+@@ -233,9 +233,14 @@
+ conduitblockentity.destroyTarget = null;
+ }
+
+- if (conduitblockentity.destroyTarget != null) {
+- level.playSound((Player) null, conduitblockentity.destroyTarget.getX(), conduitblockentity.destroyTarget.getY(), conduitblockentity.destroyTarget.getZ(), SoundEvents.CONDUIT_ATTACK_TARGET, SoundSource.BLOCKS, 1.0F, 1.0F);
+- conduitblockentity.destroyTarget.hurt(level.damageSources().magic(), 4.0F);
++ if (blockEntity.destroyTarget != null) {
++ // CraftBukkit start
++ CraftEventFactory.blockDamage = CraftBlock.at(level, pos);
++ if (blockEntity.destroyTarget.hurt(level.damageSources().magic(), 4.0F)) {
++ level.playSound((Player) null, blockEntity.destroyTarget.getX(), blockEntity.destroyTarget.getY(), blockEntity.destroyTarget.getZ(), SoundEvents.CONDUIT_ATTACK_TARGET, SoundSource.BLOCKS, 1.0F, 1.0F);
++ }
++ CraftEventFactory.blockDamage = null;
++ // CraftBukkit end
+ }
+
+ if (livingentity != conduitblockentity.destroyTarget) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch
new file mode 100644
index 0000000000..7c5c00e021
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch
@@ -0,0 +1,82 @@
+--- a/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java
++++ b/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java
+@@ -13,6 +13,7 @@
+
+ private static final int CHECK_TICK_DELAY = 5;
+ private int openCount;
++ public boolean opened; // CraftBukkit
+
+ public ContainerOpenersCounter() {}
+
+@@ -22,11 +23,36 @@
+
+ protected abstract void openerCountChanged(Level level, BlockPos pos, BlockState state, int count, int openCount);
+
++ // CraftBukkit start
++ public void onAPIOpen(Level world, BlockPos blockposition, IBlockData iblockdata) {
++ onOpen(world, blockposition, iblockdata);
++ }
++
++ public void onAPIClose(Level world, BlockPos blockposition, IBlockData iblockdata) {
++ onClose(world, blockposition, iblockdata);
++ }
++
++ public void openerAPICountChanged(Level world, BlockPos blockposition, IBlockData iblockdata, int i, int j) {
++ openerCountChanged(world, blockposition, iblockdata, i, j);
++ }
++ // CraftBukkit end
++
+ protected abstract boolean isOwnContainer(Player player);
+
+- public void incrementOpeners(Player player, Level level, BlockPos blockpos, BlockState blockstate) {
++ public void incrementOpeners(Player player, Level level, BlockPos pos, IBlockData state) {
++ int oldPower = Math.max(0, Math.min(15, this.openCount)); // CraftBukkit - Get power before new viewer is added
+ int i = this.openCount++;
+
++ // CraftBukkit start - Call redstone event
++ if (level.getBlockState(pos).is(net.minecraft.world.level.block.Blocks.TRAPPED_CHEST)) {
++ int newPower = Math.max(0, Math.min(15, this.openCount));
++
++ if (oldPower != newPower) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(level, pos, oldPower, newPower);
++ }
++ }
++ // CraftBukkit end
++
+ if (i == 0) {
+ this.onOpen(level, blockpos, blockstate);
+ level.gameEvent((Entity) player, GameEvent.CONTAINER_OPEN, blockpos);
+@@ -36,9 +62,20 @@
+ this.openerCountChanged(level, blockpos, blockstate, i, this.openCount);
+ }
+
+- public void decrementOpeners(Player player, Level level, BlockPos blockpos, BlockState blockstate) {
++ public void decrementOpeners(Player player, Level level, BlockPos pos, IBlockData state) {
++ int oldPower = Math.max(0, Math.min(15, this.openCount)); // CraftBukkit - Get power before new viewer is added
+ int i = this.openCount--;
+
++ // CraftBukkit start - Call redstone event
++ if (level.getBlockState(pos).is(net.minecraft.world.level.block.Blocks.TRAPPED_CHEST)) {
++ int newPower = Math.max(0, Math.min(15, this.openCount));
++
++ if (oldPower != newPower) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.callRedstoneChange(level, pos, oldPower, newPower);
++ }
++ }
++ // CraftBukkit end
++
+ if (this.openCount == 0) {
+ this.onClose(level, blockpos, blockstate);
+ level.gameEvent((Entity) player, GameEvent.CONTAINER_CLOSE, blockpos);
+@@ -57,8 +94,9 @@
+ return level.getEntities(EntityTypeTest.forClass(Player.class), aabb, this::isOwnContainer).size();
+ }
+
+- public void recheckOpeners(Level level, BlockPos blockpos, BlockState blockstate) {
+- int i = this.getOpenCount(level, blockpos);
++ public void recheckOpeners(Level level, BlockPos pos, IBlockData state) {
++ int i = this.getOpenCount(level, pos);
++ if (opened) i++; // CraftBukkit - add dummy count from API
+ int j = this.openCount;
+
+ if (j != i) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/CrafterBlockEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/CrafterBlockEntity.java.patch
new file mode 100644
index 0000000000..1a7e685c3a
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/CrafterBlockEntity.java.patch
@@ -0,0 +1,67 @@
+--- a/net/minecraft/world/level/block/entity/CrafterBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/CrafterBlockEntity.java
+@@ -21,7 +21,11 @@
+ import net.minecraft.world.level.block.CrafterBlock;
+ import net.minecraft.world.level.block.state.BlockState;
+
+-public class CrafterBlockEntity extends RandomizableContainerBlockEntity implements CraftingContainer {
++// CraftBukkit start
++import org.bukkit.Location;
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++// CraftBukkit end
+
+ public static final int CONTAINER_WIDTH = 3;
+ public static final int CONTAINER_HEIGHT = 3;
+@@ -33,9 +39,49 @@
+ private NonNullList<ItemStack> items;
+ private int craftingTicksRemaining;
+ protected final ContainerData containerData;
++ // CraftBukkit start - add fields and methods
++ public List<HumanEntity> transaction = new java.util.ArrayList<>();
++ private int maxStack = 1;
+
+- public CrafterBlockEntity(BlockPos blockpos, BlockState blockstate) {
+- super(BlockEntityType.CRAFTER, blockpos, blockstate);
++ @Override
++ public List<ItemStack> getContents() {
++ return this.items;
++ }
++
++ @Override
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ @Override
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ @Override
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ @Override
++ public int getMaxStackSize() {
++ return maxStack;
++ }
++
++ @Override
++ public void setMaxStackSize(int size) {
++ maxStack = size;
++ }
++
++ @Override
++ public Location getLocation() {
++ if (level == null) return null;
++ return new org.bukkit.Location(level.getWorld(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ());
++ }
++ // CraftBukkit end
++
++ public CrafterBlockEntity(BlockPos blockposition, IBlockData iblockdata) {
++ super(BlockEntityType.CRAFTER, blockposition, iblockdata);
+ this.items = NonNullList.withSize(9, ItemStack.EMPTY);
+ this.craftingTicksRemaining = 0;
+ this.containerData = new ContainerData() {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java.patch
new file mode 100644
index 0000000000..2668c3cd48
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java.patch
@@ -0,0 +1,62 @@
+--- a/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java
+@@ -21,8 +21,59 @@
+ import net.minecraft.world.level.block.state.properties.BlockStateProperties;
+ import net.minecraft.world.ticks.ContainerSingleItem;
+
++// CraftBukkit start
++import java.util.ArrayList;
++import java.util.Arrays;
++import java.util.List;
++import org.bukkit.Location;
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.craftbukkit.util.CraftLocation;
++import org.bukkit.entity.HumanEntity;
++// CraftBukkit end
++
+ public class DecoratedPotBlockEntity extends BlockEntity implements RandomizableContainer, ContainerSingleItem {
+
++ // CraftBukkit start - add fields and methods
++ public List<HumanEntity> transaction = new ArrayList<>();
++ private int maxStack = MAX_STACK;
++
++ @Override
++ public List<ItemStack> getContents() {
++ return Arrays.asList(this.item);
++ }
++
++ @Override
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ @Override
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ @Override
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ @Override
++ public int getMaxStackSize() {
++ return maxStack;
++ }
++
++ @Override
++ public void setMaxStackSize(int i) {
++ maxStack = i;
++ }
++
++ @Override
++ public Location getLocation() {
++ if (level == null) return null;
++ return CraftLocation.toBukkit(worldPosition, level.getWorld());
++ }
++ // CraftBukkit end
++
+ public static final String TAG_SHERDS = "sherds";
+ public static final String TAG_ITEM = "item";
+ public static final int EVENT_POT_WOBBLES = 1;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch
new file mode 100644
index 0000000000..56c3c6798f
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch
@@ -0,0 +1,54 @@
+--- a/net/minecraft/world/level/block/entity/DispenserBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/DispenserBlockEntity.java
+@@ -13,13 +13,49 @@
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.level.block.state.BlockState;
+
++// CraftBukkit start
++import java.util.List;
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++// CraftBukkit end
++
+ public class DispenserBlockEntity extends RandomizableContainerBlockEntity {
+
+ public static final int CONTAINER_SIZE = 9;
+ private NonNullList<ItemStack> items;
+
+- protected DispenserBlockEntity(BlockEntityType<?> blockentitytype, BlockPos blockpos, BlockState blockstate) {
+- super(blockentitytype, blockpos, blockstate);
++ // CraftBukkit start - add fields and methods
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++ private int maxStack = MAX_STACK;
++
++ public List<ItemStack> getContents() {
++ return this.items;
++ }
++
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ @Override
++ public int getMaxStackSize() {
++ return maxStack;
++ }
++
++ public void setMaxStackSize(int size) {
++ maxStack = size;
++ }
++ // CraftBukkit end
++
++ protected DispenserBlockEntity(BlockEntityType<?> type, BlockPos pos, IBlockData blockState) {
++ super(type, pos, blockState);
+ this.items = NonNullList.withSize(9, ItemStack.EMPTY);
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/HopperBlockEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/HopperBlockEntity.java.patch
new file mode 100644
index 0000000000..b143c488e7
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/HopperBlockEntity.java.patch
@@ -0,0 +1,211 @@
+--- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java
+@@ -31,6 +31,17 @@
+ import net.minecraft.world.phys.AABB;
+ import net.minecraft.world.phys.shapes.BooleanOp;
+ import net.minecraft.world.phys.shapes.Shapes;
++import org.bukkit.Bukkit;
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.craftbukkit.inventory.CraftInventory;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.entity.HumanEntity;
++import org.bukkit.event.inventory.HopperInventorySearchEvent;
++import org.bukkit.event.inventory.InventoryMoveItemEvent;
++import org.bukkit.event.inventory.InventoryPickupItemEvent;
++import org.bukkit.inventory.Inventory;
++// CraftBukkit end
+
+ public class HopperBlockEntity extends RandomizableContainerBlockEntity implements Hopper {
+
+@@ -40,8 +51,38 @@
+ private int cooldownTime;
+ private long tickedGameTime;
+
+- public HopperBlockEntity(BlockPos blockpos, BlockState blockstate) {
+- super(BlockEntityType.HOPPER, blockpos, blockstate);
++ // CraftBukkit start - add fields and methods
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++ private int maxStack = MAX_STACK;
++
++ public List<ItemStack> getContents() {
++ return this.items;
++ }
++
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ @Override
++ public int getMaxStackSize() {
++ return maxStack;
++ }
++
++ public void setMaxStackSize(int size) {
++ maxStack = size;
++ }
++ // CraftBukkit end
++
++ public HopperBlockEntity(BlockPos pos, IBlockData blockState) {
++ super(BlockEntityType.HOPPER, pos, blockState);
+ this.items = NonNullList.withSize(5, ItemStack.EMPTY);
+ this.cooldownTime = -1;
+ }
+@@ -118,8 +153,8 @@
+ if (!hopperblockentity.isOnCooldown() && (Boolean) blockstate.getValue(HopperBlock.ENABLED)) {
+ boolean flag = false;
+
+- if (!hopperblockentity.isEmpty()) {
+- flag = ejectItems(level, blockpos, blockstate, hopperblockentity);
++ if (!blockEntity.isEmpty()) {
++ flag = ejectItems(level, pos, state, (Container) blockEntity, blockEntity); // CraftBukkit
+ }
+
+ if (!hopperblockentity.inventoryFull()) {
+@@ -153,8 +188,8 @@
+ return false;
+ }
+
+- private static boolean ejectItems(Level level, BlockPos blockpos, BlockState blockstate, Container container) {
+- Container container1 = getAttachedContainer(level, blockpos, blockstate);
++ private static boolean ejectItems(Level world, BlockPos blockposition, IBlockData iblockdata, Container iinventory, HopperBlockEntity hopper) { // CraftBukkit
++ Container iinventory1 = getAttachedContainer(world, blockposition, iblockdata);
+
+ if (container1 == null) {
+ return false;
+@@ -169,6 +204,29 @@
+ ItemStack itemstack = container.getItem(i).copy();
+ ItemStack itemstack1 = addItem(container, container1, container.removeItem(i, 1), direction);
+
++ // CraftBukkit start - Call event when pushing items into other inventories
++ CraftItemStack oitemstack = CraftItemStack.asCraftMirror(iinventory.removeItem(i, 1));
++
++ Inventory destinationInventory;
++ // Have to special case large chests as they work oddly
++ if (iinventory1 instanceof CompoundContainer) {
++ destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory1);
++ } else if (iinventory1.getOwner() != null) {
++ destinationInventory = iinventory1.getOwner().getInventory();
++ } else {
++ destinationInventory = new CraftInventory(iinventory);
++ }
++
++ InventoryMoveItemEvent event = new InventoryMoveItemEvent(iinventory.getOwner().getInventory(), oitemstack.clone(), destinationInventory, true);
++ world.getCraftServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ hopper.setItem(i, itemstack);
++ hopper.setCooldown(8); // Delay hopper checks
++ return false;
++ }
++ ItemStack itemstack1 = addItem(iinventory, iinventory1, CraftItemStack.asNMSCopy(event.getItem()), enumdirection);
++ // CraftBukkit end
++
+ if (itemstack1.isEmpty()) {
+ container1.setChanged();
+ return true;
+@@ -232,8 +290,35 @@
+
+ if (!itemstack.isEmpty() && canTakeItemFromContainer(hopper, container, itemstack, i, direction)) {
+ ItemStack itemstack1 = itemstack.copy();
+- ItemStack itemstack2 = addItem(container, hopper, container.removeItem(i, 1), (Direction) null);
++ // ItemStack itemstack2 = addItem(iinventory, ihopper, iinventory.removeItem(i, 1), (EnumDirection) null);
++ // CraftBukkit start - Call event on collection of items from inventories into the hopper
++ CraftItemStack oitemstack = CraftItemStack.asCraftMirror(container.removeItem(slot, 1));
+
++ Inventory sourceInventory;
++ // Have to special case large chests as they work oddly
++ if (container instanceof CompoundContainer) {
++ sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) container);
++ } else if (container.getOwner() != null) {
++ sourceInventory = container.getOwner().getInventory();
++ } else {
++ sourceInventory = new CraftInventory(container);
++ }
++
++ InventoryMoveItemEvent event = new InventoryMoveItemEvent(sourceInventory, oitemstack.clone(), hopper.getOwner().getInventory(), false);
++
++ Bukkit.getServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ container.setItem(slot, itemstack1);
++
++ if (hopper instanceof HopperBlockEntity) {
++ ((HopperBlockEntity) hopper).setCooldown(8); // Delay hopper checks
++ }
++
++ return false;
++ }
++ ItemStack itemstack2 = addItem(container, hopper, CraftItemStack.asNMSCopy(event.getItem()), null);
++ // CraftBukkit end
++
+ if (itemstack2.isEmpty()) {
+ container.setChanged();
+ return true;
+@@ -247,7 +332,14 @@
+
+ public static boolean addItem(Container container, ItemEntity itementity) {
+ boolean flag = false;
+- ItemStack itemstack = itementity.getItem().copy();
++ // CraftBukkit start
++ InventoryPickupItemEvent event = new InventoryPickupItemEvent(container.getOwner().getInventory(), (org.bukkit.entity.Item) item.getBukkitEntity());
++ item.level().getCraftServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ return false;
++ }
++ // CraftBukkit end
++ ItemStack itemstack = item.getItem().copy();
+ ItemStack itemstack1 = addItem((Container) null, container, itemstack, (Direction) null);
+
+ if (itemstack1.isEmpty()) {
+@@ -373,16 +465,40 @@
+ return itemstack;
+ }
+
++ // CraftBukkit start
+ @Nullable
+- private static Container getAttachedContainer(Level level, BlockPos blockpos, BlockState blockstate) {
+- Direction direction = (Direction) blockstate.getValue(HopperBlock.FACING);
++ private static Container runHopperInventorySearchEvent(Container inventory, CraftBlock hopper, CraftBlock searchLocation, HopperInventorySearchEvent.ContainerType containerType) {
++ HopperInventorySearchEvent event = new HopperInventorySearchEvent((inventory != null) ? new CraftInventory(inventory) : null, containerType, hopper, searchLocation);
++ Bukkit.getServer().getPluginManager().callEvent(event);
++ CraftInventory craftInventory = (CraftInventory) event.getInventory();
++ return (craftInventory != null) ? craftInventory.getInventory() : null;
++ }
++ // CraftBukkit end
+
+- return getContainerAt(level, blockpos.relative(direction));
++ @Nullable
++ private static Container getAttachedContainer(Level level, BlockPos pos, IBlockData state) {
++ Direction enumdirection = (Direction) state.getValue(HopperBlock.FACING);
++
++ // CraftBukkit start
++ BlockPos searchPosition = pos.relative(enumdirection);
++ Container inventory = getContainerAt(level, pos.relative(enumdirection));
++
++ CraftBlock hopper = CraftBlock.at(level, pos);
++ CraftBlock searchBlock = CraftBlock.at(level, searchPosition);
++ return runHopperInventorySearchEvent(inventory, hopper, searchBlock, HopperInventorySearchEvent.ContainerType.DESTINATION);
++ // CraftBukkit end
+ }
+
+ @Nullable
+ private static Container getSourceContainer(Level level, Hopper hopper) {
+- return getContainerAt(level, hopper.getLevelX(), hopper.getLevelY() + 1.0D, hopper.getLevelZ());
++ // CraftBukkit start
++ Container inventory = getContainerAt(level, hopper.getLevelX(), hopper.getLevelY() + 1.0D, hopper.getLevelZ());
++
++ BlockPos blockPosition = BlockPos.containing(hopper.getLevelX(), hopper.getLevelY(), hopper.getLevelZ());
++ CraftBlock hopper1 = CraftBlock.at(level, blockPosition);
++ CraftBlock container = CraftBlock.at(level, blockPosition.above());
++ return runHopperInventorySearchEvent(inventory, hopper1, container, HopperInventorySearchEvent.ContainerType.SOURCE);
++ // CraftBukkit end
+ }
+
+ public static List<ItemEntity> getItemsAtAndAbove(Level level, Hopper hopper) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch
new file mode 100644
index 0000000000..c146dc3218
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch
@@ -0,0 +1,93 @@
+--- a/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java
+@@ -22,14 +22,26 @@
+ import net.minecraft.world.phys.Vec3;
+ import net.minecraft.world.ticks.ContainerSingleItem;
+
++// CraftBukkit start
++import java.util.Collections;
++import java.util.List;
++import org.bukkit.Location;
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++// CraftBukkit end
++
+ public class JukeboxBlockEntity extends BlockEntity implements Clearable, ContainerSingleItem {
+
+ private static final int SONG_END_PADDING = 20;
+ private ItemStack item;
+ private int ticksSinceLastEvent;
+- private long tickCount;
+- private long recordStartedTick;
+- private boolean isPlaying;
++ public long tickCount;
++ public long recordStartedTick;
++ public boolean isPlaying;
++ // CraftBukkit start - add fields and methods
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++ private int maxStack = MAX_STACK;
++ public boolean opened;
+
+ public JukeboxBlockEntity(BlockPos blockpos, BlockState blockstate) {
+ super(BlockEntityType.JUKEBOX, blockpos, blockstate);
+@@ -51,8 +69,32 @@
+
+ @Override
+ @Override
+- protected void saveAdditional(CompoundTag compoundtag) {
+- super.saveAdditional(compoundtag);
++ public Location getLocation() {
++ if (level == null) return null;
++ return new org.bukkit.Location(level.getWorld(), worldPosition.getX(), worldPosition.getY(), worldPosition.getZ());
++ }
++ // CraftBukkit end
++
++ public JukeboxBlockEntity(BlockPos pos, IBlockData blockState) {
++ super(BlockEntityType.JUKEBOX, pos, blockState);
++ this.item = ItemStack.EMPTY;
++ }
++
++ @Override
++ public void load(CompoundTag tag) {
++ super.load(tag);
++ if (tag.contains("RecordItem", 10)) {
++ this.item = ItemStack.of(tag.getCompound("RecordItem"));
++ }
++
++ this.isPlaying = tag.getBoolean("IsPlaying");
++ this.recordStartedTick = tag.getLong("RecordStartTick");
++ this.tickCount = tag.getLong("TickCount");
++ }
++
++ @Override
++ protected void saveAdditional(CompoundTag tag) {
++ super.saveAdditional(tag);
+ if (!this.getTheItem().isEmpty()) {
+ compoundtag.put("RecordItem", this.getTheItem().save(new CompoundTag()));
+ }
+@@ -156,7 +194,7 @@
+ @Override
+ @Override
+ public int getMaxStackSize() {
+- return 1;
++ return maxStack; // CraftBukkit
+ }
+
+ @Override
+@@ -210,9 +245,13 @@
+ }
+
+ @VisibleForTesting
+- public void setRecordWithoutPlaying(ItemStack itemstack) {
+- this.item = itemstack;
+- this.level.updateNeighborsAt(this.getBlockPos(), this.getBlockState().getBlock());
++ public void setRecordWithoutPlaying(ItemStack stack) {
++ this.item = stack;
++ // CraftBukkit start - add null check for level
++ if (level != null) {
++ this.level.updateNeighborsAt(this.getBlockPos(), this.getBlockState().getBlock());
++ }
++ // CraftBukkit end
+ this.setChanged();
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch
new file mode 100644
index 0000000000..41faf78303
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch
@@ -0,0 +1,163 @@
+--- a/net/minecraft/world/level/block/entity/LecternBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/LecternBlockEntity.java
+@@ -24,16 +24,69 @@
+ import net.minecraft.world.level.block.state.BlockState;
+ import net.minecraft.world.phys.Vec2;
+ import net.minecraft.world.phys.Vec3;
++// CraftBukkit start
++import java.util.ArrayList;
++import java.util.Arrays;
++import java.util.List;
++import org.bukkit.Location;
++import org.bukkit.block.Lectern;
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.craftbukkit.util.CraftLocation;
++import org.bukkit.entity.HumanEntity;
++import org.bukkit.inventory.InventoryHolder;
++// CraftBukkit end
+
+-public class LecternBlockEntity extends BlockEntity implements Clearable, MenuProvider {
++public class LecternBlockEntity extends BlockEntity implements Clearable, ITileInventory, CommandSource { // CraftBukkit - ICommandListener
+
+ public static final int DATA_PAGE = 0;
+ public static final int NUM_DATA = 1;
+ public static final int SLOT_BOOK = 0;
+ public static final int NUM_SLOTS = 1;
+- private final Container bookAccess = new Container() {
++ // CraftBukkit start - add fields and methods
++ public final Container bookAccess = new LecternInventory();
++ public class LecternInventory implements Container {
++
++ public List<HumanEntity> transaction = new ArrayList<>();
++ private int maxStack = 1;
++
+ @Override
+ @Override
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ @Override
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ @Override
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ @Override
++ public void setMaxStackSize(int i) {
++ maxStack = i;
++ }
++
++ @Override
++ public Location getLocation() {
++ if (level == null) return null;
++ return CraftLocation.toBukkit(worldPosition, level.getWorld());
++ }
++
++ @Override
++ public InventoryHolder getOwner() {
++ return (Lectern) LecternBlockEntity.this.getOwner();
++ }
++
++ public LecternBlockEntity getLectern() {
++ return LecternBlockEntity.this;
++ }
++ // CraftBukkit end
++
++ @Override
+ public int getContainerSize() {
+ return 1;
+ }
+@@ -81,13 +134,21 @@
+ }
+
+ @Override
+- @Override
+- public void setItem(int i, ItemStack itemstack) {}
++ // CraftBukkit start
++ public void setItem(int slot, ItemStack stack) {
++ if (slot == 0) {
++ LecternBlockEntity.this.setBook(stack);
++ if (LecternBlockEntity.this.getLevel() != null) {
++ LecternBlock.resetBookState(null, LecternBlockEntity.this.getLevel(), LecternBlockEntity.this.getBlockPos(), LecternBlockEntity.this.getBlockState(), LecternBlockEntity.this.hasBook());
++ }
++ }
++ }
++ // CraftBukkit end
+
+ @Override
+ @Override
+ public int getMaxStackSize() {
+- return 1;
++ return maxStack; // CraftBukkit
+ }
+
+ @Override
+@@ -174,7 +227,7 @@
+ if (j != this.page) {
+ this.page = j;
+ this.setChanged();
+- LecternBlock.signalPageChange(this.getLevel(), this.getBlockPos(), this.getBlockState());
++ if (this.level != null) LecternBlock.signalPageChange(this.getLevel(), this.getBlockPos(), this.getBlockState()); // CraftBukkit
+ }
+
+ }
+@@ -197,6 +250,32 @@
+ return itemstack;
+ }
+
++ // CraftBukkit start
++ @Override
++ public void sendSystemMessage(Component component) {
++ }
++
++ @Override
++ public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) {
++ return wrapper.getEntity() != null ? wrapper.getEntity().getBukkitSender(wrapper) : new org.bukkit.craftbukkit.command.CraftBlockCommandSender(wrapper, this);
++ }
++
++ @Override
++ public boolean acceptsSuccess() {
++ return false;
++ }
++
++ @Override
++ public boolean acceptsFailure() {
++ return false;
++ }
++
++ @Override
++ public boolean shouldInformAdmins() {
++ return false;
++ }
++
++ // CraftBukkit end
+ private CommandSourceStack createCommandSourceStack(@Nullable Player player) {
+ String s;
+ Object object;
+@@ -211,7 +290,8 @@
+
+ Vec3 vec3 = Vec3.atCenterOf(this.worldPosition);
+
+- return new CommandSourceStack(CommandSource.NULL, vec3, Vec2.ZERO, (ServerLevel) this.level, 2, s, (Component) object, this.level.getServer(), player);
++ // CraftBukkit - this
++ return new CommandSourceStack(this, vec3d, Vec2.ZERO, (ServerLevel) this.level, 2, s, (Component) object, this.level.getServer(), player);
+ }
+
+ @Override
+@@ -252,9 +328,8 @@
+ }
+
+ @Override
+- @Override
+- public AbstractContainerMenu createMenu(int i, Inventory inventory, Player player) {
+- return new LecternMenu(i, this.bookAccess, this.dataAccess);
++ public AbstractContainerMenu createMenu(int containerId, Inventory playerInventory, Player player) {
++ return new LecternMenu(containerId, this.bookAccess, this.dataAccess, playerInventory); // CraftBukkit
+ }
+
+ @Override
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java.patch
new file mode 100644
index 0000000000..d6171b3c6b
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java.patch
@@ -0,0 +1,47 @@
+--- a/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java
+@@ -29,19 +29,22 @@
+
+ private final SculkCatalystBlockEntity.CatalystListener catalystListener;
+
+- public SculkCatalystBlockEntity(BlockPos blockpos, BlockState blockstate) {
+- super(BlockEntityType.SCULK_CATALYST, blockpos, blockstate);
+- this.catalystListener = new SculkCatalystBlockEntity.CatalystListener(blockstate, new BlockPositionSource(blockpos));
++ public SculkCatalystBlockEntity(BlockPos pos, IBlockData blockState) {
++ super(BlockEntityType.SCULK_CATALYST, pos, blockState);
++ this.catalystListener = new SculkCatalystBlockEntity.CatalystListener(blockState, new BlockPositionSource(pos));
++ catalystListener.level = level; // CraftBukkit
+ }
+
+- public static void serverTick(Level level, BlockPos blockpos, BlockState blockstate, SculkCatalystBlockEntity sculkcatalystblockentity) {
+- sculkcatalystblockentity.catalystListener.getSculkSpreader().updateCursors(level, blockpos, level.getRandom(), true);
++ public static void serverTick(Level level, BlockPos pos, IBlockData state, SculkCatalystBlockEntity sculkCatalyst) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = sculkCatalyst.getBlockPos(); // CraftBukkit - SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep.
++ sculkCatalyst.catalystListener.getSculkSpreader().updateCursors(level, pos, level.getRandom(), true);
++ org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = null; // CraftBukkit
+ }
+
+ @Override
+- @Override
+- public void load(CompoundTag compoundtag) {
+- this.catalystListener.sculkSpreader.load(compoundtag);
++ public void load(CompoundTag tag) {
++ super.load(tag); // CraftBukkit - SPIGOT-7393: Load super Bukkit data
++ this.catalystListener.sculkSpreader.load(tag);
+ }
+
+ @Override
+@@ -63,11 +64,13 @@
+ final SculkSpreader sculkSpreader;
+ private final BlockState blockState;
+ private final PositionSource positionSource;
++ private Level level; // CraftBukkit
+
+ public CatalystListener(BlockState blockstate, PositionSource positionsource) {
+ this.blockState = blockstate;
+ this.positionSource = positionsource;
+ this.sculkSpreader = SculkSpreader.createLevelSpreader();
++ this.sculkSpreader.level = level; // CraftBukkit
+ }
+
+ @Override
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch
new file mode 100644
index 0000000000..2182780559
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch
@@ -0,0 +1,71 @@
+--- a/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java
+@@ -31,6 +31,10 @@
+ import net.minecraft.world.level.material.PushReaction;
+ import net.minecraft.world.phys.AABB;
+ import net.minecraft.world.phys.Vec3;
++// CraftBukkit start
++import org.bukkit.craftbukkit.entity.CraftHumanEntity;
++import org.bukkit.entity.HumanEntity;
++// CraftBukkit end
+
+ public class ShulkerBoxBlockEntity extends RandomizableContainerBlockEntity implements WorldlyContainer {
+
+@@ -51,8 +55,39 @@
+ @Nullable
+ private final DyeColor color;
+
+- public ShulkerBoxBlockEntity(@Nullable DyeColor dyecolor, BlockPos blockpos, BlockState blockstate) {
+- super(BlockEntityType.SHULKER_BOX, blockpos, blockstate);
++ // CraftBukkit start - add fields and methods
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++ private int maxStack = MAX_STACK;
++ public boolean opened;
++
++ public List<ItemStack> getContents() {
++ return this.itemStacks;
++ }
++
++ public void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ @Override
++ public int getMaxStackSize() {
++ return maxStack;
++ }
++
++ public void setMaxStackSize(int size) {
++ maxStack = size;
++ }
++ // CraftBukkit end
++
++ public ShulkerBoxBlockEntity(@Nullable DyeColor color, BlockPos pos, IBlockData blockState) {
++ super(BlockEntityType.SHULKER_BOX, pos, blockState);
+ this.itemStacks = NonNullList.withSize(27, ItemStack.EMPTY);
+ this.animationStatus = ShulkerBoxBlockEntity.AnimationStatus.CLOSED;
+ this.color = dyecolor;
+@@ -175,6 +207,7 @@
+ }
+
+ ++this.openCount;
++ if (opened) return; // CraftBukkit - only animate if the ShulkerBox hasn't been forced open already by an API call.
+ this.level.blockEvent(this.worldPosition, this.getBlockState().getBlock(), 1, this.openCount);
+ if (this.openCount == 1) {
+ this.level.gameEvent((Entity) player, GameEvent.CONTAINER_OPEN, this.worldPosition);
+@@ -189,6 +221,7 @@
+ public void stopOpen(Player player) {
+ if (!this.remove && !player.isSpectator()) {
+ --this.openCount;
++ if (opened) return; // CraftBukkit - only animate if the ShulkerBox hasn't been forced open already by an API call.
+ this.level.blockEvent(this.worldPosition, this.getBlockState().getBlock(), 1, this.openCount);
+ if (this.openCount <= 0) {
+ this.level.gameEvent((Entity) player, GameEvent.CONTAINER_CLOSE, this.worldPosition);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/SignBlockEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/SignBlockEntity.java.patch
new file mode 100644
index 0000000000..3b5dbd1f4d
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/SignBlockEntity.java.patch
@@ -0,0 +1,131 @@
+--- a/net/minecraft/world/level/block/entity/SignBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/SignBlockEntity.java
+@@ -32,8 +33,14 @@
+ import net.minecraft.world.phys.Vec2;
+ import net.minecraft.world.phys.Vec3;
+ import org.slf4j.Logger;
++import org.bukkit.block.sign.Side;
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.craftbukkit.util.CraftChatMessage;
++import org.bukkit.entity.Player;
++import org.bukkit.event.block.SignChangeEvent;
++// CraftBukkit end
+
+-public class SignBlockEntity extends BlockEntity {
++public class SignBlockEntity extends BlockEntity implements CommandSource { // CraftBukkit - implements
+
+ private static final Logger LOGGER = LogUtils.getLogger();
+ private static final int MAX_TEXT_LINE_WIDTH = 90;
+@@ -173,12 +178,13 @@
+ public void updateSignText(Player player, boolean flag, List<FilteredText> list) {
+ if (!this.isWaxed() && player.getUUID().equals(this.getPlayerWhoMayEdit()) && this.level != null) {
+ this.updateText((signtext) -> {
+- return this.setMessages(player, list, signtext);
+- }, flag);
++ return this.setMessages(player, filteredText, signtext, isFrontText); // CraftBukkit
++ }, isFrontText);
+ this.setAllowedPlayerEditor((UUID) null);
+ this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 3);
+ } else {
+ SignBlockEntity.LOGGER.warn("Player {} just tried to change non-editable sign", player.getName().getString());
++ ((ServerPlayer) player).connection.send(this.getUpdatePacket()); // CraftBukkit
+ }
+ }
+
+@@ -188,7 +194,8 @@
+ return this.setText((SignText) unaryoperator.apply(signtext), flag);
+ }
+
+- private SignText setMessages(Player player, List<FilteredText> list, SignText signtext) {
++ private SignText setMessages(net.minecraft.world.entity.player.Player entityhuman, List<FilteredText> list, SignText signtext, boolean front) { // CraftBukkit
++ SignText originalText = signtext; // CraftBukkit
+ for (int i = 0; i < list.size(); ++i) {
+ FilteredText filteredtext = (FilteredText) list.get(i);
+ Style style = signtext.getMessage(i, player.isTextFilteringEnabled()).getStyle();
+@@ -200,6 +207,29 @@
+ }
+ }
+
++ // CraftBukkit start
++ Player player = ((ServerPlayer) entityhuman).getBukkitEntity();
++ String[] lines = new String[4];
++
++ for (int i = 0; i < list.size(); ++i) {
++ lines[i] = CraftChatMessage.fromComponent(signtext.getMessage(i, entityhuman.isTextFilteringEnabled()));
++ }
++
++ SignChangeEvent event = new SignChangeEvent(CraftBlock.at(this.level, this.worldPosition), player, lines.clone(), (front) ? Side.FRONT : Side.BACK);
++ entityhuman.level().getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return originalText;
++ }
++
++ Component[] components = org.bukkit.craftbukkit.block.CraftSign.sanitizeLines(event.getLines());
++ for (int i = 0; i < components.length; i++) {
++ if (!Objects.equals(lines[i], event.getLine(i))) {
++ signtext = signtext.setMessage(i, components[i]);
++ }
++ }
++ // CraftBukkit end
++
+ return signtext;
+ }
+
+@@ -250,15 +280,34 @@
+ return flag1;
+ }
+
+- private static CommandSourceStack createCommandSourceStack(@Nullable Player player, Level level, BlockPos blockpos) {
+- String s = player == null ? "Sign" : player.getName().getString();
+- Object object = player == null ? Component.literal("Sign") : player.getDisplayName();
++ // CraftBukkit start
++ @Override
++ public void sendSystemMessage(Component component) {}
+
+ return new CommandSourceStack(CommandSource.NULL, Vec3.atCenterOf(blockpos), Vec2.ZERO, (ServerLevel) level, 2, s, (Component) object, level.getServer(), player);
+ }
+
+ @Override
+ @Override
++ public boolean acceptsFailure() {
++ return false;
++ }
++
++ @Override
++ public boolean shouldInformAdmins() {
++ return false;
++ }
++
++ private CommandSourceStack createCommandSourceStack(@Nullable net.minecraft.world.entity.player.Player level, Level pos, BlockPos blockposition) {
++ // CraftBukkit end
++ String s = level == null ? "Sign" : level.getName().getString();
++ Object object = level == null ? Component.literal("Sign") : level.getDisplayName();
++
++ // CraftBukkit - this
++ return new CommandSourceStack(this, Vec3.atCenterOf(blockposition), Vec2.ZERO, (ServerLevel) pos, 2, s, (Component) object, pos.getServer(), level);
++ }
++
++ @Override
+ public ClientboundBlockEntityDataPacket getUpdatePacket() {
+ return ClientboundBlockEntityDataPacket.create(this);
+ }
+@@ -281,12 +334,17 @@
+
+ @Nullable
+ public UUID getPlayerWhoMayEdit() {
++ // CraftBukkit start - unnecessary sign ticking removed, so do this lazily
++ if (this.level != null && this.playerWhoMayEdit != null) {
++ clearInvalidPlayerWhoMayEdit(this, this.level, this.playerWhoMayEdit);
++ }
++ // CraftBukkit end
+ return this.playerWhoMayEdit;
+ }
+
+ private void markUpdated() {
+ this.setChanged();
+- this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 3);
++ if (this.level != null) this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 3); // CraftBukkit - skip notify if world is null (SPIGOT-5122)
+ }
+
+ public boolean isWaxed() {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch
new file mode 100644
index 0000000000..8044466def
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/level/block/entity/SkullBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/SkullBlockEntity.java
+@@ -198,7 +193,17 @@
+ String s = compoundtag.getString("SkullOwner");
+
+ if (!Util.isBlank(s)) {
+- resolveGameProfile(compoundtag, s);
++ resolveGameProfile(nbttagcompound, s);
++ // CraftBukkit start
++ } else {
++ net.minecraft.nbt.ListTag textures = nbttagcompound.getCompound("SkullOwner").getCompound("Properties").getList("textures", 10); // Safe due to method contracts
++ for (int i = 0; i < textures.size(); i++) {
++ if (textures.get(i) instanceof CompoundTag && !((CompoundTag) textures.get(i)).contains("Signature", 8) && ((CompoundTag) textures.get(i)).getString("Value").trim().isEmpty()) {
++ nbttagcompound.remove("SkullOwner");
++ break;
++ }
++ }
++ // CraftBukkit end
+ }
+
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java.patch
new file mode 100644
index 0000000000..86289c3ead
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java.patch
@@ -0,0 +1,69 @@
+--- a/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java
+@@ -32,6 +33,11 @@
+ import net.minecraft.world.phys.AABB;
+ import net.minecraft.world.phys.Vec3;
+ import org.slf4j.Logger;
++import org.bukkit.Bukkit;
++import org.bukkit.craftbukkit.entity.CraftPlayer;
++import org.bukkit.craftbukkit.util.CraftLocation;
++import org.bukkit.event.player.PlayerTeleportEvent;
++// CraftBukkit end
+
+ public class TheEndGatewayBlockEntity extends TheEndPortalBlockEntity {
+
+@@ -174,12 +175,12 @@
+ theendgatewayblockentity.teleportCooldown = 100;
+ BlockPos blockpos1;
+
+- if (theendgatewayblockentity.exitPortal == null && level.dimension() == Level.END) {
+- blockpos1 = findOrCreateValidTeleportPos(serverlevel, blockpos);
+- blockpos1 = blockpos1.above(10);
+- TheEndGatewayBlockEntity.LOGGER.debug("Creating portal at {}", blockpos1);
+- spawnGatewayPortal(serverlevel, blockpos1, EndGatewayConfiguration.knownExit(blockpos, false));
+- theendgatewayblockentity.exitPortal = blockpos1;
++ if (blockEntity.exitPortal == null && level.getTypeKey() == LevelStem.END) { // CraftBukkit - work in alternate worlds
++ blockposition1 = findOrCreateValidTeleportPos(worldserver, pos);
++ blockposition1 = blockposition1.above(10);
++ TheEndGatewayBlockEntity.LOGGER.debug("Creating portal at {}", blockposition1);
++ spawnGatewayPortal(worldserver, blockposition1, EndGatewayConfiguration.knownExit(pos, false));
++ blockEntity.exitPortal = blockposition1;
+ }
+
+ if (theendgatewayblockentity.exitPortal != null) {
+@@ -203,8 +204,34 @@
+ entity1 = entity.getRootVehicle();
+ }
+
++ // CraftBukkit start - Fire PlayerTeleportEvent/EntityTeleportEvent
++ if (entity1 instanceof ServerPlayer) {
++ org.bukkit.craftbukkit.entity.CraftPlayer player = (CraftPlayer) entity1.getBukkitEntity();
++ org.bukkit.Location location = CraftLocation.toBukkit(blockposition1, level.getWorld()).add(0.5D, 0.5D, 0.5D);
++ location.setPitch(player.getLocation().getPitch());
++ location.setYaw(player.getLocation().getYaw());
++
++ PlayerTeleportEvent teleEvent = new PlayerTeleportEvent(player, player.getLocation(), location, PlayerTeleportEvent.TeleportCause.END_GATEWAY);
++ Bukkit.getPluginManager().callEvent(teleEvent);
++ if (teleEvent.isCancelled()) {
++ return;
++ }
++
++ entity1.setPortalCooldown();
++ ((ServerPlayer) entity1).connection.teleport(teleEvent.getTo());
++ triggerCooldown(level, pos, state, blockEntity); // CraftBukkit - call at end of method
++ return;
++
++ }
++
++ org.bukkit.event.entity.EntityTeleportEvent teleEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTeleportEvent(entity1, blockposition1.getX() + 0.5, blockposition1.getY() + 0.5, blockposition1.getZ() + 0.5);
++ if (teleEvent.isCancelled()) {
++ return;
++ }
++
+ entity1.setPortalCooldown();
+- entity1.teleportToWithTicket((double) blockpos1.getX() + 0.5D, (double) blockpos1.getY(), (double) blockpos1.getZ() + 0.5D);
++ entity1.teleportToWithTicket(teleEvent.getTo().getX(), teleEvent.getTo().getY(), teleEvent.getTo().getZ());
++ // CraftBukkit end
+ }
+
+ triggerCooldown(level, blockpos, blockstate, theendgatewayblockentity);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch
new file mode 100644
index 0000000000..cf6b0a02ef
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch
@@ -0,0 +1,89 @@
+--- a/net/minecraft/world/level/block/piston/PistonBaseBlock.java
++++ b/net/minecraft/world/level/block/piston/PistonBaseBlock.java
+@@ -40,6 +40,13 @@
+ import net.minecraft.world.phys.shapes.CollisionContext;
+ import net.minecraft.world.phys.shapes.Shapes;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++// CraftBukkit start
++import com.google.common.collect.ImmutableList;
++import java.util.AbstractList;
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.event.block.BlockPistonRetractEvent;
++import org.bukkit.event.block.BlockPistonExtendEvent;
++// CraftBukkit end
+
+ public class PistonBaseBlock extends DirectionalBlock {
+
+@@ -157,7 +158,19 @@
+ }
+ }
+
+- level.blockEvent(blockpos, this, b0, direction.get3DDataValue());
++ // CraftBukkit start
++ if (!this.isSticky) {
++ org.bukkit.block.Block block = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++ BlockPistonRetractEvent event = new BlockPistonRetractEvent(block, ImmutableList.<org.bukkit.block.Block>of(), CraftBlock.notchToBlockFace(enumdirection));
++ level.getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return;
++ }
++ }
++ // PAIL: checkME - what happened to setTypeAndData?
++ // CraftBukkit end
++ level.blockEvent(pos, this, b0, enumdirection.get3DDataValue());
+ }
+
+ }
+@@ -338,8 +350,50 @@
+ BlockState[] ablockstate = new BlockState[list.size() + list2.size()];
+ Direction direction1 = flag ? direction : direction.getOpposite();
+ int i = 0;
++ // CraftBukkit start
++ final org.bukkit.block.Block bblock = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
+
+- BlockPos blockpos3;
++ final List<BlockPos> moved = pistonextendschecker.getToPush();
++ final List<BlockPos> broken = pistonextendschecker.getToDestroy();
++
++ List<org.bukkit.block.Block> blocks = new AbstractList<org.bukkit.block.Block>() {
++
++ @Override
++ public int size() {
++ return moved.size() + broken.size();
++ }
++
++ @Override
++ public org.bukkit.block.Block get(int index) {
++ if (index >= size() || index < 0) {
++ throw new ArrayIndexOutOfBoundsException(index);
++ }
++ BlockPos pos = (BlockPos) (index < moved.size() ? moved.get(index) : broken.get(index - moved.size()));
++ return bblock.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++ }
++ };
++ org.bukkit.event.block.BlockPistonEvent event;
++ if (extending) {
++ event = new BlockPistonExtendEvent(bblock, blocks, CraftBlock.notchToBlockFace(enumdirection1));
++ } else {
++ event = new BlockPistonRetractEvent(bblock, blocks, CraftBlock.notchToBlockFace(enumdirection1));
++ }
++ level.getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ for (BlockPos b : broken) {
++ level.sendBlockUpdated(b, Blocks.AIR.defaultBlockState(), level.getBlockState(b), 3);
++ }
++ for (BlockPos b : moved) {
++ level.sendBlockUpdated(b, Blocks.AIR.defaultBlockState(), level.getBlockState(b), 3);
++ b = b.relative(enumdirection1);
++ level.sendBlockUpdated(b, Blocks.AIR.defaultBlockState(), level.getBlockState(b), 3);
++ }
++ return false;
++ }
++ // CraftBukkit end
++
++ BlockPos blockposition3;
+ int j;
+ BlockState blockstate1;
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/state/BlockBehaviour.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/state/BlockBehaviour.java.patch
new file mode 100644
index 0000000000..70419eb747
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/block/state/BlockBehaviour.java.patch
@@ -0,0 +1,15 @@
+--- a/net/minecraft/world/level/block/state/BlockBehaviour.java
++++ b/net/minecraft/world/level/block/state/BlockBehaviour.java
+@@ -188,8 +188,10 @@
+ BlockEntity blockentity = blockstate.hasBlockEntity() ? level.getBlockEntity(blockpos) : null;
+ LootParams.Builder lootparams_builder = (new LootParams.Builder(serverlevel)).withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(blockpos)).withParameter(LootContextParams.TOOL, ItemStack.EMPTY).withOptionalParameter(LootContextParams.BLOCK_ENTITY, blockentity).withOptionalParameter(LootContextParams.THIS_ENTITY, explosion.getDirectSourceEntity());
+
+- if (explosion.getBlockInteraction() == Explosion.BlockInteraction.DESTROY_WITH_DECAY) {
+- lootparams_builder.withParameter(LootContextParams.EXPLOSION_RADIUS, explosion.radius());
++ // CraftBukkit start - add yield
++ if (explosion.yield < 1.0F) {
++ lootparams_a.withParameter(LootContextParams.EXPLOSION_RADIUS, 1.0F / explosion.yield);
++ // CraftBukkit end
+ }
+
+ blockstate.spawnAfterBreak(serverlevel, blockpos, ItemStack.EMPTY, flag);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/border/WorldBorder.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/border/WorldBorder.java.patch
new file mode 100644
index 0000000000..45f0b0b718
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/border/WorldBorder.java.patch
@@ -0,0 +1,22 @@
+--- a/net/minecraft/world/level/border/WorldBorder.java
++++ b/net/minecraft/world/level/border/WorldBorder.java
+@@ -29,6 +29,7 @@
+ int absoluteMaxSize = 29999984;
+ private WorldBorder.BorderExtent extent = new WorldBorder.StaticBorderExtent(5.9999968E7D);
+ public static final WorldBorder.Settings DEFAULT_SETTINGS = new WorldBorder.Settings(0.0D, 0.0D, 0.2D, 5.0D, 5, 15, 5.9999968E7D, 0L, 0.0D);
++ public net.minecraft.server.level.ServerLevel world; // CraftBukkit
+
+ public WorldBorder() {}
+
+@@ -163,8 +164,9 @@
+ return Lists.newArrayList(this.listeners);
+ }
+
+- public void addListener(BorderChangeListener borderchangelistener) {
+- this.listeners.add(borderchangelistener);
++ public void addListener(BorderChangeListener listener) {
++ if (listeners.contains(listener)) return; // CraftBukkit
++ this.listeners.add(listener);
+ }
+
+ public void removeListener(BorderChangeListener borderchangelistener) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/ChunkAccess.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/ChunkAccess.java.patch
new file mode 100644
index 0000000000..ecc6a59334
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/ChunkAccess.java.patch
@@ -0,0 +1,102 @@
+--- a/net/minecraft/world/level/chunk/ChunkAccess.java
++++ b/net/minecraft/world/level/chunk/ChunkAccess.java
+@@ -82,25 +82,34 @@
+ protected final LevelHeightAccessor levelHeightAccessor;
+ protected final LevelChunkSection[] sections;
+
+- public ChunkAccess(ChunkPos chunkpos, UpgradeData upgradedata, LevelHeightAccessor levelheightaccessor, Registry<Biome> registry, long i, @Nullable LevelChunkSection[] alevelchunksection, @Nullable BlendingData blendingdata) {
+- this.chunkPos = chunkpos;
+- this.upgradeData = upgradedata;
+- this.levelHeightAccessor = levelheightaccessor;
+- this.sections = new LevelChunkSection[levelheightaccessor.getSectionsCount()];
+- this.inhabitedTime = i;
+- this.postProcessing = new ShortList[levelheightaccessor.getSectionsCount()];
+- this.blendingData = blendingdata;
+- this.skyLightSources = new ChunkSkyLightSources(levelheightaccessor);
+- if (alevelchunksection != null) {
+- if (this.sections.length == alevelchunksection.length) {
+- System.arraycopy(alevelchunksection, 0, this.sections, 0, this.sections.length);
++ // CraftBukkit start - SPIGOT-6814: move to IChunkAccess to account for 1.17 to 1.18 chunk upgrading.
++ private static final org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry();
++ public org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer persistentDataContainer = new org.bukkit.craftbukkit.persistence.DirtyCraftPersistentDataContainer(DATA_TYPE_REGISTRY);
++ // CraftBukkit end
++
++ public ChunkAccess(ChunkPos chunkPos, UpgradeData upgradeData, LevelHeightAccessor levelHeightAccessor, Registry<Biome> biomeRegistry, long inhabitedTime, @Nullable LevelChunkSection[] achunksection, @Nullable BlendingData sections) {
++ this.chunkPos = chunkPos;
++ this.upgradeData = upgradeData;
++ this.levelHeightAccessor = levelHeightAccessor;
++ this.sections = new LevelChunkSection[levelHeightAccessor.getSectionsCount()];
++ this.inhabitedTime = inhabitedTime;
++ this.postProcessing = new ShortList[levelHeightAccessor.getSectionsCount()];
++ this.blendingData = sections;
++ this.skyLightSources = new ChunkSkyLightSources(levelHeightAccessor);
++ if (achunksection != null) {
++ if (this.sections.length == achunksection.length) {
++ System.arraycopy(achunksection, 0, this.sections, 0, this.sections.length);
+ } else {
+ ChunkAccess.LOGGER.warn("Could not set level chunk sections, array length is {} instead of {}", alevelchunksection.length, this.sections.length);
+ }
+ }
+
+- replaceMissingSections(registry, this.sections);
++ replaceMissingSections(biomeRegistry, this.sections);
++ // CraftBukkit start
++ this.biomeRegistry = biomeRegistry;
+ }
++ public final Registry<Biome> biomeRegistry;
++ // CraftBukkit end
+
+ private static void replaceMissingSections(Registry<Biome> registry, LevelChunkSection[] alevelchunksection) {
+ for (int i = 0; i < alevelchunksection.length; ++i) {
+@@ -267,12 +270,13 @@
+ return true;
+ }
+
+- public void setUnsaved(boolean flag) {
+- this.unsaved = flag;
++ public void setUnsaved(boolean unsaved) {
++ this.unsaved = unsaved;
++ if (!unsaved) this.persistentDataContainer.dirty(false); // CraftBukkit - SPIGOT-6814: chunk was saved, pdc is no longer dirty
+ }
+
+ public boolean isUnsaved() {
+- return this.unsaved;
++ return this.unsaved || this.persistentDataContainer.dirty(); // CraftBukkit - SPIGOT-6814: chunk is unsaved if pdc was mutated
+ }
+
+ public abstract ChunkStatus getStatus();
+@@ -445,12 +445,33 @@
+ CrashReport crashreport = CrashReport.forThrowable(throwable, "Getting biome");
+ CrashReportCategory crashreportcategory = crashreport.addCategory("Biome being got");
+
+- crashreportcategory.setDetail("Location", () -> {
++ crashreportsystemdetails.setDetail("Location", () -> {
++ return CrashReportCategory.formatLocation(this, x, y, z);
++ });
++ throw new ReportedException(crashreport);
++ }
++ }
++
++ // CraftBukkit start
++ public void setBiome(int i, int j, int k, Holder<Biome> biome) {
++ try {
++ int l = QuartPos.fromBlock(this.getMinBuildHeight());
++ int i1 = l + QuartPos.fromBlock(this.getHeight()) - 1;
++ int j1 = Mth.clamp(j, l, i1);
++ int k1 = this.getSectionIndex(QuartPos.toBlock(j1));
++
++ this.sections[k1].setBiome(i & 3, j1 & 3, k & 3, biome);
++ } catch (Throwable throwable) {
++ CrashReport crashreport = CrashReport.forThrowable(throwable, "Setting biome");
++ CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Biome being set");
++
++ crashreportsystemdetails.setDetail("Location", () -> {
+ return CrashReportCategory.formatLocation(this, i, j, k);
+ });
+ throw new ReportedException(crashreport);
+ }
+ }
++ // CraftBukkit end
+
+ public void fillBiomesFromNoise(BiomeResolver biomeresolver, Climate.Sampler climate_sampler) {
+ ChunkPos chunkpos = this.getPos();
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/ChunkGenerator.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/ChunkGenerator.java.patch
new file mode 100644
index 0000000000..5e896beacf
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/ChunkGenerator.java.patch
@@ -0,0 +1,74 @@
+--- a/net/minecraft/world/level/chunk/ChunkGenerator.java
++++ b/net/minecraft/world/level/chunk/ChunkGenerator.java
+@@ -307,8 +306,8 @@
+ }
+ }
+
+- public void applyBiomeDecoration(WorldGenLevel worldgenlevel, ChunkAccess chunkaccess, StructureManager structuremanager) {
+- ChunkPos chunkpos = chunkaccess.getPos();
++ public void addVanillaDecorations(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) { // CraftBukkit
++ ChunkPos chunkcoordintpair = ichunkaccess.getPos();
+
+ if (!SharedConstants.debugVoidTerrain(chunkpos)) {
+ SectionPos sectionpos = SectionPos.of(chunkpos, worldgenlevel.getMinSection());
+@@ -440,11 +439,38 @@
+ }
+ }
+
+- private static BoundingBox getWritableArea(ChunkAccess chunkaccess) {
+- ChunkPos chunkpos = chunkaccess.getPos();
+- int i = chunkpos.getMinBlockX();
+- int j = chunkpos.getMinBlockZ();
+- LevelHeightAccessor levelheightaccessor = chunkaccess.getHeightAccessorForGeneration();
++ // CraftBukkit start
++ public void applyBiomeDecoration(WorldGenLevel level, ChunkAccess chunk, StructureManager structureManager) {
++ applyBiomeDecoration(level, chunk, structureManager, true);
++ }
++
++ public void applyBiomeDecoration(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager, boolean vanilla) {
++ if (vanilla) {
++ addVanillaDecorations(generatoraccessseed, ichunkaccess, structuremanager);
++ }
++
++ org.bukkit.World world = generatoraccessseed.getMinecraftWorld().getWorld();
++ // only call when a populator is present (prevents unnecessary entity conversion)
++ if (!world.getPopulators().isEmpty()) {
++ org.bukkit.craftbukkit.generator.CraftLimitedRegion limitedRegion = new org.bukkit.craftbukkit.generator.CraftLimitedRegion(generatoraccessseed, ichunkaccess.getPos());
++ int x = ichunkaccess.getPos().x;
++ int z = ichunkaccess.getPos().z;
++ for (org.bukkit.generator.BlockPopulator populator : world.getPopulators()) {
++ WorldgenRandom seededrandom = new WorldgenRandom(new net.minecraft.world.level.levelgen.LegacyRandomSource(generatoraccessseed.getSeed()));
++ seededrandom.setDecorationSeed(generatoraccessseed.getSeed(), x, z);
++ populator.populate(world, new org.bukkit.craftbukkit.util.RandomSourceWrapper.RandomWrapper(seededrandom), x, z, limitedRegion);
++ }
++ limitedRegion.saveEntities();
++ limitedRegion.breakLink();
++ }
++ }
++ // CraftBukkit end
++
++ private static BoundingBox getWritableArea(ChunkAccess chunk) {
++ ChunkPos chunkcoordintpair = chunk.getPos();
++ int i = chunkcoordintpair.getMinBlockX();
++ int j = chunkcoordintpair.getMinBlockZ();
++ LevelHeightAccessor levelheightaccessor = chunk.getHeightAccessorForGeneration();
+ int k = levelheightaccessor.getMinBuildHeight() + 1;
+ int l = levelheightaccessor.getMaxBuildHeight() - 1;
+
+@@ -577,7 +603,15 @@
+ StructureStart structurestart = structure.generate(registryaccess, this, this.biomeSource, randomstate, structuretemplatemanager, i, chunkpos, j, chunkaccess, predicate);
+
+ if (structurestart.isValid()) {
+- structuremanager.setStartForStructure(sectionpos, structure, structurestart, chunkaccess);
++ // CraftBukkit start
++ BoundingBox box = structurestart.getBoundingBox();
++ org.bukkit.event.world.AsyncStructureSpawnEvent event = new org.bukkit.event.world.AsyncStructureSpawnEvent(structureManager.level.getMinecraftWorld().getWorld(), org.bukkit.craftbukkit.generator.structure.CraftStructure.minecraftToBukkit(structure, registryAccess), new org.bukkit.util.BoundingBox(box.minX(), box.minY(), box.minZ(), box.maxX(), box.maxY(), box.maxZ()), chunk.x, chunk.z);
++ org.bukkit.Bukkit.getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ return true;
++ }
++ // CraftBukkit end
++ structureManager.setStartForStructure(chunkPos, structure, structurestart, ichunkaccess);
+ return true;
+ } else {
+ return false;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/ChunkStatus.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/ChunkStatus.java.patch
new file mode 100644
index 0000000000..2e317e64ea
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/ChunkStatus.java.patch
@@ -0,0 +1,15 @@
+--- a/net/minecraft/world/level/chunk/ChunkStatus.java
++++ b/net/minecraft/world/level/chunk/ChunkStatus.java
+@@ -38,9 +38,9 @@
+ };
+ public static final ChunkStatus EMPTY = registerSimple("empty", (ChunkStatus) null, -1, ChunkStatus.PRE_FEATURES, ChunkStatus.ChunkType.PROTOCHUNK, (chunkstatus, serverlevel, chunkgenerator, list, chunkaccess) -> {
+ });
+- public static final ChunkStatus STRUCTURE_STARTS = register("structure_starts", ChunkStatus.EMPTY, 0, false, ChunkStatus.PRE_FEATURES, ChunkStatus.ChunkType.PROTOCHUNK, (chunkstatus, executor, serverlevel, chunkgenerator, structuretemplatemanager, threadedlevellightengine, function, list, chunkaccess) -> {
+- if (serverlevel.getServer().getWorldData().worldGenOptions().generateStructures()) {
+- chunkgenerator.createStructures(serverlevel.registryAccess(), serverlevel.getChunkSource().getGeneratorState(), serverlevel.structureManager(), chunkaccess, structuretemplatemanager);
++ public static final ChunkStatus STRUCTURE_STARTS = register("structure_starts", ChunkStatus.EMPTY, 0, false, ChunkStatus.PRE_FEATURES, ChunkStatus.Type.PROTOCHUNK, (chunkstatus, executor, worldserver, chunkgenerator, structuretemplatemanager, lightenginethreaded, function, list, ichunkaccess) -> {
++ if (worldserver.serverLevelData.worldGenOptions().generateStructures()) { // CraftBukkit
++ chunkgenerator.createStructures(worldserver.registryAccess(), worldserver.getChunkSource().getGeneratorState(), worldserver.structureManager(), ichunkaccess, structuretemplatemanager);
+ }
+
+ serverlevel.onStructureStartsAvailable(chunkaccess);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/DataLayer.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/DataLayer.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/DataLayer.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/LevelChunk.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/LevelChunk.java.patch
new file mode 100644
index 0000000000..dc92d39044
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/LevelChunk.java.patch
@@ -0,0 +1,185 @@
+--- a/net/minecraft/world/level/chunk/LevelChunk.java
++++ b/net/minecraft/world/level/chunk/LevelChunk.java
+@@ -78,8 +74,8 @@
+ }
+ };
+ private final Map<BlockPos, LevelChunk.RebindableTickingBlockEntityWrapper> tickersInLevel;
+- private boolean loaded;
+- final Level level;
++ public boolean loaded;
++ public final ServerLevel level; // CraftBukkit - type
+ @Nullable
+ private Supplier<FullChunkStatus> fullStatus;
+ @Nullable
+@@ -95,7 +91,7 @@
+ public LevelChunk(Level level, ChunkPos chunkpos, UpgradeData upgradedata, LevelChunkTicks<Block> levelchunkticks, LevelChunkTicks<Fluid> levelchunkticks1, long i, @Nullable LevelChunkSection[] alevelchunksection, @Nullable LevelChunk.PostLoadProcessor levelchunk_postloadprocessor, @Nullable BlendingData blendingdata) {
+ super(chunkpos, upgradedata, level, level.registryAccess().registryOrThrow(Registries.BIOME), i, alevelchunksection, blendingdata);
+ this.tickersInLevel = Maps.newHashMap();
+- this.level = level;
++ this.level = (ServerLevel) level; // CraftBukkit - type
+ this.gameEventListenerRegistrySections = new Int2ObjectOpenHashMap();
+ Heightmap.Types[] aheightmap_types = Heightmap.Types.values();
+ int j = aheightmap_types.length;
+@@ -113,9 +109,10 @@
+ this.fluidTicks = levelchunkticks1;
+ }
+
+- public LevelChunk(ServerLevel serverlevel, ProtoChunk protochunk, @Nullable LevelChunk.PostLoadProcessor levelchunk_postloadprocessor) {
+- this(serverlevel, protochunk.getPos(), protochunk.getUpgradeData(), protochunk.unpackBlockTicks(), protochunk.unpackFluidTicks(), protochunk.getInhabitedTime(), protochunk.getSections(), levelchunk_postloadprocessor, protochunk.getBlendingData());
+- Iterator iterator = protochunk.getBlockEntities().values().iterator();
++ // CraftBukkit start
++ public boolean mustNotSave;
++ public boolean needsDecoration;
++ // CraftBukkit end
+
+ while (iterator.hasNext()) {
+ BlockEntity blockentity = (BlockEntity) iterator.next();
+@@ -144,6 +145,10 @@
+ this.skyLightSources = protochunk.skyLightSources;
+ this.setLightCorrect(protochunk.isLightCorrect());
+ this.unsaved = true;
++ this.needsDecoration = true; // CraftBukkit
++ // CraftBukkit start
++ this.persistentDataContainer = chunk.persistentDataContainer; // SPIGOT-6814: copy PDC to account for 1.17 to 1.18 chunk upgrading.
++ // CraftBukkit end
+ }
+
+ @Override
+@@ -254,6 +253,7 @@
+ }
+ }
+
++ // CraftBukkit start
+ @Nullable
+ @Override
+ @Override
+@@ -262,7 +260,14 @@
+ LevelChunkSection levelchunksection = this.getSection(this.getSectionIndex(i));
+ boolean flag1 = levelchunksection.hasOnlyAir();
+
+- if (flag1 && blockstate.isAir()) {
++ @Nullable
++ public IBlockData setBlockState(BlockPos blockposition, IBlockData iblockdata, boolean flag, boolean doPlace) {
++ // CraftBukkit end
++ int i = blockposition.getY();
++ LevelChunkSection chunksection = this.getSection(this.getSectionIndex(i));
++ boolean flag1 = chunksection.hasOnlyAir();
++
++ if (flag1 && iblockdata.isAir()) {
+ return null;
+ } else {
+ int j = blockpos.getX() & 15;
+@@ -306,8 +311,9 @@
+ if (!levelchunksection.getBlockState(j, k, l).is(block)) {
+ return null;
+ } else {
+- if (!this.level.isClientSide) {
+- blockstate.onPlace(this.level, blockpos, blockstate1, flag);
++ // CraftBukkit - Don't place while processing the BlockPlaceEvent, unless it's a BlockContainer. Prevents blocks such as TNT from activating when cancelled.
++ if (!this.level.isClientSide && doPlace && (!this.level.captureBlockStates || block instanceof net.minecraft.world.level.block.BaseEntityBlock)) {
++ iblockdata.onPlace(this.level, blockposition, iblockdata1, flag);
+ }
+
+ if (blockstate.hasBlockEntity()) {
+@@ -352,8 +356,13 @@
+ }
+
+ @Nullable
+- public BlockEntity getBlockEntity(BlockPos blockpos, LevelChunk.EntityCreationType levelchunk_entitycreationtype) {
+- BlockEntity blockentity = (BlockEntity) this.blockEntities.get(blockpos);
++ public BlockEntity getBlockEntity(BlockPos pos, LevelChunk.EnumTileEntityState creationType) {
++ // CraftBukkit start
++ BlockEntity tileentity = level.capturedTileEntities.get(pos);
++ if (tileentity == null) {
++ tileentity = (BlockEntity) this.blockEntities.get(pos);
++ }
++ // CraftBukkit end
+
+ if (blockentity == null) {
+ CompoundTag compoundtag = (CompoundTag) this.pendingBlockEntities.remove(blockpos);
+@@ -432,6 +440,13 @@
+ blockentity1.setRemoved();
+ }
+
++ // CraftBukkit start
++ } else {
++ System.out.println("Attempted to place a tile entity (" + blockEntity + ") at " + blockEntity.getBlockPos().getX() + "," + blockEntity.getBlockPos().getY() + "," + blockEntity.getBlockPos().getZ()
++ + " (" + getBlockState(blockposition) + ") where there was no entity tile!");
++ System.out.println("Chunk coordinates: " + (this.chunkPos.x * 16) + "," + (this.chunkPos.z * 16));
++ new Exception().printStackTrace();
++ // CraftBukkit end
+ }
+ }
+
+@@ -463,8 +476,11 @@
+ if (this.isInLevel()) {
+ BlockEntity blockentity = (BlockEntity) this.blockEntities.remove(blockpos);
+
+- if (blockentity != null) {
+- Level level = this.level;
++ // CraftBukkit start - SPIGOT-5561: Also remove from pending map
++ if (!pendingBlockEntities.isEmpty()) {
++ pendingBlockEntities.remove(pos);
++ }
++ // CraftBukkit end
+
+ if (level instanceof ServerLevel) {
+ ServerLevel serverlevel = (ServerLevel) level;
+@@ -516,6 +535,57 @@
+
+ }
+
++ // CraftBukkit start
++ public void loadCallback() {
++ org.bukkit.Server server = this.level.getCraftServer();
++ if (server != null) {
++ /*
++ * If it's a new world, the first few chunks are generated inside
++ * the World constructor. We can't reliably alter that, so we have
++ * no way of creating a CraftWorld/CraftServer at that point.
++ */
++ org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this);
++ server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkLoadEvent(bukkitChunk, this.needsDecoration));
++
++ if (this.needsDecoration) {
++ this.needsDecoration = false;
++ java.util.Random random = new java.util.Random();
++ random.setSeed(level.getSeed());
++ long xRand = random.nextLong() / 2L * 2L + 1L;
++ long zRand = random.nextLong() / 2L * 2L + 1L;
++ random.setSeed((long) this.chunkPos.x * xRand + (long) this.chunkPos.z * zRand ^ level.getSeed());
++
++ org.bukkit.World world = this.level.getWorld();
++ if (world != null) {
++ this.level.populating = true;
++ try {
++ for (org.bukkit.generator.BlockPopulator populator : world.getPopulators()) {
++ populator.populate(world, random, bukkitChunk);
++ }
++ } finally {
++ this.level.populating = false;
++ }
++ }
++ server.getPluginManager().callEvent(new org.bukkit.event.world.ChunkPopulateEvent(bukkitChunk));
++ }
++ }
++ }
++
++ public void unloadCallback() {
++ org.bukkit.Server server = this.level.getCraftServer();
++ org.bukkit.Chunk bukkitChunk = new org.bukkit.craftbukkit.CraftChunk(this);
++ org.bukkit.event.world.ChunkUnloadEvent unloadEvent = new org.bukkit.event.world.ChunkUnloadEvent(bukkitChunk, this.isUnsaved());
++ server.getPluginManager().callEvent(unloadEvent);
++ // note: saving can be prevented, but not forced if no saving is actually required
++ this.mustNotSave = !unloadEvent.isSaveChunk();
++ }
++
++ @Override
++ public boolean isUnsaved() {
++ return super.isUnsaved() && !this.mustNotSave;
++ }
++ // CraftBukkit end
++
+ public boolean isEmpty() {
+ return false;
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/LevelChunkSection.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/LevelChunkSection.java.patch
new file mode 100644
index 0000000000..87bf8fcc9d
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/LevelChunkSection.java.patch
@@ -0,0 +1,37 @@
+--- a/net/minecraft/world/level/chunk/LevelChunkSection.java
++++ b/net/minecraft/world/level/chunk/LevelChunkSection.java
+@@ -22,11 +22,13 @@
+ private short nonEmptyBlockCount;
+ private short tickingBlockCount;
+ private short tickingFluidCount;
+- private final PalettedContainer<BlockState> states;
+- private PalettedContainerRO<Holder<Biome>> biomes;
++ private final PalettedContainer<IBlockData> states;
++ // CraftBukkit start - read/write
++ private PalettedContainer<Holder<Biome>> biomes;
+
+- public LevelChunkSection(PalettedContainer<BlockState> palettedcontainer, PalettedContainerRO<Holder<Biome>> palettedcontainerro) {
+- this.states = palettedcontainer;
++ public LevelChunkSection(PalettedContainer<IBlockData> datapaletteblock, PalettedContainer<Holder<Biome>> palettedcontainerro) {
++ // CraftBukkit end
++ this.states = datapaletteblock;
+ this.biomes = palettedcontainerro;
+ this.recalcBlockCounts();
+ }
+@@ -189,8 +190,14 @@
+ return (Holder) this.biomes.get(i, j, k);
+ }
+
+- public void fillBiomesFromNoise(BiomeResolver biomeresolver, Climate.Sampler climate_sampler, int i, int j, int k) {
+- PalettedContainer<Holder<Biome>> palettedcontainer = this.biomes.recreate();
++ // CraftBukkit start
++ public void setBiome(int i, int j, int k, Holder<Biome> biome) {
++ this.biomes.set(i, j, k, biome);
++ }
++ // CraftBukkit end
++
++ public void fillBiomesFromNoise(BiomeResolver biomeResolver, Climate.Sampler climateSampler, int x, int y, int z) {
++ PalettedContainer<Holder<Biome>> datapaletteblock = this.biomes.recreate();
+ boolean flag = true;
+
+ for (int l = 0; l < 4; ++l) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/storage/ChunkSerializer.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/storage/ChunkSerializer.java.patch
new file mode 100644
index 0000000000..1f6ebd7010
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/storage/ChunkSerializer.java.patch
@@ -0,0 +1,94 @@
+--- a/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
++++ b/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
+@@ -93,16 +93,16 @@
+ ChunkSerializer.LOGGER.error("Chunk file at {} is in the wrong location; relocating. (Expected {}, got {})", new Object[]{chunkpos, chunkpos, chunkpos1});
+ }
+
+- UpgradeData upgradedata = compoundtag.contains("UpgradeData", 10) ? new UpgradeData(compoundtag.getCompound("UpgradeData"), serverlevel) : UpgradeData.EMPTY;
+- boolean flag = compoundtag.getBoolean("isLightOn");
+- ListTag listtag = compoundtag.getList("sections", 10);
+- int i = serverlevel.getSectionsCount();
+- LevelChunkSection[] alevelchunksection = new LevelChunkSection[i];
+- boolean flag1 = serverlevel.dimensionType().hasSkyLight();
+- ServerChunkCache serverchunkcache = serverlevel.getChunkSource();
+- LevelLightEngine levellightengine = serverchunkcache.getLightEngine();
+- Registry<Biome> registry = serverlevel.registryAccess().registryOrThrow(Registries.BIOME);
+- Codec<PalettedContainerRO<Holder<Biome>>> codec = makeBiomeCodec(registry);
++ UpgradeData chunkconverter = tag.contains("UpgradeData", 10) ? new UpgradeData(tag.getCompound("UpgradeData"), level) : UpgradeData.EMPTY;
++ boolean flag = tag.getBoolean("isLightOn");
++ ListTag nbttaglist = tag.getList("sections", 10);
++ int i = level.getSectionsCount();
++ LevelChunkSection[] achunksection = new LevelChunkSection[i];
++ boolean flag1 = level.dimensionType().hasSkyLight();
++ ServerChunkCache chunkproviderserver = level.getChunkSource();
++ LevelLightEngine levellightengine = chunkproviderserver.getLightEngine();
++ Registry<Biome> iregistry = level.registryAccess().registryOrThrow(Registries.BIOME);
++ Codec<PalettedContainer<Holder<Biome>>> codec = makeBiomeCodecRW(iregistry); // CraftBukkit - read/write
+ boolean flag2 = false;
+
+ DataResult dataresult;
+@@ -127,7 +127,7 @@
+ palettedcontainer = new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState(), PalettedContainer.Strategy.SECTION_STATES);
+ }
+
+- Object object;
++ PalettedContainer object; // CraftBukkit - read/write
+
+ if (compoundtag1.contains("biomes", 10)) {
+ dataresult = codec.parse(NbtOps.INSTANCE, compoundtag1.getCompound("biomes")).promotePartial((s) -> {
+@@ -140,7 +140,7 @@
+ object = new PalettedContainer<>(registry.asHolderIdMap(), registry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES);
+ }
+
+- LevelChunkSection levelchunksection = new LevelChunkSection(palettedcontainer, (PalettedContainerRO) object);
++ LevelChunkSection chunksection = new LevelChunkSection(datapaletteblock, (PalettedContainer) object); // CraftBukkit - read/write
+
+ alevelchunksection[k] = levelchunksection;
+ SectionPos sectionpos = SectionPos.of(chunkpos, b0);
+@@ -221,6 +221,13 @@
+ }
+ }
+
++ // CraftBukkit start - load chunk persistent data from nbt - SPIGOT-6814: Already load PDC here to account for 1.17 to 1.18 chunk upgrading.
++ net.minecraft.nbt.Tag persistentBase = tag.get("ChunkBukkitValues");
++ if (persistentBase instanceof CompoundTag) {
++ ((ChunkAccess) object1).persistentDataContainer.putAll((CompoundTag) persistentBase);
++ }
++ // CraftBukkit end
++
+ ((ChunkAccess) object1).setLightCorrect(flag);
+ CompoundTag compoundtag2 = compoundtag.getCompound("Heightmaps");
+ EnumSet<Heightmap.Types> enumset = EnumSet.noneOf(Heightmap.Types.class);
+@@ -300,9 +307,11 @@
+ return PalettedContainer.codecRO(registry.asHolderIdMap(), registry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, registry.getHolderOrThrow(Biomes.PLAINS));
+ }
+
+- public static CompoundTag write(ServerLevel serverlevel, ChunkAccess chunkaccess) {
+- ChunkPos chunkpos = chunkaccess.getPos();
+- CompoundTag compoundtag = NbtUtils.addCurrentDataVersion(new CompoundTag());
++ // CraftBukkit start - read/write
++ private static Codec<PalettedContainer<Holder<Biome>>> makeBiomeCodecRW(Registry<Biome> iregistry) {
++ return PalettedContainer.codecRW(iregistry.asHolderIdMap(), iregistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, iregistry.getHolderOrThrow(Biomes.PLAINS));
++ }
++ // CraftBukkit end
+
+ compoundtag.putInt("xPos", chunkpos.x);
+ compoundtag.putInt("yPos", chunkaccess.getMinSection());
+@@ -439,9 +452,14 @@
+ }
+ }
+
+- compoundtag.put("Heightmaps", compoundtag3);
+- compoundtag.put("structures", packStructureData(StructurePieceSerializationContext.fromLevel(serverlevel), chunkpos, chunkaccess.getAllStarts(), chunkaccess.getAllReferences()));
+- return compoundtag;
++ nbttagcompound.put("Heightmaps", nbttagcompound3);
++ nbttagcompound.put("structures", packStructureData(StructurePieceSerializationContext.fromLevel(level), chunkcoordintpair, chunk.getAllStarts(), chunk.getAllReferences()));
++ // CraftBukkit start - store chunk persistent data in nbt
++ if (!chunk.persistentDataContainer.isEmpty()) { // SPIGOT-6814: Always save PDC to account for 1.17 to 1.18 chunk upgrading.
++ nbttagcompound.put("ChunkBukkitValues", chunk.persistentDataContainer.toTagCompound());
++ }
++ // CraftBukkit end
++ return nbttagcompound;
+ }
+
+ private static void saveTicks(ServerLevel serverlevel, CompoundTag compoundtag, ChunkAccess.TicksToSave chunkaccess_tickstosave) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch
new file mode 100644
index 0000000000..b8f6e2e997
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch
@@ -0,0 +1,91 @@
+--- a/net/minecraft/world/level/chunk/storage/ChunkStorage.java
++++ b/net/minecraft/world/level/chunk/storage/ChunkStorage.java
+@@ -16,6 +18,10 @@
+ import net.minecraft.world.level.ChunkPos;
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.chunk.ChunkGenerator;
++// CraftBukkit start
++import java.util.concurrent.ExecutionException;
++import net.minecraft.world.level.chunk.ChunkStatus;
++import net.minecraft.world.level.dimension.LevelStem;
+ import net.minecraft.world.level.levelgen.structure.LegacyStructureDataHandler;
+ import net.minecraft.world.level.storage.DimensionDataStorage;
+
+@@ -36,9 +42,53 @@
+ return this.worker.isOldChunkAround(chunkpos, i);
+ }
+
+- public CompoundTag upgradeChunkTag(ResourceKey<Level> resourcekey, Supplier<DimensionDataStorage> supplier, CompoundTag compoundtag, Optional<ResourceKey<Codec<? extends ChunkGenerator>>> optional) {
+- int i = getVersion(compoundtag);
++ // CraftBukkit start
++ private boolean check(ServerChunkCache cps, int x, int z) {
++ ChunkPos pos = new ChunkPos(x, z);
++ if (cps != null) {
++ com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread");
++ if (cps.hasChunk(x, z)) {
++ return true;
++ }
++ }
+
++ CompoundTag nbt;
++ try {
++ nbt = read(pos).get().orElse(null);
++ } catch (InterruptedException | ExecutionException ex) {
++ throw new RuntimeException(ex);
++ }
++ if (nbt != null) {
++ CompoundTag level = nbt.getCompound("Level");
++ if (level.getBoolean("TerrainPopulated")) {
++ return true;
++ }
++
++ ChunkStatus status = ChunkStatus.byName(level.getString("Status"));
++ if (status != null && status.isOrAfter(ChunkStatus.FEATURES)) {
++ return true;
++ }
++ }
++
++ return false;
++ }
++
++ public CompoundTag upgradeChunkTag(ResourceKey<LevelStem> resourcekey, Supplier<DimensionDataStorage> supplier, CompoundTag nbttagcompound, Optional<ResourceKey<Codec<? extends ChunkGenerator>>> optional, ChunkPos pos, @Nullable LevelAccessor generatoraccess) {
++ // CraftBukkit end
++ int i = getVersion(nbttagcompound);
++
++ // CraftBukkit start
++ if (i < 1466) {
++ CompoundTag level = nbttagcompound.getCompound("Level");
++ if (level.getBoolean("TerrainPopulated") && !level.getBoolean("LightPopulated")) {
++ ServerChunkCache cps = (generatoraccess == null) ? null : ((ServerLevel) generatoraccess).getChunkSource();
++ if (check(cps, pos.x - 1, pos.z) && check(cps, pos.x - 1, pos.z - 1) && check(cps, pos.x, pos.z - 1)) {
++ level.putBoolean("LightPopulated", true);
++ }
++ }
++ }
++ // CraftBukkit end
++
+ if (i < 1493) {
+ compoundtag = DataFixTypes.CHUNK.update(this.fixerUpper, compoundtag, i, 1493);
+ if (compoundtag.getCompound("Level").getBoolean("hasLegacyStructureData")) {
+@@ -58,8 +108,8 @@
+ return compoundtag;
+ }
+
+- private LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey<Level> resourcekey, Supplier<DimensionDataStorage> supplier) {
+- LegacyStructureDataHandler legacystructuredatahandler = this.legacyStructureHandler;
++ private LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey<LevelStem> level, Supplier<DimensionDataStorage> storage) { // CraftBukkit
++ LegacyStructureDataHandler persistentstructurelegacy = this.legacyStructureHandler;
+
+ if (legacystructuredatahandler == null) {
+ synchronized (this) {
+@@ -73,8 +123,8 @@
+ return legacystructuredatahandler;
+ }
+
+- public static void injectDatafixingContext(CompoundTag compoundtag, ResourceKey<Level> resourcekey, Optional<ResourceKey<Codec<? extends ChunkGenerator>>> optional) {
+- CompoundTag compoundtag1 = new CompoundTag();
++ public static void injectDatafixingContext(CompoundTag chunkData, ResourceKey<LevelStem> levelKey, Optional<ResourceKey<Codec<? extends ChunkGenerator>>> chunkGeneratorKey) { // CraftBukkit
++ CompoundTag nbttagcompound1 = new CompoundTag();
+
+ compoundtag1.putString("dimension", resourcekey.location().toString());
+ optional.ifPresent((resourcekey1) -> {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/storage/RegionFile.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/storage/RegionFile.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/storage/RegionFile.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch
new file mode 100644
index 0000000000..ce9acb3765
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch
@@ -0,0 +1,72 @@
+--- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
++++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+@@ -30,8 +30,8 @@
+ this.sync = flag;
+ }
+
+- private RegionFile getRegionFile(ChunkPos chunkpos) throws IOException {
+- long i = ChunkPos.asLong(chunkpos.getRegionX(), chunkpos.getRegionZ());
++ private RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit
++ long i = ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ());
+ RegionFile regionfile = (RegionFile) this.regionCache.getAndMoveToFirst(i);
+
+ if (regionfile != null) {
+@@ -43,8 +43,9 @@
+
+ FileUtil.createDirectoriesSafe(this.folder);
+ Path path = this.folder;
+- int j = chunkpos.getRegionX();
+- Path path1 = path.resolve("r." + j + "." + chunkpos.getRegionZ() + ".mca");
++ int j = chunkcoordintpair.getRegionX();
++ Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca");
++ if (existingOnly && !java.nio.file.Files.exists(path1)) return null; // CraftBukkit
+ RegionFile regionfile1 = new RegionFile(path1, this.folder, this.sync);
+
+ this.regionCache.putAndMoveToFirst(i, regionfile1);
+@@ -53,9 +54,14 @@
+ }
+
+ @Nullable
+- public CompoundTag read(ChunkPos chunkpos) throws IOException {
+- RegionFile regionfile = this.getRegionFile(chunkpos);
+- DataInputStream datainputstream = regionfile.getChunkDataInputStream(chunkpos);
++ public CompoundTag read(ChunkPos chunkPos) throws IOException {
++ // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing
++ RegionFile regionfile = this.getRegionFile(chunkPos, true);
++ if (regionfile == null) {
++ return null;
++ }
++ // CraftBukkit end
++ DataInputStream datainputstream = regionfile.getChunkDataInputStream(chunkPos);
+
+ CompoundTag compoundtag;
+ label43:
+@@ -93,9 +99,14 @@
+ return compoundtag;
+ }
+
+- public void scanChunk(ChunkPos chunkpos, StreamTagVisitor streamtagvisitor) throws IOException {
+- RegionFile regionfile = this.getRegionFile(chunkpos);
+- DataInputStream datainputstream = regionfile.getChunkDataInputStream(chunkpos);
++ public void scanChunk(ChunkPos chunkPos, StreamTagVisitor visitor) throws IOException {
++ // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing
++ RegionFile regionfile = this.getRegionFile(chunkPos, true);
++ if (regionfile == null) {
++ return;
++ }
++ // CraftBukkit end
++ DataInputStream datainputstream = regionfile.getChunkDataInputStream(chunkPos);
+
+ try {
+ if (datainputstream != null) {
+@@ -119,8 +130,8 @@
+
+ }
+
+- protected void write(ChunkPos chunkpos, @Nullable CompoundTag compoundtag) throws IOException {
+- RegionFile regionfile = this.getRegionFile(chunkpos);
++ protected void write(ChunkPos chunkPos, @Nullable CompoundTag chunkData) throws IOException {
++ RegionFile regionfile = this.getRegionFile(chunkPos, false); // CraftBukkit
+
+ if (compoundtag == null) {
+ regionfile.clear(chunkpos);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch
new file mode 100644
index 0000000000..53b66c693e
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch
@@ -0,0 +1,46 @@
+--- a/net/minecraft/world/level/dimension/end/EndDragonFight.java
++++ b/net/minecraft/world/level/dimension/end/EndDragonFight.java
+@@ -510,7 +510,7 @@
+ return this.previouslyKilled;
+ }
+
+- public void tryRespawn() {
++ public boolean tryRespawn() { // CraftBukkit - return boolean
+ if (this.dragonKilled && this.respawnStage == null) {
+ BlockPos blockpos = this.portalLocation;
+
+@@ -537,19 +537,19 @@
+ List<EndCrystal> list1 = this.level.getEntitiesOfClass(EndCrystal.class, new AABB(blockpos1.relative(direction, 2)));
+
+ if (list1.isEmpty()) {
+- return;
++ return false; // CraftBukkit - return value
+ }
+
+ list.addAll(list1);
+ }
+
+ EndDragonFight.LOGGER.debug("Found all crystals, respawning dragon.");
+- this.respawnDragon(list);
++ return this.respawnDragon(list); // CraftBukkit - return value
+ }
+-
++ return false; // CraftBukkit - return value
+ }
+
+- private void respawnDragon(List<EndCrystal> list) {
++ public boolean respawnDragon(List<EndCrystal> list) { // CraftBukkit - return boolean
+ if (this.dragonKilled && this.respawnStage == null) {
+ for (BlockPattern.BlockPatternMatch blockpattern_blockpatternmatch = this.findExitPortal(); blockpattern_blockpatternmatch != null; blockpattern_blockpatternmatch = this.findExitPortal()) {
+ for (int i = 0; i < this.exitPortalPattern.getWidth(); ++i) {
+@@ -569,8 +569,9 @@
+ this.respawnTime = 0;
+ this.spawnExitPortal(false);
+ this.respawnCrystals = list;
++ return true; // CraftBukkit - return value
+ }
+-
++ return false; // CraftBukkit - return value
+ }
+
+ public void resetSpikeCrystals() {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/entity/PersistentEntitySectionManager.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/entity/PersistentEntitySectionManager.java.patch
new file mode 100644
index 0000000000..b7bfb6b1e1
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/entity/PersistentEntitySectionManager.java.patch
@@ -0,0 +1,110 @@
+--- a/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
++++ b/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
+@@ -31,6 +30,11 @@
+ import net.minecraft.world.entity.Entity;
+ import net.minecraft.world.level.ChunkPos;
+ import org.slf4j.Logger;
++import net.minecraft.world.level.ChunkPos;
++// CraftBukkit start
++import net.minecraft.world.level.chunk.storage.EntityStorage;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++// CraftBukkit end
+
+ public class PersistentEntitySectionManager<T extends EntityAccess> implements AutoCloseable {
+
+@@ -55,7 +59,17 @@
+ this.entityGetter = new LevelEntityGetterAdapter<>(this.visibleEntityStorage, this.sectionStorage);
+ }
+
+- void removeSectionIfEmpty(long i, EntitySection<T> entitysection) {
++ // CraftBukkit start - add method to get all entities in chunk
++ public List<Entity> getEntities(ChunkPos chunkCoordIntPair) {
++ return sectionStorage.getExistingSectionsInChunk(chunkCoordIntPair.toLong()).flatMap(EntitySection::getEntities).map(entity -> (Entity) entity).collect(Collectors.toList());
++ }
++
++ public boolean isPending(long pair) {
++ return chunkLoadStatuses.get(pair) == b.PENDING;
++ }
++ // CraftBukkit end
++
++ void removeSectionIfEmpty(long sectionKey, EntitySection<T> entitysection) {
+ if (entitysection.isEmpty()) {
+ this.sectionStorage.remove(i);
+ }
+@@ -195,10 +209,16 @@
+
+ }
+
+- private boolean storeChunkSections(long i, Consumer<T> consumer) {
+- PersistentEntitySectionManager.ChunkLoadStatus persistententitysectionmanager_chunkloadstatus = (PersistentEntitySectionManager.ChunkLoadStatus) this.chunkLoadStatuses.get(i);
++ private boolean storeChunkSections(long chunkPosValue, Consumer<T> consumer) {
++ // CraftBukkit start - add boolean for event call
++ return storeChunkSections(chunkPosValue, consumer, false);
++ }
+
+- if (persistententitysectionmanager_chunkloadstatus == PersistentEntitySectionManager.ChunkLoadStatus.PENDING) {
++ private boolean storeChunkSections(long i, Consumer<T> consumer, boolean callEvent) {
++ // CraftBukkit end
++ PersistentEntitySectionManager.b persistententitysectionmanager_b = (PersistentEntitySectionManager.b) this.chunkLoadStatuses.get(i);
++
++ if (persistententitysectionmanager_b == PersistentEntitySectionManager.b.PENDING) {
+ return false;
+ } else {
+ List<T> list = (List) this.sectionStorage.getExistingSectionsInChunk(i).flatMap((entitysection) -> {
+@@ -206,7 +226,8 @@
+ }).collect(Collectors.toList());
+
+ if (list.isEmpty()) {
+- if (persistententitysectionmanager_chunkloadstatus == PersistentEntitySectionManager.ChunkLoadStatus.LOADED) {
++ if (persistententitysectionmanager_b == PersistentEntitySectionManager.b.LOADED) {
++ if (callEvent) CraftEventFactory.callEntitiesUnloadEvent(((EntityStorage) permanentStorage).level, new ChunkPos(i), ImmutableList.of()); // CraftBukkit
+ this.permanentStorage.storeEntities(new ChunkEntities<>(new ChunkPos(i), ImmutableList.of()));
+ }
+
+@@ -215,6 +236,7 @@
+ this.requestChunkLoad(i);
+ return false;
+ } else {
++ if (callEvent) CraftEventFactory.callEntitiesUnloadEvent(((EntityStorage) permanentStorage).level, new ChunkPos(i), list.stream().map(entity -> (Entity) entity).collect(Collectors.toList())); // CraftBukkit
+ this.permanentStorage.storeEntities(new ChunkEntities<>(new ChunkPos(i), list));
+ list.forEach(consumer);
+ return true;
+@@ -238,7 +260,7 @@
+ private boolean processChunkUnload(long i) {
+ boolean flag = this.storeChunkSections(i, (entityaccess) -> {
+ entityaccess.getPassengersAndSelf().forEach(this::unloadEntity);
+- });
++ }, true); // CraftBukkit - add boolean for event call
+
+ if (!flag) {
+ return false;
+@@ -266,7 +288,11 @@
+ chunkentities.getEntities().forEach((entityaccess) -> {
+ this.addEntity(entityaccess, true);
+ });
+- this.chunkLoadStatuses.put(chunkentities.getPos().toLong(), PersistentEntitySectionManager.ChunkLoadStatus.LOADED);
++ this.chunkLoadStatuses.put(chunkentities.getPos().toLong(), PersistentEntitySectionManager.b.LOADED);
++ // CraftBukkit start - call entity load event
++ List<Entity> entities = getEntities(chunkentities.getPos());
++ CraftEventFactory.callEntitiesLoadEvent(((EntityStorage) permanentStorage).level, chunkentities.getPos(), entities);
++ // CraftBukkit end
+ }
+
+ }
+@@ -324,7 +349,15 @@
+
+ @Override
+ public void close() throws IOException {
+- this.saveAll();
++ // CraftBukkit start - add save boolean
++ close(true);
++ }
++
++ public void close(boolean save) throws IOException {
++ if (save) {
++ this.saveAll();
++ }
++ // CraftBukkit end
+ this.permanentStorage.close();
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/gameevent/GameEventDispatcher.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/gameevent/GameEventDispatcher.java.patch
new file mode 100644
index 0000000000..65e8eb295c
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/gameevent/GameEventDispatcher.java.patch
@@ -0,0 +1,48 @@
+--- a/net/minecraft/world/level/gameevent/GameEventDispatcher.java
++++ b/net/minecraft/world/level/gameevent/GameEventDispatcher.java
+@@ -10,6 +10,12 @@
+ import net.minecraft.server.level.ServerLevel;
+ import net.minecraft.world.level.chunk.LevelChunk;
+ import net.minecraft.world.phys.Vec3;
++// CraftBukkit start
++import org.bukkit.Bukkit;
++import org.bukkit.craftbukkit.CraftGameEvent;
++import org.bukkit.craftbukkit.util.CraftLocation;
++import org.bukkit.event.world.GenericGameEvent;
++// CraftBukkit end
+
+ public class GameEventDispatcher {
+
+@@ -19,15 +25,23 @@
+ this.level = serverlevel;
+ }
+
+- public void post(GameEvent gameevent, Vec3 vec3, GameEvent.Context gameevent_context) {
+- int i = gameevent.getNotificationRadius();
+- BlockPos blockpos = BlockPos.containing(vec3);
+- int j = SectionPos.blockToSectionCoord(blockpos.getX() - i);
+- int k = SectionPos.blockToSectionCoord(blockpos.getY() - i);
+- int l = SectionPos.blockToSectionCoord(blockpos.getZ() - i);
+- int i1 = SectionPos.blockToSectionCoord(blockpos.getX() + i);
+- int j1 = SectionPos.blockToSectionCoord(blockpos.getY() + i);
+- int k1 = SectionPos.blockToSectionCoord(blockpos.getZ() + i);
++ public void post(GameEvent event, Vec3 pos, GameEvent.Context context) {
++ int i = event.getNotificationRadius();
++ BlockPos blockposition = BlockPos.containing(pos);
++ // CraftBukkit start
++ GenericGameEvent event1 = new GenericGameEvent(CraftGameEvent.minecraftToBukkit(event), CraftLocation.toBukkit(blockposition, level.getWorld()), (context.sourceEntity() == null) ? null : context.sourceEntity().getBukkitEntity(), i, !Bukkit.isPrimaryThread());
++ level.getCraftServer().getPluginManager().callEvent(event1);
++ if (event1.isCancelled()) {
++ return;
++ }
++ i = event1.getRadius();
++ // CraftBukkit end
++ int j = SectionPos.blockToSectionCoord(blockposition.getX() - i);
++ int k = SectionPos.blockToSectionCoord(blockposition.getY() - i);
++ int l = SectionPos.blockToSectionCoord(blockposition.getZ() - i);
++ int i1 = SectionPos.blockToSectionCoord(blockposition.getX() + i);
++ int j1 = SectionPos.blockToSectionCoord(blockposition.getY() + i);
++ int k1 = SectionPos.blockToSectionCoord(blockposition.getZ() + i);
+ List<GameEvent.ListenerInfo> list = new ArrayList();
+ GameEventListenerRegistry.ListenerVisitor gameeventlistenerregistry_listenervisitor = (gameeventlistener, vec31) -> {
+ if (gameeventlistener.getDeliveryMode() == GameEventListener.DeliveryMode.BY_DISTANCE) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java.patch
new file mode 100644
index 0000000000..a5aa9c1f84
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java.patch
@@ -0,0 +1,33 @@
+--- a/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java
++++ b/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java
+@@ -27,6 +27,11 @@
+ import net.minecraft.world.level.gameevent.PositionSource;
+ import net.minecraft.world.phys.HitResult;
+ import net.minecraft.world.phys.Vec3;
++// CraftBukkit start
++import org.bukkit.craftbukkit.CraftGameEvent;
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.event.block.BlockReceiveGameEvent;
++// CraftBukkit end
+
+ public interface VibrationSystem {
+
+@@ -284,9 +287,15 @@
+ if (optional.isEmpty()) {
+ return false;
+ } else {
+- Vec3 vec31 = (Vec3) optional.get();
+-
+- if (!vibrationsystem_user.canReceiveVibration(serverlevel, BlockPos.containing(vec3), gameevent, gameevent_context)) {
++ Vec3 vec3d1 = (Vec3) optional.get();
++ // CraftBukkit start
++ boolean defaultCancel = !vibrationsystem_d.canReceiveVibration(level, BlockPos.containing(pos), gameEvent, context);
++ Entity entity = context.sourceEntity();
++ BlockReceiveGameEvent event = new BlockReceiveGameEvent(CraftGameEvent.minecraftToBukkit(gameEvent), CraftBlock.at(level, BlockPos.containing(vec3d1)), (entity == null) ? null : entity.getBukkitEntity());
++ event.setCancelled(defaultCancel);
++ level.getCraftServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ // CraftBukkit end
+ return false;
+ } else if (isOccluded(serverlevel, vec3, vec31)) {
+ return false;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/FlatLevelSource.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/FlatLevelSource.java.patch
new file mode 100644
index 0000000000..966130e6fd
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/FlatLevelSource.java.patch
@@ -0,0 +1,26 @@
+--- a/net/minecraft/world/level/levelgen/FlatLevelSource.java
++++ b/net/minecraft/world/level/levelgen/FlatLevelSource.java
+@@ -34,14 +34,21 @@
+ });
+ private final FlatLevelGeneratorSettings settings;
+
+- public FlatLevelSource(FlatLevelGeneratorSettings flatlevelgeneratorsettings) {
+- FixedBiomeSource fixedbiomesource = new FixedBiomeSource(flatlevelgeneratorsettings.getBiome());
++ public FlatLevelSource(FlatLevelGeneratorSettings settings) {
++ // CraftBukkit start
++ // WorldChunkManagerHell worldchunkmanagerhell = new WorldChunkManagerHell(generatorsettingsflat.getBiome());
+
+ Objects.requireNonNull(flatlevelgeneratorsettings);
+ super(fixedbiomesource, Util.memoize(flatlevelgeneratorsettings::adjustGenerationSettings));
+ this.settings = flatlevelgeneratorsettings;
+ }
+
++ public FlatLevelSource(FlatLevelGeneratorSettings generatorsettingsflat, net.minecraft.world.level.biome.BiomeSource worldchunkmanager) {
++ super(worldchunkmanager, Util.memoize(generatorsettingsflat::adjustGenerationSettings));
++ // CraftBukkit end
++ this.settings = generatorsettingsflat;
++ }
++
+ @Override
+ @Override
+ public ChunkGeneratorStructureState createState(HolderLookup<StructureSet> holderlookup, RandomState randomstate, long i) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/PatrolSpawner.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/PatrolSpawner.java.patch
new file mode 100644
index 0000000000..a83bcc06ec
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/PatrolSpawner.java.patch
@@ -0,0 +1,15 @@
+--- a/net/minecraft/world/level/levelgen/PatrolSpawner.java
++++ b/net/minecraft/world/level/levelgen/PatrolSpawner.java
+@@ -116,9 +115,9 @@
+ patrollingmonster.findPatrolTarget();
+ }
+
+- patrollingmonster.setPos((double) blockpos.getX(), (double) blockpos.getY(), (double) blockpos.getZ());
+- patrollingmonster.finalizeSpawn(serverlevel, serverlevel.getCurrentDifficultyAt(blockpos), MobSpawnType.PATROL, (SpawnGroupData) null, (CompoundTag) null);
+- serverlevel.addFreshEntityWithPassengers(patrollingmonster);
++ entitymonsterpatrolling.setPos((double) pos.getX(), (double) pos.getY(), (double) pos.getZ());
++ entitymonsterpatrolling.finalizeSpawn(level, level.getCurrentDifficultyAt(pos), EnumMobSpawn.PATROL, (GroupDataEntity) null, (CompoundTag) null);
++ level.addFreshEntityWithPassengers(entitymonsterpatrolling, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.PATROL); // CraftBukkit
+ return true;
+ } else {
+ return false;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java.patch
new file mode 100644
index 0000000000..ea4c28cdd1
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java.patch
@@ -0,0 +1,25 @@
+--- a/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java
++++ b/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java
+@@ -236,16 +236,16 @@
+ }
+ }
+
+- public static LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey<Level> resourcekey, @Nullable DimensionDataStorage dimensiondatastorage) {
+- if (resourcekey == Level.OVERWORLD) {
+- return new LegacyStructureDataHandler(dimensiondatastorage, ImmutableList.of("Monument", "Stronghold", "Village", "Mineshaft", "Temple", "Mansion"), ImmutableList.of("Village", "Mineshaft", "Mansion", "Igloo", "Desert_Pyramid", "Jungle_Pyramid", "Swamp_Hut", "Stronghold", "Monument"));
++ public static LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey<LevelStem> level, @Nullable DimensionDataStorage storage) { // CraftBukkit
++ if (level == LevelStem.OVERWORLD) { // CraftBukkit
++ return new LegacyStructureDataHandler(storage, ImmutableList.of("Monument", "Stronghold", "Village", "Mineshaft", "Temple", "Mansion"), ImmutableList.of("Village", "Mineshaft", "Mansion", "Igloo", "Desert_Pyramid", "Jungle_Pyramid", "Swamp_Hut", "Stronghold", "Monument"));
+ } else {
+ ImmutableList immutablelist;
+
+- if (resourcekey == Level.NETHER) {
++ if (level == LevelStem.NETHER) { // CraftBukkit
+ immutablelist = ImmutableList.of("Fortress");
+- return new LegacyStructureDataHandler(dimensiondatastorage, immutablelist, immutablelist);
+- } else if (resourcekey == Level.END) {
++ return new LegacyStructureDataHandler(storage, immutablelist, immutablelist);
++ } else if (level == LevelStem.END) { // CraftBukkit
+ immutablelist = ImmutableList.of("EndCity");
+ return new LegacyStructureDataHandler(dimensiondatastorage, immutablelist, immutablelist);
+ } else {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/StructurePiece.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/StructurePiece.java.patch
new file mode 100644
index 0000000000..f7502c633a
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/StructurePiece.java.patch
@@ -0,0 +1,128 @@
+--- a/net/minecraft/world/level/levelgen/structure/StructurePiece.java
++++ b/net/minecraft/world/level/levelgen/structure/StructurePiece.java
+@@ -67,7 +61,8 @@
+ this(structurepiecetype, i, (BoundingBox) dataresult.resultOrPartial(logger::error).orElseThrow(() -> {
+ return new IllegalArgumentException("Invalid boundingbox");
+ }));
+- int j = compoundtag.getInt("O");
++ // CraftBukkit end
++ int j = tag.getInt("O");
+
+ this.setOrientation(j == -1 ? null : Direction.from2DDataValue(j));
+ }
+@@ -91,7 +83,8 @@
+ dataresult.resultOrPartial(logger::error).ifPresent((tag) -> {
+ compoundtag.put("BB", tag);
+ });
+- Direction direction = this.getOrientation();
++ // CraftBukkit end
++ Direction enumdirection = this.getOrientation();
+
+ compoundtag.putInt("O", direction == null ? -1 : direction.get2DDataValue());
+ compoundtag.putInt("GD", this.genDepth);
+@@ -189,8 +182,13 @@
+ blockstate = blockstate.rotate(this.rotation);
+ }
+
+- worldgenlevel.setBlock(blockpos_mutableblockpos, blockstate, 2);
+- FluidState fluidstate = worldgenlevel.getFluidState(blockpos_mutableblockpos);
++ level.setBlock(blockposition_mutableblockposition, blockstate, 2);
++ // CraftBukkit start - fluid handling is already done if we have a transformer generator access
++ if (level instanceof org.bukkit.craftbukkit.util.TransformerGeneratorAccess) {
++ return;
++ }
++ // CraftBukkit end
++ FluidState fluid = level.getFluidState(blockposition_mutableblockposition);
+
+ if (!fluidstate.isEmpty()) {
+ worldgenlevel.scheduleTick(blockpos_mutableblockpos, fluidstate.getType(), 0);
+@@ -204,7 +202,39 @@
+ }
+ }
+
+- protected boolean canBeReplaced(LevelReader levelreader, int i, int j, int k, BoundingBox boundingbox) {
++ // CraftBukkit start
++ protected boolean placeCraftBlockEntity(ServerLevelAccessor worldAccess, BlockPos position, org.bukkit.craftbukkit.block.CraftBlockEntityState<?> craftBlockEntityState, int i) {
++ if (worldAccess instanceof org.bukkit.craftbukkit.util.TransformerGeneratorAccess transformerAccess) {
++ return transformerAccess.setCraftBlock(position, craftBlockEntityState, i);
++ }
++ boolean result = worldAccess.setBlock(position, craftBlockEntityState.getHandle(), i);
++ BlockEntity tileEntity = worldAccess.getBlockEntity(position);
++ if (tileEntity != null) {
++ tileEntity.load(craftBlockEntityState.getSnapshotNBT());
++ }
++ return result;
++ }
++
++ protected void placeCraftSpawner(ServerLevelAccessor worldAccess, BlockPos position, org.bukkit.entity.EntityType entityType, int i) {
++ // This method is used in structures that are generated by code and place spawners as they set the entity after the block was placed making it impossible for plugins to access that information
++ org.bukkit.craftbukkit.block.CraftCreatureSpawner spawner = (org.bukkit.craftbukkit.block.CraftCreatureSpawner) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(position, Blocks.SPAWNER.defaultBlockState(), null);
++ spawner.setSpawnedType(entityType);
++ placeCraftBlockEntity(worldAccess, position, spawner, i);
++ }
++
++ protected void setCraftLootTable(ServerLevelAccessor worldAccess, BlockPos position, RandomSource randomSource, net.minecraft.resources.ResourceLocation loottableKey) {
++ // This method is used in structures that use data markers to a loot table to loot containers as otherwise plugins won't have access to that information.
++ net.minecraft.world.level.block.entity.BlockEntity tileEntity = worldAccess.getBlockEntity(position);
++ if (tileEntity instanceof net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity tileEntityLootable) {
++ tileEntityLootable.setLootTable(loottableKey, randomSource.nextLong());
++ if (worldAccess instanceof org.bukkit.craftbukkit.util.TransformerGeneratorAccess transformerAccess) {
++ transformerAccess.setCraftBlock(position, (org.bukkit.craftbukkit.block.CraftBlockState) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(position, tileEntity.getBlockState(), tileEntityLootable.saveWithFullMetadata()), 3);
++ }
++ }
++ }
++ // CraftBukkit end
++
++ protected boolean canBeReplaced(LevelReader level, int x, int y, int z, BoundingBox box) {
+ return true;
+ }
+
+@@ -397,12 +427,20 @@
+ blockstate = reorient(serverlevelaccessor, blockpos, Blocks.CHEST.defaultBlockState());
+ }
+
+- serverlevelaccessor.setBlock(blockpos, blockstate, 2);
+- BlockEntity blockentity = serverlevelaccessor.getBlockEntity(blockpos);
++ // CraftBukkit start
++ /*
++ worldaccess.setBlock(blockposition, iblockdata, 2);
++ TileEntity tileentity = worldaccess.getBlockEntity(blockposition);
+
+ if (blockentity instanceof ChestBlockEntity) {
+ ((ChestBlockEntity) blockentity).setLootTable(resourcelocation, randomsource.nextLong());
+ }
++ */
++ org.bukkit.craftbukkit.block.CraftChest chestState = (org.bukkit.craftbukkit.block.CraftChest) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(pos, state, null);
++ chestState.setLootTable(org.bukkit.Bukkit.getLootTable(org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(lootTable)));
++ chestState.setSeed(random.nextLong());
++ placeCraftBlockEntity(level, pos, chestState, 2);
++ // CraftBukkit end
+
+ return true;
+ } else {
+@@ -413,14 +451,22 @@
+ protected boolean createDispenser(WorldGenLevel worldgenlevel, BoundingBox boundingbox, RandomSource randomsource, int i, int j, int k, Direction direction, ResourceLocation resourcelocation) {
+ BlockPos.MutableBlockPos blockpos_mutableblockpos = this.getWorldPos(i, j, k);
+
+- if (boundingbox.isInside(blockpos_mutableblockpos) && !worldgenlevel.getBlockState(blockpos_mutableblockpos).is(Blocks.DISPENSER)) {
+- this.placeBlock(worldgenlevel, (BlockState) Blocks.DISPENSER.defaultBlockState().setValue(DispenserBlock.FACING, direction), i, j, k, boundingbox);
+- BlockEntity blockentity = worldgenlevel.getBlockEntity(blockpos_mutableblockpos);
++ if (box.isInside(blockposition_mutableblockposition) && !level.getBlockState(blockposition_mutableblockposition).is(Blocks.DISPENSER)) {
++ // CraftBukkit start
++ /*
++ this.placeBlock(generatoraccessseed, (IBlockData) Blocks.DISPENSER.defaultBlockState().setValue(BlockDispenser.FACING, enumdirection), i, j, k, structureboundingbox);
++ TileEntity tileentity = generatoraccessseed.getBlockEntity(blockposition_mutableblockposition);
+
+ if (blockentity instanceof DispenserBlockEntity) {
+ ((DispenserBlockEntity) blockentity).setLootTable(resourcelocation, randomsource.nextLong());
+ }
+
++ org.bukkit.craftbukkit.block.CraftDispenser dispenserState = (org.bukkit.craftbukkit.block.CraftDispenser) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(blockposition_mutableblockposition, iblockdata, null);
++ dispenserState.setLootTable(org.bukkit.Bukkit.getLootTable(org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(lootTable)));
++ dispenserState.setSeed(random.nextLong());
++ placeCraftBlockEntity(level, blockposition_mutableblockposition, dispenserState, 2);
++ // CraftBukkit end
++
+ return true;
+ } else {
+ return false;
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/StructureStart.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/StructureStart.java.patch
new file mode 100644
index 0000000000..4b625628e7
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/StructureStart.java.patch
@@ -0,0 +1,44 @@
+--- a/net/minecraft/world/level/levelgen/structure/StructureStart.java
++++ b/net/minecraft/world/level/levelgen/structure/StructureStart.java
+@@ -31,6 +31,7 @@
+ private int references;
+ @Nullable
+ private volatile BoundingBox cachedBoundingBox;
++ public org.bukkit.event.world.AsyncStructureGenerateEvent.Cause generationEventCause = org.bukkit.event.world.AsyncStructureGenerateEvent.Cause.WORLD_GENERATION; // CraftBukkit
+
+ public StructureStart(Structure structure, ChunkPos chunkpos, int i, PiecesContainer piecescontainer) {
+ this.structure = structure;
+@@ -88,9 +89,11 @@
+ List<StructurePiece> list = this.pieceContainer.pieces();
+
+ if (!list.isEmpty()) {
+- BoundingBox boundingbox1 = ((StructurePiece) list.get(0)).boundingBox;
+- BlockPos blockpos = boundingbox1.getCenter();
+- BlockPos blockpos1 = new BlockPos(blockpos.getX(), boundingbox1.minY(), blockpos.getZ());
++ BoundingBox structureboundingbox1 = ((StructurePiece) list.get(0)).boundingBox;
++ BlockPos blockposition = structureboundingbox1.getCenter();
++ BlockPos blockposition1 = new BlockPos(blockposition.getX(), structureboundingbox1.minY(), blockposition.getZ());
++ // CraftBukkit start
++ /*
+ Iterator iterator = list.iterator();
+
+ while (iterator.hasNext()) {
+@@ -100,6 +103,18 @@
+ structurepiece.postProcess(worldgenlevel, structuremanager, chunkgenerator, randomsource, boundingbox, chunkpos, blockpos1);
+ }
+ }
++ */
++ List<StructurePiece> pieces = list.stream().filter(piece -> piece.getBoundingBox().intersects(box)).toList();
++ if (!pieces.isEmpty()) {
++ org.bukkit.craftbukkit.util.TransformerGeneratorAccess transformerAccess = new org.bukkit.craftbukkit.util.TransformerGeneratorAccess();
++ transformerAccess.setHandle(level);
++ transformerAccess.setStructureTransformer(new org.bukkit.craftbukkit.util.CraftStructureTransformer(generationEventCause, level, structureManager, structure, box, chunkPos));
++ for (StructurePiece piece : pieces) {
++ piece.postProcess(transformerAccess, structureManager, generator, random, box, chunkPos, blockposition1);
++ }
++ transformerAccess.getStructureTransformer().discard();
++ }
++ // CraftBukkit end
+
+ this.structure.afterPlace(worldgenlevel, structuremanager, chunkgenerator, randomsource, boundingbox, chunkpos, this.pieceContainer);
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java.patch
new file mode 100644
index 0000000000..c20ca612c5
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java.patch
@@ -0,0 +1,28 @@
+--- a/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java
++++ b/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java
+@@ -69,11 +68,20 @@
+
+ }
+
+- private static void placeSuspiciousSand(BoundingBox boundingbox, WorldGenLevel worldgenlevel, BlockPos blockpos) {
+- if (boundingbox.isInside(blockpos)) {
+- worldgenlevel.setBlock(blockpos, Blocks.SUSPICIOUS_SAND.defaultBlockState(), 2);
+- worldgenlevel.getBlockEntity(blockpos, BlockEntityType.BRUSHABLE_BLOCK).ifPresent((brushableblockentity) -> {
+- brushableblockentity.setLootTable(BuiltInLootTables.DESERT_PYRAMID_ARCHAEOLOGY, blockpos.asLong());
++ private static void placeSuspiciousSand(BoundingBox boundingBox, WorldGenLevel worldGenLevel, BlockPos pos) {
++ if (boundingBox.isInside(pos)) {
++ // CraftBukkit start
++ if (worldGenLevel instanceof org.bukkit.craftbukkit.util.TransformerGeneratorAccess transformerAccess) {
++ org.bukkit.craftbukkit.block.CraftBrushableBlock brushableState = (org.bukkit.craftbukkit.block.CraftBrushableBlock) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(pos, Blocks.SUSPICIOUS_SAND.defaultBlockState(), null);
++ brushableState.setLootTable(org.bukkit.Bukkit.getLootTable(org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(BuiltInLootTables.DESERT_PYRAMID_ARCHAEOLOGY)));
++ brushableState.setSeed(pos.asLong());
++ transformerAccess.setCraftBlock(pos, brushableState, 2);
++ return;
++ }
++ // CraftBukkit end
++ worldGenLevel.setBlock(pos, Blocks.SUSPICIOUS_SAND.defaultBlockState(), 2);
++ worldGenLevel.getBlockEntity(pos, BlockEntityType.BRUSHABLE_BLOCK).ifPresent((brushableblockentity) -> {
++ brushableblockentity.setLootTable(BuiltInLootTables.DESERT_PYRAMID_ARCHAEOLOGY, pos.asLong());
+ });
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java.patch
new file mode 100644
index 0000000000..ebf61ce987
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java.patch
@@ -0,0 +1,18 @@
+--- a/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java
++++ b/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java
+@@ -294,8 +283,13 @@
+ if (s.startsWith("Chest")) {
+ BlockPos blockpos1 = blockpos.below();
+
+- if (boundingbox.isInside(blockpos1)) {
+- RandomizableContainer.setBlockEntityLootTable(serverlevelaccessor, randomsource, blockpos1, BuiltInLootTables.END_CITY_TREASURE);
++ if (box.isInside(blockposition1)) {
++ // CraftBukkit start - ensure block transformation
++ /*
++ RandomizableContainer.setBlockEntityLootTable(worldaccess, randomsource, blockposition1, LootTables.END_CITY_TREASURE);
++ */
++ setCraftLootTable(level, blockposition1, random, BuiltInLootTables.END_CITY_TREASURE);
++ // CraftBukkit end
+ }
+ } else if (boundingbox.isInside(blockpos) && Level.isInSpawnableBounds(blockpos)) {
+ if (s.startsWith("Sentry")) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java.patch
new file mode 100644
index 0000000000..5eed6bf146
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java.patch
@@ -0,0 +1,27 @@
+--- a/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java
++++ b/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java
+@@ -83,15 +80,19 @@
+ }
+
+ @Override
+- @Override
+- protected void handleDataMarker(String s, BlockPos blockpos, ServerLevelAccessor serverlevelaccessor, RandomSource randomsource, BoundingBox boundingbox) {
+- if ("chest".equals(s)) {
+- serverlevelaccessor.setBlock(blockpos, Blocks.AIR.defaultBlockState(), 3);
+- BlockEntity blockentity = serverlevelaccessor.getBlockEntity(blockpos.below());
++ protected void handleDataMarker(String name, BlockPos pos, ServerLevelAccessor level, RandomSource random, BoundingBox box) {
++ if ("chest".equals(name)) {
++ level.setBlock(pos, Blocks.AIR.defaultBlockState(), 3);
++ // CraftBukkit start - ensure block transformation
++ /*
++ TileEntity tileentity = worldaccess.getBlockEntity(blockposition.below());
+
+ if (blockentity instanceof ChestBlockEntity) {
+ ((ChestBlockEntity) blockentity).setLootTable(BuiltInLootTables.IGLOO_CHEST, randomsource.nextLong());
+ }
++ */
++ setCraftLootTable(level, pos.below(), random, BuiltInLootTables.IGLOO_CHEST);
++ // CraftBukkit end
+
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java.patch
new file mode 100644
index 0000000000..01a9c37ee1
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java.patch
@@ -0,0 +1,24 @@
+--- a/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java
++++ b/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java
+@@ -523,14 +512,19 @@
+
+ if (boundingbox.isInside(blockpos_mutableblockpos) && this.isInterior(worldgenlevel, 1, 0, l, boundingbox)) {
+ this.hasPlacedSpider = true;
+- worldgenlevel.setBlock(blockpos_mutableblockpos, Blocks.SPAWNER.defaultBlockState(), 2);
+- BlockEntity blockentity = worldgenlevel.getBlockEntity(blockpos_mutableblockpos);
++ // CraftBukkit start
++ /*
++ generatoraccessseed.setBlock(blockposition_mutableblockposition, Blocks.SPAWNER.defaultBlockState(), 2);
++ TileEntity tileentity = generatoraccessseed.getBlockEntity(blockposition_mutableblockposition);
+
+ if (blockentity instanceof SpawnerBlockEntity) {
+ SpawnerBlockEntity spawnerblockentity = (SpawnerBlockEntity) blockentity;
+
+ spawnerblockentity.setEntityId(EntityType.CAVE_SPIDER, randomsource);
+ }
++ */
++ placeCraftSpawner(level, blockposition_mutableblockposition, org.bukkit.entity.EntityType.CAVE_SPIDER, 2);
++ // CraftBukkit end
+ }
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java.patch
new file mode 100644
index 0000000000..6fdb04621d
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java.patch
@@ -0,0 +1,24 @@
+--- a/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java
++++ b/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java
+@@ -438,14 +425,19 @@
+
+ if (boundingbox.isInside(blockpos_mutableblockpos)) {
+ this.hasPlacedSpawner = true;
+- worldgenlevel.setBlock(blockpos_mutableblockpos, Blocks.SPAWNER.defaultBlockState(), 2);
+- BlockEntity blockentity = worldgenlevel.getBlockEntity(blockpos_mutableblockpos);
++ // CraftBukkit start
++ /*
++ generatoraccessseed.setBlock(blockposition_mutableblockposition, Blocks.SPAWNER.defaultBlockState(), 2);
++ TileEntity tileentity = generatoraccessseed.getBlockEntity(blockposition_mutableblockposition);
+
+ if (blockentity instanceof SpawnerBlockEntity) {
+ SpawnerBlockEntity spawnerblockentity = (SpawnerBlockEntity) blockentity;
+
+ spawnerblockentity.setEntityId(EntityType.BLAZE, randomsource);
+ }
++ */
++ placeCraftSpawner(level, blockposition_mutableblockposition, org.bukkit.entity.EntityType.BLAZE, 2);
++ // CraftBukkit end
+ }
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java.patch
new file mode 100644
index 0000000000..26ca09acac
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java.patch
@@ -0,0 +1,34 @@
+--- a/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java
++++ b/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java
+@@ -197,17 +194,24 @@
+ }
+
+ @Override
+- @Override
+- protected void handleDataMarker(String s, BlockPos blockpos, ServerLevelAccessor serverlevelaccessor, RandomSource randomsource, BoundingBox boundingbox) {
+- if ("chest".equals(s)) {
+- serverlevelaccessor.setBlock(blockpos, (BlockState) Blocks.CHEST.defaultBlockState().setValue(ChestBlock.WATERLOGGED, serverlevelaccessor.getFluidState(blockpos).is(FluidTags.WATER)), 2);
+- BlockEntity blockentity = serverlevelaccessor.getBlockEntity(blockpos);
++ protected void handleDataMarker(String name, BlockPos pos, ServerLevelAccessor level, RandomSource random, BoundingBox box) {
++ if ("chest".equals(name)) {
++ // CraftBukkit start - transform block to ensure loot table is accessible
++ /*
++ worldaccess.setBlock(blockposition, (IBlockData) Blocks.CHEST.defaultBlockState().setValue(BlockChest.WATERLOGGED, worldaccess.getFluidState(blockposition).is(TagsFluid.WATER)), 2);
++ TileEntity tileentity = worldaccess.getBlockEntity(blockposition);
+
+ if (blockentity instanceof ChestBlockEntity) {
+ ((ChestBlockEntity) blockentity).setLootTable(this.isLarge ? BuiltInLootTables.UNDERWATER_RUIN_BIG : BuiltInLootTables.UNDERWATER_RUIN_SMALL, randomsource.nextLong());
+ }
+- } else if ("drowned".equals(s)) {
+- Drowned drowned = (Drowned) EntityType.DROWNED.create(serverlevelaccessor.getLevel());
++ */
++ org.bukkit.craftbukkit.block.CraftChest craftChest = (org.bukkit.craftbukkit.block.CraftChest) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(pos, Blocks.CHEST.defaultBlockState().setValue(ChestBlock.WATERLOGGED, level.getFluidState(pos).is(FluidTags.WATER)), null);
++ craftChest.setSeed(random.nextLong());
++ craftChest.setLootTable(org.bukkit.Bukkit.getLootTable(org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(this.isLarge ? BuiltInLootTables.UNDERWATER_RUIN_BIG : BuiltInLootTables.UNDERWATER_RUIN_SMALL)));
++ placeCraftBlockEntity(level, pos, craftChest, 2);
++ // CraftBukkit end
++ } else if ("drowned".equals(name)) {
++ Drowned entitydrowned = (Drowned) EntityType.DROWNED.create(level.getLevel());
+
+ if (drowned != null) {
+ drowned.setPersistenceRequired();
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java.patch
new file mode 100644
index 0000000000..118134ef2a
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java.patch
@@ -0,0 +1,33 @@
+--- a/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java
++++ b/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java
+@@ -55,7 +50,7 @@
+ public boolean doPlace(int i) {
+ return super.doPlace(i) && i > 5;
+ }
+- }};
++ } }; // CraftBukkit - fix decompile styling
+ private static List<StrongholdPieces.PieceWeight> currentPieces;
+ static Class<? extends StrongholdPieces.StrongholdPiece> imposedPiece;
+ private static int totalWeight;
+@@ -1166,14 +1133,19 @@
+
+ if (boundingbox.isInside(blockpos_mutableblockpos)) {
+ this.hasPlacedSpawner = true;
+- worldgenlevel.setBlock(blockpos_mutableblockpos, Blocks.SPAWNER.defaultBlockState(), 2);
+- BlockEntity blockentity = worldgenlevel.getBlockEntity(blockpos_mutableblockpos);
++ // CraftBukkit start
++ /*
++ generatoraccessseed.setBlock(blockposition_mutableblockposition, Blocks.SPAWNER.defaultBlockState(), 2);
++ TileEntity tileentity = generatoraccessseed.getBlockEntity(blockposition_mutableblockposition);
+
+ if (blockentity instanceof SpawnerBlockEntity) {
+ SpawnerBlockEntity spawnerblockentity = (SpawnerBlockEntity) blockentity;
+
+ spawnerblockentity.setEntityId(EntityType.SILVERFISH, randomsource);
+ }
++ */
++ placeCraftSpawner(level, blockposition_mutableblockposition, org.bukkit.entity.EntityType.SILVERFISH, 2);
++ // CraftBukkit end
+ }
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java.patch
new file mode 100644
index 0000000000..b2b2723fc8
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java.patch
@@ -0,0 +1,36 @@
+--- a/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java
++++ b/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java
+@@ -98,11 +96,11 @@
+ this.spawnedWitch = true;
+ Witch witch = (Witch) EntityType.WITCH.create(worldgenlevel.getLevel());
+
+- if (witch != null) {
+- witch.setPersistenceRequired();
+- witch.moveTo((double) blockpos_mutableblockpos.getX() + 0.5D, (double) blockpos_mutableblockpos.getY(), (double) blockpos_mutableblockpos.getZ() + 0.5D, 0.0F, 0.0F);
+- witch.finalizeSpawn(worldgenlevel, worldgenlevel.getCurrentDifficultyAt(blockpos_mutableblockpos), MobSpawnType.STRUCTURE, (SpawnGroupData) null, (CompoundTag) null);
+- worldgenlevel.addFreshEntityWithPassengers(witch);
++ if (entitywitch != null) {
++ entitywitch.setPersistenceRequired();
++ entitywitch.moveTo((double) blockposition_mutableblockposition.getX() + 0.5D, (double) blockposition_mutableblockposition.getY(), (double) blockposition_mutableblockposition.getZ() + 0.5D, 0.0F, 0.0F);
++ entitywitch.finalizeSpawn(level, level.getCurrentDifficultyAt(blockposition_mutableblockposition), EnumMobSpawn.STRUCTURE, (GroupDataEntity) null, (CompoundTag) null);
++ level.addFreshEntityWithPassengers(entitywitch, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN); // CraftBukkit - add SpawnReason
+ }
+ }
+ }
+@@ -119,11 +117,11 @@
+ this.spawnedCat = true;
+ Cat cat = (Cat) EntityType.CAT.create(serverlevelaccessor.getLevel());
+
+- if (cat != null) {
+- cat.setPersistenceRequired();
+- cat.moveTo((double) blockpos_mutableblockpos.getX() + 0.5D, (double) blockpos_mutableblockpos.getY(), (double) blockpos_mutableblockpos.getZ() + 0.5D, 0.0F, 0.0F);
+- cat.finalizeSpawn(serverlevelaccessor, serverlevelaccessor.getCurrentDifficultyAt(blockpos_mutableblockpos), MobSpawnType.STRUCTURE, (SpawnGroupData) null, (CompoundTag) null);
+- serverlevelaccessor.addFreshEntityWithPassengers(cat);
++ if (entitycat != null) {
++ entitycat.setPersistenceRequired();
++ entitycat.moveTo((double) blockposition_mutableblockposition.getX() + 0.5D, (double) blockposition_mutableblockposition.getY(), (double) blockposition_mutableblockposition.getZ() + 0.5D, 0.0F, 0.0F);
++ entitycat.finalizeSpawn(level, level.getCurrentDifficultyAt(blockposition_mutableblockposition), EnumMobSpawn.STRUCTURE, (GroupDataEntity) null, (CompoundTag) null);
++ level.addFreshEntityWithPassengers(entitycat, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN); // CraftBukkit - add SpawnReason
+ }
+ }
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java.patch
new file mode 100644
index 0000000000..33369a7692
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java.patch
@@ -0,0 +1,25 @@
+--- a/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java
++++ b/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java
+@@ -22,7 +22,7 @@
+ private boolean keepLiquids;
+ @Nullable
+ private RandomSource random;
+- private int palette;
++ public int palette = -1; // CraftBukkit - Set initial value so we know if the palette has been set forcefully
+ private final List<StructureProcessor> processors;
+ private boolean knownShape;
+ private boolean finalizeEntities;
+@@ -149,6 +149,13 @@
+
+ if (i == 0) {
+ throw new IllegalStateException("No palettes");
++ // CraftBukkit start
++ } else if (this.palette > 0) {
++ if (this.palette >= i) {
++ throw new IllegalArgumentException("Palette index out of bounds. Got " + this.palette + " where there are only " + i + " palettes available.");
++ }
++ return palettes.get(this.palette);
++ // CraftBukkit end
+ } else {
+ return (StructureTemplate.Palette) list.get(this.getRandom(blockpos).nextInt(i));
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java.patch
new file mode 100644
index 0000000000..9d43b91b36
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java.patch
@@ -0,0 +1,157 @@
+--- a/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java
++++ b/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java
+@@ -51,6 +52,9 @@
+ import net.minecraft.world.phys.Vec3;
+ import net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape;
+ import net.minecraft.world.phys.shapes.DiscreteVoxelShape;
++import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer;
++import org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry;
++// CraftBukkit end
+
+ public class StructureTemplate {
+
+@@ -70,6 +74,11 @@
+ private Vec3i size;
+ private String author;
+
++ // CraftBukkit start - data containers
++ private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry();
++ public CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(DATA_TYPE_REGISTRY);
++ // CraftBukkit end
++
+ public StructureTemplate() {
+ this.size = Vec3i.ZERO;
+ this.author = "?";
+@@ -229,7 +238,20 @@
+ if (this.palettes.isEmpty()) {
+ return false;
+ } else {
+- List<StructureTemplate.StructureBlockInfo> list = structureplacesettings.getRandomPalette(this.palettes, blockpos).blocks();
++ // CraftBukkit start
++ // We only want the TransformerGeneratorAccess at certain locations because in here are many "block update" calls that shouldn't be transformed
++ ServerLevelAccessor wrappedAccess = serverLevel;
++ org.bukkit.craftbukkit.util.CraftStructureTransformer structureTransformer = null;
++ if (wrappedAccess instanceof org.bukkit.craftbukkit.util.TransformerGeneratorAccess transformerAccess) {
++ serverLevel = transformerAccess.getHandle();
++ structureTransformer = transformerAccess.getStructureTransformer();
++ // The structureTransformer is not needed if we can not transform blocks therefore we can save a little bit of performance doing this
++ if (structureTransformer != null && !structureTransformer.canTransformBlocks()) {
++ structureTransformer = null;
++ }
++ }
++ // CraftBukkit end
++ List<StructureTemplate.StructureBlockInfo> list = settings.getRandomPalette(this.palettes, offset).blocks();
+
+ if ((!list.isEmpty() || !structureplacesettings.isIgnoreEntities() && !this.entityInfoList.isEmpty()) && this.size.getX() >= 1 && this.size.getY() >= 1 && this.size.getZ() >= 1) {
+ BoundingBox boundingbox = structureplacesettings.getBoundingBox();
+@@ -260,20 +282,34 @@
+ Clearable.tryClear(blockentity);
+ serverlevelaccessor.setBlock(blockpos2, Blocks.BARRIER.defaultBlockState(), 20);
+ }
++ // CraftBukkit start
++ if (structureTransformer != null) {
++ org.bukkit.craftbukkit.block.CraftBlockState craftBlockState = (org.bukkit.craftbukkit.block.CraftBlockState) org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(blockposition2, iblockdata, null);
++ if (definedstructure_blockinfo.nbt != null && craftBlockState instanceof org.bukkit.craftbukkit.block.CraftBlockEntityState<?> entityState) {
++ entityState.loadData(definedstructure_blockinfo.nbt);
++ if (craftBlockState instanceof org.bukkit.craftbukkit.block.CraftLootable<?> craftLootable) {
++ craftLootable.setSeed(random.nextLong());
++ }
++ }
++ craftBlockState = structureTransformer.transformCraftState(craftBlockState);
++ iblockdata = craftBlockState.getHandle();
++ definedstructure_blockinfo = new StructureTemplate.StructureBlockInfo(blockposition2, iblockdata, (craftBlockState instanceof org.bukkit.craftbukkit.block.CraftBlockEntityState<?> craftBlockEntityState ? craftBlockEntityState.getSnapshotNBT() : null));
++ }
++ // CraftBukkit end
+
+- if (serverlevelaccessor.setBlock(blockpos2, blockstate, i)) {
+- j = Math.min(j, blockpos2.getX());
+- k = Math.min(k, blockpos2.getY());
+- l = Math.min(l, blockpos2.getZ());
+- i1 = Math.max(i1, blockpos2.getX());
+- j1 = Math.max(j1, blockpos2.getY());
+- k1 = Math.max(k1, blockpos2.getZ());
+- list3.add(Pair.of(blockpos2, structuretemplate_structureblockinfo.nbt));
+- if (structuretemplate_structureblockinfo.nbt != null) {
+- blockentity = serverlevelaccessor.getBlockEntity(blockpos2);
+- if (blockentity != null) {
+- if (blockentity instanceof RandomizableContainer) {
+- structuretemplate_structureblockinfo.nbt.putLong("LootTableSeed", randomsource.nextLong());
++ if (serverLevel.setBlock(blockposition2, iblockdata, flags)) {
++ j = Math.min(j, blockposition2.getX());
++ k = Math.min(k, blockposition2.getY());
++ l = Math.min(l, blockposition2.getZ());
++ i1 = Math.max(i1, blockposition2.getX());
++ j1 = Math.max(j1, blockposition2.getY());
++ k1 = Math.max(k1, blockposition2.getZ());
++ list3.add(Pair.of(blockposition2, definedstructure_blockinfo.nbt));
++ if (definedstructure_blockinfo.nbt != null) {
++ tileentity = serverLevel.getBlockEntity(blockposition2);
++ if (tileentity != null) {
++ if (structureTransformer == null && tileentity instanceof RandomizableContainer) { // CraftBukkit - only process if don't have a transformer access (Was already set above) - SPIGOT-7520: Use structureTransformer as check, so that it is the same as above
++ definedstructure_blockinfo.nbt.putLong("LootTableSeed", random.nextLong());
+ }
+
+ blockentity.load(structuretemplate_structureblockinfo.nbt);
+@@ -376,8 +412,8 @@
+ }
+ }
+
+- if (!structureplacesettings.isIgnoreEntities()) {
+- this.placeEntities(serverlevelaccessor, blockpos, structureplacesettings.getMirror(), structureplacesettings.getRotation(), structureplacesettings.getRotationPivot(), boundingbox, structureplacesettings.shouldFinalizeEntities());
++ if (!settings.isIgnoreEntities()) {
++ this.placeEntities(wrappedAccess, offset, settings.getMirror(), settings.getRotation(), settings.getRotationPivot(), structureboundingbox, settings.shouldFinalizeEntities()); // CraftBukkit
+ }
+
+ return true;
+@@ -471,12 +507,14 @@
+
+ }
+
+- private static Optional<Entity> createEntityIgnoreException(ServerLevelAccessor serverlevelaccessor, CompoundTag compoundtag) {
+- try {
+- return EntityType.create(compoundtag, serverlevelaccessor.getLevel());
+- } catch (Exception exception) {
+- return Optional.empty();
+- }
++ private static Optional<Entity> createEntityIgnoreException(ServerLevelAccessor level, CompoundTag tag) {
++ // CraftBukkit start
++ // try {
++ return EntityType.create(tag, level.getLevel());
++ // } catch (Exception exception) {
++ // return Optional.empty();
++ // }
++ // CraftBukkit end
+ }
+
+ public Vec3i getSize(Rotation rotation) {
+@@ -688,9 +726,14 @@
+ }
+ }
+
+- compoundtag.put("entities", listtag3);
+- compoundtag.put("size", this.newIntegerList(this.size.getX(), this.size.getY(), this.size.getZ()));
+- return NbtUtils.addCurrentDataVersion(compoundtag);
++ tag.put("entities", nbttaglist3);
++ tag.put("size", this.newIntegerList(this.size.getX(), this.size.getY(), this.size.getZ()));
++ // CraftBukkit start - PDC
++ if (!this.persistentDataContainer.isEmpty()) {
++ tag.put("BukkitValues", this.persistentDataContainer.toTagCompound());
++ }
++ // CraftBukkit end
++ return NbtUtils.addCurrentDataVersion(tag);
+ }
+
+ public void load(HolderGetter<Block> holdergetter, CompoundTag compoundtag) {
+@@ -729,6 +772,12 @@
+ }
+ }
+
++ // CraftBukkit start - PDC
++ Tag base = tag.get("BukkitValues");
++ if (base instanceof CompoundTag) {
++ this.persistentDataContainer.putAll((CompoundTag) base);
++ }
++ // CraftBukkit end
+ }
+
+ private void loadPalette(HolderGetter<Block> holdergetter, ListTag listtag, ListTag listtag1) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/material/FlowingFluid.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/material/FlowingFluid.java.patch
new file mode 100644
index 0000000000..2ad5f6d0ef
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/material/FlowingFluid.java.patch
@@ -0,0 +1,104 @@
+--- a/net/minecraft/world/level/material/FlowingFluid.java
++++ b/net/minecraft/world/level/material/FlowingFluid.java
+@@ -31,6 +31,14 @@
+ import net.minecraft.world.phys.Vec3;
+ import net.minecraft.world.phys.shapes.Shapes;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++// CraftBukkit start
++import org.bukkit.block.BlockFace;
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.craftbukkit.block.data.CraftBlockData;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.block.BlockFromToEvent;
++import org.bukkit.event.block.FluidLevelChangeEvent;
++// CraftBukkit end
+
+ public abstract class FlowingFluid extends Fluid {
+
+@@ -134,13 +139,22 @@
+ BlockState blockstate1 = level.getBlockState(blockpos1);
+ FluidState fluidstate1 = this.getNewLiquid(level, blockpos1, blockstate1);
+
+- if (this.canSpreadTo(level, blockpos, blockstate, Direction.DOWN, blockpos1, blockstate1, level.getFluidState(blockpos1), fluidstate1.getType())) {
+- this.spreadTo(level, blockpos1, blockstate1, Direction.DOWN, fluidstate1);
+- if (this.sourceNeighborCount(level, blockpos) >= 3) {
+- this.spreadToSides(level, blockpos, fluidstate, blockstate);
++ if (this.canSpreadTo(level, pos, iblockdata, Direction.DOWN, blockposition1, iblockdata1, level.getFluidState(blockposition1), fluid1.getType())) {
++ // CraftBukkit start
++ org.bukkit.block.Block source = CraftBlock.at(level, pos);
++ BlockFromToEvent event = new BlockFromToEvent(source, BlockFace.DOWN);
++ level.getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return;
+ }
+- } else if (fluidstate.isSource() || !this.isWaterHole(level, fluidstate1.getType(), blockpos, blockstate, blockpos1, blockstate1)) {
+- this.spreadToSides(level, blockpos, fluidstate, blockstate);
++ // CraftBukkit end
++ this.spreadTo(level, blockposition1, iblockdata1, Direction.DOWN, fluid1);
++ if (this.sourceNeighborCount(level, pos) >= 3) {
++ this.spreadToSides(level, pos, state, iblockdata);
++ }
++ } else if (state.isSource() || !this.isWaterHole(level, fluid1.getType(), pos, iblockdata, blockposition1, iblockdata1)) {
++ this.spreadToSides(level, pos, state, iblockdata);
+ }
+
+ }
+@@ -164,8 +178,17 @@
+ BlockPos blockpos1 = blockpos.relative(direction);
+ BlockState blockstate1 = level.getBlockState(blockpos1);
+
+- if (this.canSpreadTo(level, blockpos, blockstate, direction, blockpos1, blockstate1, level.getFluidState(blockpos1), fluidstate1.getType())) {
+- this.spreadTo(level, blockpos1, blockstate1, direction, fluidstate1);
++ if (this.canSpreadTo(level, pos, blockState, enumdirection, blockposition1, iblockdata1, level.getFluidState(blockposition1), fluid1.getType())) {
++ // CraftBukkit start
++ org.bukkit.block.Block source = CraftBlock.at(level, pos);
++ BlockFromToEvent event = new BlockFromToEvent(source, org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(enumdirection));
++ level.getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ continue;
++ }
++ // CraftBukkit end
++ this.spreadTo(level, blockposition1, iblockdata1, enumdirection, fluid1);
+ }
+ }
+
+@@ -440,16 +462,27 @@
+ FluidState fluidstate1 = this.getNewLiquid(level, blockpos, level.getBlockState(blockpos));
+ int i = this.getSpreadDelay(level, blockpos, fluidstate, fluidstate1);
+
+- if (fluidstate1.isEmpty()) {
+- fluidstate = fluidstate1;
+- level.setBlock(blockpos, Blocks.AIR.defaultBlockState(), 3);
+- } else if (!fluidstate1.equals(fluidstate)) {
+- fluidstate = fluidstate1;
+- BlockState blockstate = fluidstate1.createLegacyBlock();
+-
+- level.setBlock(blockpos, blockstate, 2);
+- level.scheduleTick(blockpos, fluidstate1.getType(), i);
+- level.updateNeighborsAt(blockpos, blockstate.getBlock());
++ if (fluid1.isEmpty()) {
++ state = fluid1;
++ // CraftBukkit start
++ FluidLevelChangeEvent event = CraftEventFactory.callFluidLevelChangeEvent(level, pos, Blocks.AIR.defaultBlockState());
++ if (event.isCancelled()) {
++ return;
++ }
++ level.setBlock(pos, ((CraftBlockData) event.getNewData()).getState(), 3);
++ // CraftBukkit end
++ } else if (!fluid1.equals(state)) {
++ state = fluid1;
++ IBlockData iblockdata = fluid1.createLegacyBlock();
++ // CraftBukkit start
++ FluidLevelChangeEvent event = CraftEventFactory.callFluidLevelChangeEvent(level, pos, iblockdata);
++ if (event.isCancelled()) {
++ return;
++ }
++ level.setBlock(pos, ((CraftBlockData) event.getNewData()).getState(), 2);
++ // CraftBukkit end
++ level.scheduleTick(pos, fluid1.getType(), i);
++ level.updateNeighborsAt(pos, iblockdata.getBlock());
+ }
+ }
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/material/LavaFluid.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/material/LavaFluid.java.patch
new file mode 100644
index 0000000000..236f8f0879
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/material/LavaFluid.java.patch
@@ -0,0 +1,58 @@
+--- a/net/minecraft/world/level/material/LavaFluid.java
++++ b/net/minecraft/world/level/material/LavaFluid.java
+@@ -87,9 +82,16 @@
+
+ BlockState blockstate = level.getBlockState(blockpos1);
+
+- if (blockstate.isAir()) {
+- if (this.hasFlammableNeighbours(level, blockpos1)) {
+- level.setBlockAndUpdate(blockpos1, BaseFireBlock.getState(level, blockpos1));
++ if (iblockdata.isAir()) {
++ if (this.hasFlammableNeighbours(level, blockposition1)) {
++ // CraftBukkit start - Prevent lava putting something on fire
++ if (level.getBlockState(blockposition1).getBlock() != Blocks.FIRE) {
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(level, blockposition1, pos).isCancelled()) {
++ continue;
++ }
++ }
++ // CraftBukkit end
++ level.setBlockAndUpdate(blockposition1, BaseFireBlock.getState(level, blockposition1));
+ return;
+ }
+ } else if (blockstate.blocksMotion()) {
+@@ -104,8 +106,16 @@
+ return;
+ }
+
+- if (level.isEmptyBlock(blockpos2.above()) && this.isFlammable(level, blockpos2)) {
+- level.setBlockAndUpdate(blockpos2.above(), BaseFireBlock.getState(level, blockpos2));
++ if (level.isEmptyBlock(blockposition2.above()) && this.isFlammable(level, blockposition2)) {
++ // CraftBukkit start - Prevent lava putting something on fire
++ BlockPos up = blockposition2.above();
++ if (level.getBlockState(up).getBlock() != Blocks.FIRE) {
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callBlockIgniteEvent(level, up, pos).isCancelled()) {
++ continue;
++ }
++ }
++ // CraftBukkit end
++ level.setBlockAndUpdate(blockposition2.above(), BaseFireBlock.getState(level, blockposition2));
+ }
+ }
+ }
+@@ -209,9 +208,13 @@
+ if (direction == Direction.DOWN) {
+ FluidState fluidstate1 = levelaccessor.getFluidState(blockpos);
+
+- if (this.is(FluidTags.LAVA) && fluidstate1.is(FluidTags.WATER)) {
+- if (blockstate.getBlock() instanceof LiquidBlock) {
+- levelaccessor.setBlock(blockpos, Blocks.STONE.defaultBlockState(), 3);
++ if (this.is(FluidTags.LAVA) && fluid1.is(FluidTags.WATER)) {
++ if (blockState.getBlock() instanceof LiquidBlock) {
++ // CraftBukkit start
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(level.getMinecraftWorld(), pos, Blocks.STONE.defaultBlockState(), 3)) {
++ return;
++ }
++ // CraftBukkit end
+ }
+
+ this.fizz(levelaccessor, blockpos);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/portal/PortalForcer.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/portal/PortalForcer.java.patch
new file mode 100644
index 0000000000..8ccc7232cb
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/portal/PortalForcer.java.patch
@@ -0,0 +1,109 @@
+--- a/net/minecraft/world/level/portal/PortalForcer.java
++++ b/net/minecraft/world/level/portal/PortalForcer.java
+@@ -41,12 +41,18 @@
+ this.level = serverlevel;
+ }
+
+- public Optional<BlockUtil.FoundRectangle> findPortalAround(BlockPos blockpos, boolean flag, WorldBorder worldborder) {
+- PoiManager poimanager = this.level.getPoiManager();
+- int i = flag ? 16 : 128;
++ public Optional<BlockUtil.FoundRectangle> findPortalAround(BlockPos pos, boolean isNether, WorldBorder worldBorder) {
++ // CraftBukkit start
++ return findPortalAround(pos, worldBorder, isNether ? 16 : 128); // Search Radius
++ }
+
+- poimanager.ensureLoadedAndValid(this.level, blockpos, i);
+- Optional<PoiRecord> optional = poimanager.getInSquare((holder) -> {
++ public Optional<BlockUtil.FoundRectangle> findPortalAround(BlockPos blockposition, WorldBorder worldborder, int i) {
++ PoiManager villageplace = this.level.getPoiManager();
++ // int i = flag ? 16 : 128;
++ // CraftBukkit end
++
++ villageplace.ensureLoadedAndValid(this.level, blockposition, i);
++ Optional<PoiRecord> optional = villageplace.getInSquare((holder) -> {
+ return holder.is(PoiTypes.NETHER_PORTAL);
+ }, blockpos, i, PoiManager.Occupancy.ANY).filter((poirecord) -> {
+ return worldborder.isWithinBounds(poirecord.getPos());
+@@ -70,16 +76,22 @@
+ });
+ }
+
+- public Optional<BlockUtil.FoundRectangle> createPortal(BlockPos blockpos, Direction.Axis direction_axis) {
+- Direction direction = Direction.get(Direction.AxisDirection.POSITIVE, direction_axis);
++ public Optional<BlockUtil.FoundRectangle> createPortal(BlockPos pos, Direction.Axis axis) {
++ // CraftBukkit start
++ return this.createPortal(pos, axis, null, 16);
++ }
++
++ public Optional<BlockUtil.FoundRectangle> createPortal(BlockPos blockposition, Direction.Axis enumdirection_enumaxis, net.minecraft.world.entity.Entity entity, int createRadius) {
++ // CraftBukkit end
++ Direction enumdirection = Direction.get(Direction.AxisDirection.POSITIVE, enumdirection_enumaxis);
+ double d0 = -1.0D;
+ BlockPos blockpos1 = null;
+ double d1 = -1.0D;
+ BlockPos blockpos2 = null;
+ WorldBorder worldborder = this.level.getWorldBorder();
+ int i = Math.min(this.level.getMaxBuildHeight(), this.level.getMinBuildHeight() + this.level.getLogicalHeight()) - 1;
+- BlockPos.MutableBlockPos blockpos_mutableblockpos = blockpos.mutable();
+- Iterator iterator = BlockPos.spiralAround(blockpos, 16, Direction.EAST, Direction.SOUTH).iterator();
++ BlockPos.MutableBlockPos blockposition_mutableblockposition = blockposition.mutable();
++ Iterator iterator = BlockPos.spiralAround(blockposition, createRadius, Direction.EAST, Direction.SOUTH).iterator(); // CraftBukkit
+
+ int j;
+ int k;
+@@ -134,6 +146,7 @@
+ int j1;
+ int k1;
+
++ org.bukkit.craftbukkit.util.BlockStateListPopulator blockList = new org.bukkit.craftbukkit.util.BlockStateListPopulator(this.level); // CraftBukkit - Use BlockStateListPopulator
+ if (d0 == -1.0D) {
+ j1 = Math.max(this.level.getMinBuildHeight() - -1, 70);
+ k1 = i - 9;
+@@ -153,8 +166,8 @@
+ for (l = -1; l < 3; ++l) {
+ BlockState blockstate = l < 0 ? Blocks.OBSIDIAN.defaultBlockState() : Blocks.AIR.defaultBlockState();
+
+- blockpos_mutableblockpos.setWithOffset(blockpos1, k * direction.getStepX() + l1 * direction1.getStepX(), l, k * direction.getStepZ() + l1 * direction1.getStepZ());
+- this.level.setBlockAndUpdate(blockpos_mutableblockpos, blockstate);
++ blockposition_mutableblockposition.setWithOffset(blockposition1, k * enumdirection.getStepX() + l1 * enumdirection1.getStepX(), l, k * enumdirection.getStepZ() + l1 * enumdirection1.getStepZ());
++ blockList.setBlock(blockposition_mutableblockposition, iblockdata, 3); // CraftBukkit
+ }
+ }
+ }
+@@ -163,8 +176,8 @@
+ for (j1 = -1; j1 < 3; ++j1) {
+ for (k1 = -1; k1 < 4; ++k1) {
+ if (j1 == -1 || j1 == 2 || k1 == -1 || k1 == 3) {
+- blockpos_mutableblockpos.setWithOffset(blockpos1, j1 * direction.getStepX(), k1, j1 * direction.getStepZ());
+- this.level.setBlock(blockpos_mutableblockpos, Blocks.OBSIDIAN.defaultBlockState(), 3);
++ blockposition_mutableblockposition.setWithOffset(blockposition1, j1 * enumdirection.getStepX(), k1, j1 * enumdirection.getStepZ());
++ blockList.setBlock(blockposition_mutableblockposition, Blocks.OBSIDIAN.defaultBlockState(), 3); // CraftBukkit
+ }
+ }
+ }
+@@ -173,12 +186,22 @@
+
+ for (k1 = 0; k1 < 2; ++k1) {
+ for (j = 0; j < 3; ++j) {
+- blockpos_mutableblockpos.setWithOffset(blockpos1, k1 * direction.getStepX(), j, k1 * direction.getStepZ());
+- this.level.setBlock(blockpos_mutableblockpos, blockstate1, 18);
++ blockposition_mutableblockposition.setWithOffset(blockposition1, k1 * enumdirection.getStepX(), j, k1 * enumdirection.getStepZ());
++ blockList.setBlock(blockposition_mutableblockposition, iblockdata1, 18); // CraftBukkit
+ }
+ }
+
+- return Optional.of(new BlockUtil.FoundRectangle(blockpos1.immutable(), 2, 3));
++ // CraftBukkit start
++ org.bukkit.World bworld = this.level.getWorld();
++ org.bukkit.event.world.PortalCreateEvent event = new org.bukkit.event.world.PortalCreateEvent((java.util.List<org.bukkit.block.BlockState>) (java.util.List) blockList.getList(), bworld, (entity == null) ? null : entity.getBukkitEntity(), org.bukkit.event.world.PortalCreateEvent.CreateReason.NETHER_PAIR);
++
++ this.level.getCraftServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ return Optional.empty();
++ }
++ blockList.updateList();
++ // CraftBukkit end
++ return Optional.of(new BlockUtil.FoundRectangle(blockposition1.immutable(), 2, 3));
+ }
+
+ private boolean canPortalReplaceBlock(BlockPos.MutableBlockPos blockpos_mutableblockpos) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/portal/PortalInfo.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/portal/PortalInfo.java.patch
new file mode 100644
index 0000000000..5e814d3846
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/portal/PortalInfo.java.patch
@@ -0,0 +1,31 @@
+--- a/net/minecraft/world/level/portal/PortalInfo.java
++++ b/net/minecraft/world/level/portal/PortalInfo.java
+@@ -1,6 +2,8 @@
+ package net.minecraft.world.level.portal;
+
+ import net.minecraft.world.phys.Vec3;
++import org.bukkit.craftbukkit.event.CraftPortalEvent;
++// CraftBukkit end
+
+ public class PortalInfo {
+
+@@ -8,10 +11,16 @@
+ public final Vec3 speed;
+ public final float yRot;
+ public final float xRot;
++ // CraftBukkit start
++ public final ServerLevel world;
++ public final CraftPortalEvent portalEventInfo;
+
+- public PortalInfo(Vec3 vec3, Vec3 vec31, float f, float f1) {
+- this.pos = vec3;
+- this.speed = vec31;
++ public PortalInfo(Vec3 vec3d, Vec3 vec3d1, float f, float f1, ServerLevel world, CraftPortalEvent portalEventInfo) {
++ this.world = world;
++ this.portalEventInfo = portalEventInfo;
++ // CraftBukkit end
++ this.pos = vec3d;
++ this.speed = vec3d1;
+ this.yRot = f;
+ this.xRot = f1;
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/portal/PortalShape.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/portal/PortalShape.java.patch
new file mode 100644
index 0000000000..6693622549
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/portal/PortalShape.java.patch
@@ -0,0 +1,146 @@
+--- a/net/minecraft/world/level/portal/PortalShape.java
++++ b/net/minecraft/world/level/portal/PortalShape.java
+@@ -21,6 +21,10 @@
+ import net.minecraft.world.phys.Vec3;
+ import net.minecraft.world.phys.shapes.Shapes;
+ import net.minecraft.world.phys.shapes.VoxelShape;
++// CraftBukkit start
++import org.bukkit.craftbukkit.event.CraftPortalEvent;
++import org.bukkit.event.world.PortalCreateEvent;
++// CraftBukkit end
+
+ public class PortalShape {
+
+@@ -41,6 +45,7 @@
+ private BlockPos bottomLeft;
+ private int height;
+ private final int width;
++ org.bukkit.craftbukkit.util.BlockStateListPopulator blocks; // CraftBukkit - add field
+
+ public static Optional<PortalShape> findEmptyPortalShape(LevelAccessor levelaccessor, BlockPos blockpos, Direction.Axis direction_axis) {
+ return findPortalShape(levelaccessor, blockpos, (portalshape) -> {
+@@ -60,11 +65,12 @@
+ }
+ }
+
+- public PortalShape(LevelAccessor levelaccessor, BlockPos blockpos, Direction.Axis direction_axis) {
+- this.level = levelaccessor;
+- this.axis = direction_axis;
+- this.rightDir = direction_axis == Direction.Axis.X ? Direction.WEST : Direction.SOUTH;
+- this.bottomLeft = this.calculateBottomLeft(blockpos);
++ public PortalShape(LevelAccessor level, BlockPos bottomLeft, Direction.Axis axis) {
++ blocks = new org.bukkit.craftbukkit.util.BlockStateListPopulator(level.getMinecraftWorld()); // CraftBukkit
++ this.level = level;
++ this.axis = axis;
++ this.rightDir = axis == Direction.Axis.X ? Direction.WEST : Direction.SOUTH;
++ this.bottomLeft = this.calculateBottomLeft(bottomLeft);
+ if (this.bottomLeft == null) {
+ this.bottomLeft = blockpos;
+ this.width = 1;
+@@ -103,8 +109,9 @@
+ blockpos_mutableblockpos.set(blockpos).move(direction, i);
+ BlockState blockstate = this.level.getBlockState(blockpos_mutableblockpos);
+
+- if (!isEmpty(blockstate)) {
+- if (PortalShape.FRAME.test(blockstate, this.level, blockpos_mutableblockpos)) {
++ if (!isEmpty(iblockdata)) {
++ if (PortalShape.FRAME.test(iblockdata, this.level, blockposition_mutableblockposition)) {
++ blocks.setBlock(blockposition_mutableblockposition, iblockdata, 18); // CraftBukkit - lower left / right
+ return i;
+ }
+ break;
+@@ -115,6 +122,7 @@
+ if (!PortalShape.FRAME.test(blockstate1, this.level, blockpos_mutableblockpos)) {
+ break;
+ }
++ blocks.setBlock(blockposition_mutableblockposition, iblockdata1, 18); // CraftBukkit - bottom row
+ }
+
+ return 0;
+@@ -134,6 +142,7 @@
+ if (!PortalShape.FRAME.test(this.level.getBlockState(blockpos_mutableblockpos1), this.level, blockpos_mutableblockpos1)) {
+ return false;
+ }
++ blocks.setBlock(blockposition_mutableblockposition1, this.level.getBlockState(blockposition_mutableblockposition1), 18); // CraftBukkit - upper row
+ }
+
+ return true;
+@@ -163,6 +172,10 @@
+ ++this.numPortalBlocks;
+ }
+ }
++ // CraftBukkit start - left and right
++ blocks.setBlock(pos.set(this.bottomLeft).move(Direction.UP, i).move(this.rightDir, -1), this.level.getBlockState(pos), 18);
++ blocks.setBlock(pos.set(this.bottomLeft).move(Direction.UP, i).move(this.rightDir, this.width), this.level.getBlockState(pos), 18);
++ // CraftBukkit end
+ }
+
+ return 21;
+@@ -176,12 +189,25 @@
+ return this.bottomLeft != null && this.width >= 2 && this.width <= 21 && this.height >= 3 && this.height <= 21;
+ }
+
+- public void createPortalBlocks() {
+- BlockState blockstate = (BlockState) Blocks.NETHER_PORTAL.defaultBlockState().setValue(NetherPortalBlock.AXIS, this.axis);
++ // CraftBukkit start - return boolean
++ public boolean createPortalBlocks() {
++ org.bukkit.World bworld = this.level.getMinecraftWorld().getWorld();
+
+ BlockPos.betweenClosed(this.bottomLeft, this.bottomLeft.relative(Direction.UP, this.height - 1).relative(this.rightDir, this.width - 1)).forEach((blockpos) -> {
+ this.level.setBlock(blockpos, blockstate, 18);
+ });
++
++ PortalCreateEvent event = new PortalCreateEvent((java.util.List<org.bukkit.block.BlockState>) (java.util.List) blocks.getList(), bworld, null, PortalCreateEvent.CreateReason.FIRE);
++ this.level.getMinecraftWorld().getServer().server.getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return false;
++ }
++ // CraftBukkit end
++ BlockPos.betweenClosed(this.bottomLeft, this.bottomLeft.relative(Direction.UP, this.height - 1).relative(this.rightDir, this.width - 1)).forEach((blockposition) -> {
++ this.level.setBlock(blockposition, iblockdata, 18);
++ });
++ return true; // CraftBukkit
+ }
+
+ public boolean isComplete() {
+@@ -217,23 +246,23 @@
+ return new Vec3(d3, d2, d4);
+ }
+
+- public static PortalInfo createPortalInfo(ServerLevel serverlevel, BlockUtil.FoundRectangle blockutil_foundrectangle, Direction.Axis direction_axis, Vec3 vec3, Entity entity, Vec3 vec31, float f, float f1) {
+- BlockPos blockpos = blockutil_foundrectangle.minCorner;
+- BlockState blockstate = serverlevel.getBlockState(blockpos);
+- Direction.Axis direction_axis1 = (Direction.Axis) blockstate.getOptionalValue(BlockStateProperties.HORIZONTAL_AXIS).orElse(Direction.Axis.X);
+- double d0 = (double) blockutil_foundrectangle.axis1Size;
+- double d1 = (double) blockutil_foundrectangle.axis2Size;
+- EntityDimensions entitydimensions = entity.getDimensions(entity.getPose());
+- int i = direction_axis == direction_axis1 ? 0 : 90;
+- Vec3 vec32 = direction_axis == direction_axis1 ? vec31 : new Vec3(vec31.z, vec31.y, -vec31.x);
+- double d2 = (double) entitydimensions.width / 2.0D + (d0 - (double) entitydimensions.width) * vec3.x();
+- double d3 = (d1 - (double) entitydimensions.height) * vec3.y();
+- double d4 = 0.5D + vec3.z();
+- boolean flag = direction_axis1 == Direction.Axis.X;
+- Vec3 vec33 = new Vec3((double) blockpos.getX() + (flag ? d2 : d4), (double) blockpos.getY() + d3, (double) blockpos.getZ() + (flag ? d4 : d2));
+- Vec3 vec34 = findCollisionFreePosition(vec33, serverlevel, entity, entitydimensions);
++ public static PortalInfo createPortalInfo(ServerLevel worldserver, BlockUtil.FoundRectangle blockutil_rectangle, Direction.Axis enumdirection_enumaxis, Vec3 vec3d, Entity entity, Vec3 vec3d1, float f, float f1, CraftPortalEvent portalEventInfo) { // CraftBukkit
++ BlockPos blockposition = blockutil_rectangle.minCorner;
++ IBlockData iblockdata = worldserver.getBlockState(blockposition);
++ Direction.Axis enumdirection_enumaxis1 = (Direction.Axis) iblockdata.getOptionalValue(BlockStateProperties.HORIZONTAL_AXIS).orElse(Direction.Axis.X);
++ double d0 = (double) blockutil_rectangle.axis1Size;
++ double d1 = (double) blockutil_rectangle.axis2Size;
++ EntityDimensions entitysize = entity.getDimensions(entity.getPose());
++ int i = enumdirection_enumaxis == enumdirection_enumaxis1 ? 0 : 90;
++ Vec3 vec3d2 = enumdirection_enumaxis == enumdirection_enumaxis1 ? vec3d1 : new Vec3(vec3d1.z, vec3d1.y, -vec3d1.x);
++ double d2 = (double) entitysize.width / 2.0D + (d0 - (double) entitysize.width) * vec3d.x();
++ double d3 = (d1 - (double) entitysize.height) * vec3d.y();
++ double d4 = 0.5D + vec3d.z();
++ boolean flag = enumdirection_enumaxis1 == Direction.Axis.X;
++ Vec3 vec3d3 = new Vec3((double) blockposition.getX() + (flag ? d2 : d4), (double) blockposition.getY() + d3, (double) blockposition.getZ() + (flag ? d4 : d2));
++ Vec3 vec3d4 = findCollisionFreePosition(vec3d3, worldserver, entity, entitysize);
+
+- return new PortalInfo(vec34, vec32, f + (float) i, f1);
++ return new PortalInfo(vec3d4, vec3d2, f + (float) i, f1, worldserver, portalEventInfo); // CraftBukkit
+ }
+
+ private static Vec3 findCollisionFreePosition(Vec3 vec3, ServerLevel serverlevel, Entity entity, EntityDimensions entitydimensions) {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/redstone/NeighborUpdater.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/redstone/NeighborUpdater.java.patch
new file mode 100644
index 0000000000..b753d72f9d
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/redstone/NeighborUpdater.java.patch
@@ -0,0 +1,36 @@
+--- a/net/minecraft/world/level/redstone/NeighborUpdater.java
++++ b/net/minecraft/world/level/redstone/NeighborUpdater.java
+@@ -11,7 +12,12 @@
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.LevelAccessor;
+ import net.minecraft.world.level.block.Block;
+-import net.minecraft.world.level.block.state.BlockState;
++import net.minecraft.world.level.block.state.IBlockData;
++import org.bukkit.craftbukkit.CraftWorld;
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.craftbukkit.block.data.CraftBlockData;
++import org.bukkit.event.block.BlockPhysicsEvent;
++// CraftBukkit end
+
+ public interface NeighborUpdater {
+
+@@ -46,7 +52,18 @@
+
+ static void executeUpdate(Level level, BlockState blockstate, BlockPos blockpos, Block block, BlockPos blockpos1, boolean flag) {
+ try {
+- blockstate.neighborChanged(level, blockpos, block, blockpos1, flag);
++ // CraftBukkit start
++ CraftWorld cworld = ((ServerLevel) level).getWorld();
++ if (cworld != null) {
++ BlockPhysicsEvent event = new BlockPhysicsEvent(CraftBlock.at(level, pos), CraftBlockData.fromData(state), CraftBlock.at(level, neighborPos));
++ ((ServerLevel) level).getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ return;
++ }
++ }
++ // CraftBukkit end
++ state.neighborChanged(level, pos, neighborBlock, neighborPos, movedByPiston);
+ } catch (Throwable throwable) {
+ CrashReport crashreport = CrashReport.forThrowable(throwable, "Exception while updating neighbours");
+ CrashReportCategory crashreportcategory = crashreport.addCategory("Block being updated");
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java.patch
new file mode 100644
index 0000000000..2680282bad
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java.patch
@@ -0,0 +1,173 @@
+--- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
++++ b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
+@@ -32,6 +33,15 @@
+ import net.minecraft.world.level.saveddata.SavedData;
+ import org.slf4j.Logger;
+
++// CraftBukkit start
++import java.util.UUID;
++import org.bukkit.Bukkit;
++import org.bukkit.craftbukkit.CraftServer;
++import org.bukkit.craftbukkit.CraftWorld;
++import org.bukkit.craftbukkit.map.CraftMapView;
++import org.bukkit.craftbukkit.util.CraftChatMessage;
++// CraftBukkit end
++
+ public class MapItemSavedData extends SavedData {
+
+ private static final Logger LOGGER = LogUtils.getLogger();
+@@ -54,8 +64,15 @@
+ private final Map<String, MapFrame> frameMarkers = Maps.newHashMap();
+ private int trackedDecorationCount;
+
+- public static SavedData.Factory<MapItemSavedData> factory() {
+- return new SavedData.Factory<>(() -> {
++ // CraftBukkit start
++ public final CraftMapView mapView;
++ private CraftServer server;
++ public UUID uniqueId = null;
++ public String id;
++ // CraftBukkit end
++
++ public static SavedData.a<MapItemSavedData> factory() {
++ return new SavedData.a<>(() -> {
+ throw new IllegalStateException("Should never create an empty map saved data");
+ }, MapItemSavedData::load, DataFixTypes.SAVED_DATA_MAP_DATA);
+ }
+@@ -69,6 +86,10 @@
+ this.unlimitedTracking = flag1;
+ this.locked = flag2;
+ this.setDirty();
++ // CraftBukkit start
++ mapView = new CraftMapView(this);
++ server = (CraftServer) org.bukkit.Bukkit.getServer();
++ // CraftBukkit end
+ }
+
+ public static MapItemSavedData createFresh(double d0, double d1, byte b0, boolean flag, boolean flag1, ResourceKey<Level> resourcekey) {
+@@ -90,8 +111,26 @@
+ Logger logger = MapItemSavedData.LOGGER;
+
+ Objects.requireNonNull(logger);
+- ResourceKey<Level> resourcekey = (ResourceKey) dataresult.resultOrPartial(logger::error).orElseThrow(() -> {
+- return new IllegalArgumentException("Invalid map dimension: " + compoundtag.get("dimension"));
++ // CraftBukkit start
++ ResourceKey<Level> resourcekey = dataresult.resultOrPartial(logger::error).orElseGet(() -> {
++ long least = compoundTag.getLong("UUIDLeast");
++ long most = compoundTag.getLong("UUIDMost");
++
++ if (least != 0L && most != 0L) {
++ UUID uniqueId = new UUID(most, least);
++
++ CraftWorld world = (CraftWorld) Bukkit.getWorld(uniqueId);
++ // Check if the stored world details are correct.
++ if (world == null) {
++ /* All Maps which do not have their valid world loaded are set to a dimension which hopefully won't be reached.
++ This is to prevent them being corrupted with the wrong map data. */
++ // PAIL: Use Vanilla exception handling for now
++ } else {
++ return world.getHandle().dimension();
++ }
++ }
++ throw new IllegalArgumentException("Invalid map dimension: " + compoundTag.get("dimension"));
++ // CraftBukkit end
+ });
+ int i = compoundtag.getInt("xCenter");
+ int j = compoundtag.getInt("zCenter");
+@@ -137,14 +175,33 @@
+ dataresult.resultOrPartial(logger::error).ifPresent((tag) -> {
+ compoundtag.put("dimension", tag);
+ });
+- compoundtag.putInt("xCenter", this.centerX);
+- compoundtag.putInt("zCenter", this.centerZ);
+- compoundtag.putByte("scale", this.scale);
+- compoundtag.putByteArray("colors", this.colors);
+- compoundtag.putBoolean("trackingPosition", this.trackingPosition);
+- compoundtag.putBoolean("unlimitedTracking", this.unlimitedTracking);
+- compoundtag.putBoolean("locked", this.locked);
+- ListTag listtag = new ListTag();
++ // CraftBukkit start
++ if (true) {
++ if (this.uniqueId == null) {
++ for (org.bukkit.World world : server.getWorlds()) {
++ CraftWorld cWorld = (CraftWorld) world;
++ if (cWorld.getHandle().dimension() == this.dimension) {
++ this.uniqueId = cWorld.getUID();
++ break;
++ }
++ }
++ }
++ /* Perform a second check to see if a matching world was found, this is a necessary
++ change incase Maps are forcefully unlinked from a World and lack a UID.*/
++ if (this.uniqueId != null) {
++ compound.putLong("UUIDLeast", this.uniqueId.getLeastSignificantBits());
++ compound.putLong("UUIDMost", this.uniqueId.getMostSignificantBits());
++ }
++ }
++ // CraftBukkit end
++ compound.putInt("xCenter", this.centerX);
++ compound.putInt("zCenter", this.centerZ);
++ compound.putByte("scale", this.scale);
++ compound.putByteArray("colors", this.colors);
++ compound.putBoolean("trackingPosition", this.trackingPosition);
++ compound.putBoolean("unlimitedTracking", this.unlimitedTracking);
++ compound.putBoolean("locked", this.locked);
++ ListTag nbttaglist = new ListTag();
+ Iterator iterator = this.bannerMarkers.values().iterator();
+
+ while (iterator.hasNext()) {
+@@ -511,7 +568,7 @@
+ this.player = player;
+ }
+
+- private MapItemSavedData.MapPatch createPatch() {
++ private MapItemSavedData.MapPatch createPatch(byte[] buffer) { // CraftBukkit
+ int i = this.minDirtyX;
+ int j = this.minDirtyY;
+ int k = this.maxDirtyX + 1 - this.minDirtyX;
+@@ -520,7 +577,7 @@
+
+ for (int i1 = 0; i1 < k; ++i1) {
+ for (int j1 = 0; j1 < l; ++j1) {
+- abyte[i1 + j1 * k] = MapItemSavedData.this.colors[i + i1 + (j + j1) * 128];
++ abyte[i1 + j1 * k] = buffer[i + i1 + (j + j1) * 128]; // CraftBukkit
+ }
+ }
+
+@@ -528,21 +585,31 @@
+ }
+
+ @Nullable
+- Packet<?> nextUpdatePacket(int i) {
+- MapItemSavedData.MapPatch mapitemsaveddata_mappatch;
++ Packet<?> nextUpdatePacket(int mapId) {
++ MapItemSavedData.MapPatch worldmap_b;
++ org.bukkit.craftbukkit.map.RenderData render = MapItemSavedData.this.mapView.render((org.bukkit.craftbukkit.entity.CraftPlayer) this.player.getBukkitEntity()); // CraftBukkit
+
+ if (this.dirtyData) {
+ this.dirtyData = false;
+- mapitemsaveddata_mappatch = this.createPatch();
++ worldmap_b = this.createPatch(render.buffer); // CraftBukkit
+ } else {
+ mapitemsaveddata_mappatch = null;
+ }
+
+ Collection collection;
+
+- if (this.dirtyDecorations && this.tick++ % 5 == 0) {
++ if ((true || this.dirtyDecorations) && this.tick++ % 5 == 0) { // CraftBukkit - custom maps don't update this yet
+ this.dirtyDecorations = false;
+- collection = MapItemSavedData.this.decorations.values();
++ // CraftBukkit start
++ java.util.Collection<MapDecoration> icons = new java.util.ArrayList<MapDecoration>();
++
++ for (org.bukkit.map.MapCursor cursor : render.cursors) {
++ if (cursor.isVisible()) {
++ icons.add(new MapDecoration(MapDecoration.Type.byIcon(cursor.getRawType()), cursor.getX(), cursor.getY(), cursor.getDirection(), CraftChatMessage.fromStringOrNull(cursor.getCaption())));
++ }
++ }
++ collection = icons;
++ // CraftBukkit end
+ } else {
+ collection = null;
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/LevelStorageSource.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/LevelStorageSource.java.patch
new file mode 100644
index 0000000000..2cbea754a8
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/LevelStorageSource.java.patch
@@ -0,0 +1,91 @@
+--- a/net/minecraft/world/level/storage/LevelStorageSource.java
++++ b/net/minecraft/world/level/storage/LevelStorageSource.java
+@@ -169,11 +168,12 @@
+ Logger logger = LevelStorageSource.LOGGER;
+
+ Objects.requireNonNull(logger);
+- WorldGenSettings worldgensettings = (WorldGenSettings) dataresult.getOrThrow(false, Util.prefix("WorldGenSettings: ", logger::error));
+- LevelSettings levelsettings = LevelSettings.parse(dynamic1, worlddataconfiguration);
+- WorldDimensions.Complete worlddimensions_complete = worldgensettings.dimensions().bake(registry);
+- Lifecycle lifecycle = worlddimensions_complete.lifecycle().add(registryaccess_frozen.allRegistriesLifecycle());
+- PrimaryLevelData primaryleveldata = PrimaryLevelData.parse(dynamic1, levelsettings, worlddimensions_complete.specialWorldProperty(), worldgensettings.options(), lifecycle);
++ WorldGenSettings generatorsettings = (WorldGenSettings) dataresult.getOrThrow(false, Util.prefix("WorldGenSettings: ", logger::error));
++ LevelSettings worldsettings = LevelSettings.parse(dynamic1, worlddataconfiguration);
++ WorldDimensions.b worlddimensions_b = generatorsettings.dimensions().bake(iregistry);
++ Lifecycle lifecycle = worlddimensions_b.lifecycle().add(iregistrycustom_dimension.allRegistriesLifecycle());
++ PrimaryLevelData worlddataserver = PrimaryLevelData.parse(dynamic1, worldsettings, worlddimensions_b.specialWorldProperty(), generatorsettings.options(), lifecycle);
++ worlddataserver.pdc = ((Dynamic<Tag>) dynamic1).getElement("BukkitValues", null); // CraftBukkit - Add PDC to world
+
+ return new LevelDataAndDimensions(primaryleveldata, worlddimensions_complete);
+ }
+@@ -423,28 +423,40 @@
+ return this.backupDir;
+ }
+
+- public LevelStorageSource.LevelStorageAccess validateAndCreateAccess(String s) throws IOException, ContentValidationException {
++ public LevelStorageSource.LevelStorageAccess validateAndCreateAccess(String s, ResourceKey<LevelStem> dimensionType) throws IOException, ContentValidationException { // CraftBukkit
+ Path path = this.getLevelPath(s);
+ List<ForbiddenSymlinkInfo> list = this.worldDirValidator.validateDirectory(path, true);
+
+ if (!list.isEmpty()) {
+ throw new ContentValidationException(path, list);
+ } else {
+- return new LevelStorageSource.LevelStorageAccess(s, path);
++ return new LevelStorageSource.LevelStorageAccess(s, path, dimensionType); // CraftBukkit
+ }
+ }
+
+- public LevelStorageSource.LevelStorageAccess createAccess(String s) throws IOException {
++ public LevelStorageSource.LevelStorageAccess createAccess(String s, ResourceKey<LevelStem> dimensionType) throws IOException { // CraftBukkit
+ Path path = this.getLevelPath(s);
+
+- return new LevelStorageSource.LevelStorageAccess(s, path);
++ return new LevelStorageSource.LevelStorageAccess(s, path, dimensionType); // CraftBukkit
+ }
+
+ public DirectoryValidator getWorldDirValidator() {
+ return this.worldDirValidator;
+ }
+
+- public static record LevelCandidates(List<LevelStorageSource.LevelDirectory> levels) implements Iterable<LevelStorageSource.LevelDirectory> {
++ // CraftBukkit start
++ public static Path getStorageFolder(Path path, ResourceKey<LevelStem> dimensionType) {
++ if (dimensionType == LevelStem.OVERWORLD) {
++ return path;
++ } else if (dimensionType == LevelStem.NETHER) {
++ return path.resolve("DIM-1");
++ } else if (dimensionType == LevelStem.END) {
++ return path.resolve("DIM1");
++ } else {
++ return path.resolve("dimensions").resolve(dimensionType.location().getNamespace()).resolve(dimensionType.location().getPath());
++ }
++ }
++ // CraftBukkit end
+
+ public boolean isEmpty() {
+ return this.levels.isEmpty();
+@@ -503,8 +516,12 @@
+ final LevelStorageSource.LevelDirectory levelDirectory;
+ private final String levelId;
+ private final Map<LevelResource, Path> resources = Maps.newHashMap();
++ // CraftBukkit start
++ public final ResourceKey<LevelStem> dimensionType;
+
+- LevelStorageAccess(String s, Path path) throws IOException {
++ LevelStorageAccess(String s, Path path, ResourceKey<LevelStem> dimensionType) throws IOException {
++ this.dimensionType = dimensionType;
++ // CraftBukkit end
+ this.levelId = s;
+ this.levelDirectory = new LevelStorageSource.LevelDirectory(path);
+ this.lock = DirectoryLock.create(path);
+@@ -539,8 +556,8 @@
+ return (Path) map.computeIfAbsent(levelresource, levelstoragesource_leveldirectory::resourcePath);
+ }
+
+- public Path getDimensionPath(ResourceKey<Level> resourcekey) {
+- return DimensionType.getStorageFolder(resourcekey, this.levelDirectory.path());
++ public Path getDimensionPath(ResourceKey<Level> dimensionPath) {
++ return getStorageFolder(this.levelDirectory.path(), this.dimensionType); // CraftBukkit
+ }
+
+ private void checkLock() {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/PlayerDataStorage.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/PlayerDataStorage.java.patch
new file mode 100644
index 0000000000..411a4d76e9
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/PlayerDataStorage.java.patch
@@ -0,0 +1,70 @@
+--- a/net/minecraft/world/level/storage/PlayerDataStorage.java
++++ b/net/minecraft/world/level/storage/PlayerDataStorage.java
+@@ -15,6 +16,12 @@
+ import net.minecraft.world.entity.player.Player;
+ import org.slf4j.Logger;
+
++// CraftBukkit start
++import java.io.FileInputStream;
++import java.io.InputStream;
++import org.bukkit.craftbukkit.entity.CraftPlayer;
++// CraftBukkit end
++
+ public class PlayerDataStorage {
+
+ private static final Logger LOGGER = LogUtils.getLogger();
+@@ -58,8 +65,18 @@
+ PlayerDataStorage.LOGGER.warn("Failed to load player data for {}", player.getName().getString());
+ }
+
+- if (compoundtag != null) {
+- int i = NbtUtils.getDataVersion(compoundtag, -1);
++ if (nbttagcompound != null) {
++ // CraftBukkit start
++ if (player instanceof ServerPlayer) {
++ CraftPlayer player = (CraftPlayer) player.getBukkitEntity();
++ // Only update first played if it is older than the one we have
++ long modified = new File(this.playerDir, player.getUUID().toString() + ".dat").lastModified();
++ if (modified < player.getFirstPlayed()) {
++ player.setFirstPlayed(modified);
++ }
++ }
++ // CraftBukkit end
++ int i = NbtUtils.getDataVersion(nbttagcompound, -1);
+
+ compoundtag = DataFixTypes.PLAYER.updateToCurrentVersion(this.fixerUpper, compoundtag, i);
+ player.load(compoundtag);
+@@ -68,6 +85,22 @@
+ return compoundtag;
+ }
+
++ // CraftBukkit start
++ public CompoundTag getPlayerData(String s) {
++ try {
++ File file1 = new File(this.playerDir, s + ".dat");
++
++ if (file1.exists()) {
++ return NbtIo.readCompressed(file1.toPath(), NbtAccounter.unlimitedHeap());
++ }
++ } catch (Exception exception) {
++ LOGGER.warn("Failed to load player data for " + s);
++ }
++
++ return null;
++ }
++ // CraftBukkit end
++
+ public String[] getSeenPlayers() {
+ String[] astring = this.playerDir.list();
+
+@@ -83,4 +116,10 @@
+
+ return astring;
+ }
++
++ // CraftBukkit start
++ public File getPlayerDir() {
++ return playerDir;
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/PrimaryLevelData.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/PrimaryLevelData.java.patch
new file mode 100644
index 0000000000..7ef05b7e32
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/PrimaryLevelData.java.patch
@@ -0,0 +1,159 @@
+--- a/net/minecraft/world/level/storage/PrimaryLevelData.java
++++ b/net/minecraft/world/level/storage/PrimaryLevelData.java
+@@ -42,6 +39,20 @@
+ import net.minecraft.world.level.timers.TimerCallbacks;
+ import net.minecraft.world.level.timers.TimerQueue;
+ import org.slf4j.Logger;
++import net.minecraft.core.registries.Registries;
++import net.minecraft.nbt.CompoundTag;
++import net.minecraft.nbt.ListTag;
++import net.minecraft.nbt.NbtOps;
++import net.minecraft.nbt.NbtUtils;
++import net.minecraft.nbt.StringTag;
++import net.minecraft.nbt.Tag;
++import net.minecraft.network.protocol.game.ClientboundChangeDifficultyPacket;
++import net.minecraft.world.level.levelgen.WorldDimensions;
++import net.minecraft.world.level.levelgen.WorldGenSettings;
++import org.bukkit.Bukkit;
++import org.bukkit.event.weather.ThunderChangeEvent;
++import org.bukkit.event.weather.WeatherChangeEvent;
++// CraftBukkit end
+
+ public class PrimaryLevelData implements ServerLevelData, WorldData {
+
+@@ -81,8 +92,22 @@
+ private boolean wasModded;
+ private final Set<String> removedFeatureFlags;
+ private final TimerQueue<MinecraftServer> scheduledEvents;
++ // CraftBukkit start - Add world and pdc
++ public Registry<LevelStem> customDimensions;
++ private ServerLevel world;
++ protected Tag pdc;
+
+- private PrimaryLevelData(@Nullable CompoundTag compoundtag, boolean flag, int i, int j, int k, float f, long l, long i1, int j1, int k1, int l1, boolean flag1, int i2, boolean flag2, boolean flag3, boolean flag4, WorldBorder.Settings worldborder_settings, int j2, int k2, @Nullable UUID uuid, Set<String> set, Set<String> set1, TimerQueue<MinecraftServer> timerqueue, @Nullable CompoundTag compoundtag1, EndDragonFight.Data enddragonfight_data, LevelSettings levelsettings, WorldOptions worldoptions, PrimaryLevelData.SpecialWorldProperty primaryleveldata_specialworldproperty, Lifecycle lifecycle) {
++ public void setWorld(ServerLevel world) {
++ if (this.world != null) {
++ return;
++ }
++ this.world = world;
++ world.getWorld().readBukkitValues(pdc);
++ pdc = null;
++ }
++ // CraftBukkit end
++
++ private PrimaryLevelData(@Nullable CompoundTag nbttagcompound, boolean flag, int i, int j, int k, float f, long l, long i1, int j1, int k1, int l1, boolean flag1, int i2, boolean flag2, boolean flag3, boolean flag4, WorldBorder.Settings worldborder_c, int j2, int k2, @Nullable UUID uuid, Set<String> set, Set<String> set1, TimerQueue<MinecraftServer> customfunctioncallbacktimerqueue, @Nullable CompoundTag nbttagcompound1, EndDragonFight.Data enderdragonbattle_a, LevelSettings worldsettings, WorldOptions worldoptions, PrimaryLevelData.a worlddataserver_a, Lifecycle lifecycle) {
+ this.wasModded = flag;
+ this.xSpawn = i;
+ this.ySpawn = j;
+@@ -176,14 +200,14 @@
+
+ CompoundTag compoundtag2 = new CompoundTag();
+
+- compoundtag2.putString("Name", SharedConstants.getCurrentVersion().getName());
+- compoundtag2.putInt("Id", SharedConstants.getCurrentVersion().getDataVersion().getVersion());
+- compoundtag2.putBoolean("Snapshot", !SharedConstants.getCurrentVersion().isStable());
+- compoundtag2.putString("Series", SharedConstants.getCurrentVersion().getDataVersion().getSeries());
+- compoundtag.put("Version", compoundtag2);
+- NbtUtils.addCurrentDataVersion(compoundtag);
+- DynamicOps<Tag> dynamicops = RegistryOps.create(NbtOps.INSTANCE, (HolderLookup.Provider) registryaccess);
+- DataResult dataresult = WorldGenSettings.encode(dynamicops, this.worldOptions, registryaccess);
++ nbttagcompound2.putString("Name", SharedConstants.getCurrentVersion().getName());
++ nbttagcompound2.putInt("Id", SharedConstants.getCurrentVersion().getDataVersion().getVersion());
++ nbttagcompound2.putBoolean("Snapshot", !SharedConstants.getCurrentVersion().isStable());
++ nbttagcompound2.putString("Series", SharedConstants.getCurrentVersion().getDataVersion().getSeries());
++ nbt.put("Version", nbttagcompound2);
++ NbtUtils.addCurrentDataVersion(nbt);
++ DynamicOps<Tag> dynamicops = RegistryOps.create(NbtOps.INSTANCE, (HolderLookup.Provider) registry);
++ DataResult<Tag> dataresult = WorldGenSettings.encode(dynamicops, this.worldOptions, new WorldDimensions(this.customDimensions != null ? this.customDimensions : registry.registryOrThrow(Registries.LEVEL_STEM))); // CraftBukkit
+ Logger logger = PrimaryLevelData.LOGGER;
+
+ Objects.requireNonNull(logger);
+@@ -235,6 +259,8 @@
+ compoundtag.putUUID("WanderingTraderId", this.wanderingTraderId);
+ }
+
++ nbt.putString("Bukkit.Version", Bukkit.getName() + "/" + Bukkit.getVersion() + "/" + Bukkit.getBukkitVersion()); // CraftBukkit
++ world.getWorld().storeBukkitValues(nbt); // CraftBukkit - add pdc
+ }
+
+ private static ListTag stringCollectionToTag(Set<String> set) {
+@@ -365,9 +372,22 @@
+ }
+
+ @Override
+- @Override
+- public void setThundering(boolean flag) {
+- this.thundering = flag;
++ public void setThundering(boolean thundering) {
++ // CraftBukkit start
++ if (this.thundering == thundering) {
++ return;
++ }
++
++ org.bukkit.World world = Bukkit.getWorld(getLevelName());
++ if (world != null) {
++ ThunderChangeEvent thunder = new ThunderChangeEvent(world, thundering);
++ Bukkit.getServer().getPluginManager().callEvent(thunder);
++ if (thunder.isCancelled()) {
++ return;
++ }
++ }
++ // CraftBukkit end
++ this.thundering = thundering;
+ }
+
+ @Override
+@@ -389,9 +406,22 @@
+ }
+
+ @Override
+- @Override
+- public void setRaining(boolean flag) {
+- this.raining = flag;
++ public void setRaining(boolean isRaining) {
++ // CraftBukkit start
++ if (this.raining == isRaining) {
++ return;
++ }
++
++ org.bukkit.World world = Bukkit.getWorld(getLevelName());
++ if (world != null) {
++ WeatherChangeEvent weather = new WeatherChangeEvent(world, isRaining);
++ Bukkit.getServer().getPluginManager().callEvent(weather);
++ if (weather.isCancelled()) {
++ return;
++ }
++ }
++ // CraftBukkit end
++ this.raining = isRaining;
+ }
+
+ @Override
+@@ -470,6 +487,12 @@
+ @Override
+ public void setDifficulty(Difficulty difficulty) {
+ this.settings = this.settings.withDifficulty(difficulty);
++ // CraftBukkit start
++ ClientboundChangeDifficultyPacket packet = new ClientboundChangeDifficultyPacket(this.getDifficulty(), this.isDifficultyLocked());
++ for (ServerPlayer player : (java.util.List<ServerPlayer>) (java.util.List) world.players()) {
++ player.connection.send(packet);
++ }
++ // CraftBukkit end
+ }
+
+ @Override
+@@ -632,6 +629,14 @@
+ return this.settings.copy();
+ }
+
++ // CraftBukkit start - Check if the name stored in NBT is the correct one
++ public void checkName(String name) {
++ if (!this.settings.levelName.equals(name)) {
++ this.settings.levelName = name;
++ }
++ }
++ // CraftBukkit end
++
+ /** @deprecated */
+ @Deprecated
+ public static enum SpecialWorldProperty {
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/loot/LootDataManager.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/loot/LootDataManager.java.patch
new file mode 100644
index 0000000000..094a4a7cf4
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/loot/LootDataManager.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/world/level/storage/loot/LootDataManager.java
++++ b/net/minecraft/world/level/storage/loot/LootDataManager.java
+@@ -101,6 +101,13 @@
+ problemreporter_collector.get().forEach((s, s1) -> {
+ LootDataManager.LOGGER.warn("Found loot table element validation problem in {}: {}", s, s1);
+ });
++ // CraftBukkit start
++ map1.forEach((key, lootTable) -> {
++ if (object instanceof LootTable table) {
++ table.craftLootTable = new CraftLootTable(CraftNamespacedKey.fromMinecraft(key.location()), table);
++ }
++ });
++ // CraftBukkit end
+ this.elements = map1;
+ this.typeKeys = com_google_common_collect_immutablemultimap_builder.build();
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/loot/LootTable.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/loot/LootTable.java.patch
new file mode 100644
index 0000000000..4cdf24f83f
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/loot/LootTable.java.patch
@@ -0,0 +1,55 @@
+--- a/net/minecraft/world/level/storage/loot/LootTable.java
++++ b/net/minecraft/world/level/storage/loot/LootTable.java
+@@ -28,6 +29,13 @@
+ import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
+ import org.slf4j.Logger;
+
++// CraftBukkit start
++import org.bukkit.craftbukkit.CraftLootTable;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.event.world.LootGenerateEvent;
++// CraftBukkit end
++
+ public class LootTable {
+
+ private static final Logger LOGGER = LogUtils.getLogger();
+@@ -49,6 +57,7 @@
+ private final List<LootPool> pools;
+ private final List<LootItemFunction> functions;
+ private final BiFunction<ItemStack, LootContext, ItemStack> compositeFunction;
++ public CraftLootTable craftLootTable; // CraftBukkit
+
+ LootTable(LootContextParamSet lootcontextparamset, Optional<ResourceLocation> optional, List<LootPool> list, List<LootItemFunction> list1) {
+ this.paramSet = lootcontextparamset;
+@@ -147,12 +156,25 @@
+
+ }
+
+- public void fill(Container container, LootParams lootparams, long i) {
+- LootContext lootcontext = (new LootContext.Builder(lootparams)).withOptionalRandomSeed(i).create(this.randomSequence);
+- ObjectArrayList<ItemStack> objectarraylist = this.getRandomItems(lootcontext);
+- RandomSource randomsource = lootcontext.getRandom();
+- List<Integer> list = this.getAvailableSlots(container, randomsource);
++ public void fill(Container container, LootParams params, long seed) {
++ // CraftBukkit start
++ this.fillInventory(container, params, seed, false);
++ }
+
++ public void fillInventory(Container iinventory, LootParams lootparams, long i, boolean plugin) {
++ // CraftBukkit end
++ LootContext loottableinfo = (new LootContext.Builder(lootparams)).withOptionalRandomSeed(i).create(this.randomSequence);
++ ObjectArrayList<ItemStack> objectarraylist = this.getRandomItems(loottableinfo);
++ RandomSource randomsource = loottableinfo.getRandom();
++ // CraftBukkit start
++ LootGenerateEvent event = CraftEventFactory.callLootGenerateEvent(iinventory, this, loottableinfo, objectarraylist, plugin);
++ if (event.isCancelled()) {
++ return;
++ }
++ objectarraylist = event.getLoot().stream().map(CraftItemStack::asNMSCopy).collect(ObjectArrayList.toList());
++ // CraftBukkit end
++ List<Integer> list = this.getAvailableSlots(iinventory, randomsource);
++
+ this.shuffleAndSplitItems(objectarraylist, list.size(), randomsource);
+ ObjectListIterator objectlistiterator = objectarraylist.iterator();
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/loot/functions/LootingEnchantFunction.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/loot/functions/LootingEnchantFunction.java.patch
new file mode 100644
index 0000000000..fc87dc8f58
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/loot/functions/LootingEnchantFunction.java.patch
@@ -0,0 +1,19 @@
+--- a/net/minecraft/world/level/storage/loot/functions/LootingEnchantFunction.java
++++ b/net/minecraft/world/level/storage/loot/functions/LootingEnchantFunction.java
+@@ -60,9 +57,14 @@
+
+ if (entity instanceof LivingEntity) {
+ int i = EnchantmentHelper.getMobLooting((LivingEntity) entity);
++ // CraftBukkit start - use lootingModifier if set by plugin
++ if (context.hasParam(LootContextParams.LOOTING_MOD)) {
++ i = context.getParamOrNull(LootContextParams.LOOTING_MOD);
++ }
++ // CraftBukkit end
+
+- if (i == 0) {
+- return itemstack;
++ if (i <= 0) { // CraftBukkit - account for possible negative looting values from Bukkit
++ return stack;
+ }
+
+ float f = (float) i * this.value.getFloat(lootcontext);
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/loot/parameters/LootContextParams.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/loot/parameters/LootContextParams.java.patch
new file mode 100644
index 0000000000..06bff1eca5
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/loot/parameters/LootContextParams.java.patch
@@ -0,0 +1,10 @@
+--- a/net/minecraft/world/level/storage/loot/parameters/LootContextParams.java
++++ b/net/minecraft/world/level/storage/loot/parameters/LootContextParams.java
+@@ -21,6 +21,7 @@
+ public static final LootContextParam<BlockEntity> BLOCK_ENTITY = create("block_entity");
+ public static final LootContextParam<ItemStack> TOOL = create("tool");
+ public static final LootContextParam<Float> EXPLOSION_RADIUS = create("explosion_radius");
++ public static final LootContextParam<Integer> LOOTING_MOD = new LootContextParam<>(new ResourceLocation("bukkit:looting_mod")); // CraftBukkit
+
+ public LootContextParams() {}
+
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/loot/predicates/ExplosionCondition.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/loot/predicates/ExplosionCondition.java.patch
new file mode 100644
index 0000000000..e1866659bd
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/loot/predicates/ExplosionCondition.java.patch
@@ -0,0 +1,12 @@
+--- a/net/minecraft/world/level/storage/loot/predicates/ExplosionCondition.java
++++ b/net/minecraft/world/level/storage/loot/predicates/ExplosionCondition.java
+@@ -35,7 +32,8 @@
+ RandomSource randomsource = lootcontext.getRandom();
+ float f = 1.0F / ofloat;
+
+- return randomsource.nextFloat() <= f;
++ // CraftBukkit - <= to < to allow for plugins to completely disable block drops from explosions
++ return randomsource.nextFloat() < f;
+ } else {
+ return true;
+ }
diff --git a/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/loot/predicates/LootItemRandomChanceWithLootingCondition.java.patch b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/loot/predicates/LootItemRandomChanceWithLootingCondition.java.patch
new file mode 100644
index 0000000000..fd32980e86
--- /dev/null
+++ b/patch-remap/mache-spigotflower-stripped/net/minecraft/world/level/storage/loot/predicates/LootItemRandomChanceWithLootingCondition.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/world/level/storage/loot/predicates/LootItemRandomChanceWithLootingCondition.java
++++ b/net/minecraft/world/level/storage/loot/predicates/LootItemRandomChanceWithLootingCondition.java
+@@ -37,6 +34,11 @@
+ if (entity instanceof LivingEntity) {
+ i = EnchantmentHelper.getMobLooting((LivingEntity) entity);
+ }
++ // CraftBukkit start - only use lootingModifier if set by Bukkit
++ if (context.hasParam(LootContextParams.LOOTING_MOD)) {
++ i = context.getParamOrNull(LootContextParams.LOOTING_MOD);
++ }
++ // CraftBukkit end
+
+ return lootcontext.getRandom().nextFloat() < this.percent + (float) i * this.lootingMultiplier;
+ }