aboutsummaryrefslogtreecommitdiffhomepage
path: root/patch-remap/mache-vineflower-stripped/net/minecraft
diff options
context:
space:
mode:
Diffstat (limited to 'patch-remap/mache-vineflower-stripped/net/minecraft')
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/CrashReport.java.patch10
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/advancements/AdvancementHolder.java.patch24
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/advancements/AdvancementTree.java.patch11
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/commands/CommandSource.java.patch23
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/commands/CommandSourceStack.java.patch70
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/commands/Commands.java.patch192
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/commands/arguments/EntityArgument.java.patch24
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/commands/arguments/blocks/BlockStateParser.java.patch11
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/commands/arguments/selector/EntitySelector.java.patch11
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/commands/arguments/selector/EntitySelectorParser.java.patch37
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/core/cauldron/CauldronInteraction.java.patch300
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/core/dispenser/AbstractProjectileDispenseBehavior.java.patch69
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch64
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch133
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch908
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java.patch95
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java.patch46
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/nbt/ByteArrayTag.java.patch1
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/nbt/IntArrayTag.java.patch1
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/nbt/NbtIo.java.patch1
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/network/Connection.java.patch10
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/network/FriendlyByteBuf.java.patch54
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/network/chat/Component.java.patch27
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/network/chat/TextColor.java.patch36
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/PacketUtils.java.patch65
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch48
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/game/ClientboundInitializeBorderPacket.java.patch15
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java.patch33
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/game/ClientboundSetBorderCenterPacket.java.patch15
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java.patch1
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/game/ServerboundUseItemOnPacket.java.patch1
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/game/ServerboundUseItemPacket.java.patch1
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java.patch1
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/network/syncher/SynchedEntityData.java.patch35
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/Bootstrap.java.patch100
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/Main.java.patch316
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/MinecraftServer.java.patch673
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/PlayerAdvancements.java.patch39
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/ServerAdvancementManager.java.patch1
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/ServerFunctionManager.java.patch11
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/ServerScoreboard.java.patch194
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/ServerTickRateManager.java.patch55
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/bossevents/CustomBossEvent.java.patch30
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/DifficultyCommand.java.patch24
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/EffectCommands.java.patch46
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/GameRuleCommand.java.patch33
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/GiveCommand.java.patch1
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/ListPlayersCommand.java.patch28
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/LootCommand.java.patch22
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/PlaceCommand.java.patch47
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/ReloadCommand.java.patch19
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/ScheduleCommand.java.patch64
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/SetSpawnCommand.java.patch15
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/SpreadPlayersCommand.java.patch23
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/SummonCommand.java.patch13
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/TeleportCommand.java.patch71
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/TimeCommand.java.patch55
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/WorldBorderCommand.java.patch122
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/dedicated/DedicatedServer.java.patch302
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/dedicated/DedicatedServerProperties.java.patch95
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/dedicated/DedicatedServerSettings.java.patch26
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/dedicated/Settings.java.patch140
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/gui/MinecraftServerGui.java.patch22
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/level/ChunkHolder.java.patch153
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/level/ChunkMap.java.patch171
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/level/DistanceManager.java.patch173
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/level/ServerChunkCache.java.patch167
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/level/ServerEntity.java.patch199
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/level/ServerLevel.java.patch849
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/level/ServerPlayer.java.patch1312
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/level/ServerPlayerGameMode.java.patch327
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/level/TicketType.java.patch11
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/level/WorldGenRegion.java.patch30
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/network/LegacyQueryHandler.java.patch63
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch187
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch39
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerConnectionListener.java.patch79
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch1729
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch65
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch161
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch131
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/players/BanListEntry.java.patch34
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/players/GameProfileCache.java.patch1
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/players/OldUsersConverter.java.patch96
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/players/PlayerList.java.patch968
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/players/SleepStatus.java.patch49
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/players/StoredUserList.java.patch1
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/players/UserBanListEntry.java.patch1
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/rcon/RconConsoleSource.java.patch48
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/rcon/thread/RconClient.java.patch83
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/stats/ServerRecipeBook.java.patch36
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/stats/ServerStatsCounter.java.patch1
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/stats/StatsCounter.java.patch20
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/util/SpawnUtil.java.patch42
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/util/datafix/DataFixers.java.patch24
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java.patch34
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java.patch20
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/util/worldupdate/WorldUpgrader.java.patch40
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/CompoundContainer.java.patch76
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/Container.java.patch56
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/LockCode.java.patch35
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/SimpleContainer.java.patch79
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/damagesource/DamageSource.java.patch43
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/damagesource/DamageSources.java.patch18
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/effect/HealOrHarmMobEffect.java.patch22
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/effect/HungerMobEffect.java.patch15
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/effect/MobEffectUtil.java.patch40
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/effect/PoisonMobEffect.java.patch11
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/effect/RegenerationMobEffect.java.patch11
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/effect/SaturationMobEffect.java.patch34
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/AgeableMob.java.patch35
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/AreaEffectCloud.java.patch58
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/Entity.java.patch1065
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/EntitySelector.java.patch48
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/EntityType.java.patch138
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ExperienceOrb.java.patch161
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/Interaction.java.patch44
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ItemBasedSteering.java.patch17
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/LightningBolt.java.patch62
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/LivingEntity.java.patch1095
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/Mob.java.patch358
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/NeutralMob.java.patch24
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/PathfinderMob.java.patch28
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/attributes/Attributes.java.patch1
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java.patch88
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java.patch80
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java.patch35
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java.patch70
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java.patch59
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java.patch134
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java.patch40
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/ResetProfession.java.patch57
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/StartAttacking.java.patch41
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java.patch73
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java.patch38
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java.patch15
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/goal/EatBlockGoal.java.patch44
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java.patch30
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java.patch51
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java.patch32
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/goal/SitWhenOrderedToGoal.java.patch11
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/goal/TemptGoal.java.patch40
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/sensing/TemptingSensor.java.patch38
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/village/VillageSiege.java.patch13
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ambient/Bat.java.patch46
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Animal.java.patch121
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Bee.java.patch128
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Bucketable.java.patch52
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Cat.java.patch66
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Chicken.java.patch13
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Cow.java.patch43
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Dolphin.java.patch67
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Fox.java.patch106
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/IronGolem.java.patch13
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/MushroomCow.java.patch86
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Ocelot.java.patch11
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Panda.java.patch43
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Parrot.java.patch46
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Pig.java.patch33
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Pufferfish.java.patch25
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Rabbit.java.patch40
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Sheep.java.patch89
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/SnowGolem.java.patch78
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Turtle.java.patch46
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Wolf.java.patch75
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/allay/Allay.java.patch94
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/axolotl/Axolotl.java.patch44
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/frog/Tadpole.java.patch18
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/goat/Goat.java.patch42
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch108
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/horse/Llama.java.patch14
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java.patch37
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/horse/TraderLlama.java.patch18
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/sniffer/Sniffer.java.patch55
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch57
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch258
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch36
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch134
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/decoration/ArmorStand.java.patch259
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/decoration/HangingEntity.java.patch186
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/decoration/ItemFrame.java.patch87
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java.patch69
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/item/FallingBlockEntity.java.patch109
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/item/ItemEntity.java.patch158
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/item/PrimedTnt.java.patch57
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch28
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/CaveSpider.java.patch13
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Creeper.java.patch98
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Drowned.java.patch15
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/ElderGuardian.java.patch21
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/EnderMan.java.patch125
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Evoker.java.patch13
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Ghast.java.patch18
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Guardian.java.patch28
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Husk.java.patch14
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Illusioner.java.patch20
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Phantom.java.patch14
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Ravager.java.patch27
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Shulker.java.patch77
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Silverfish.java.patch106
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Skeleton.java.patch11
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Slime.java.patch85
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/SpellcasterIllager.java.patch24
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Spider.java.patch19
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Strider.java.patch21
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Vex.java.patch14
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Witch.java.patch24
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/WitherSkeleton.java.patch11
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Zombie.java.patch228
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/ZombieVillager.java.patch109
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/ZombifiedPiglin.java.patch68
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java.patch16
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/piglin/Piglin.java.patch162
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/piglin/PiglinAi.java.patch142
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/warden/Warden.java.patch14
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/npc/AbstractVillager.java.patch63
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/npc/InventoryCarrier.java.patch16
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/npc/Villager.java.patch106
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/npc/WanderingTrader.java.patch66
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch47
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/player/Inventory.java.patch103
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/player/Player.java.patch560
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/AbstractArrow.java.patch69
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java.patch84
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/Arrow.java.patch37
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/EvokerFangs.java.patch19
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/EyeOfEnder.java.patch25
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/Fireball.java.patch13
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch83
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/FishingHook.java.patch210
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/LargeFireball.java.patch55
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/LlamaSpit.java.patch23
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/Projectile.java.patch73
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ShulkerBullet.java.patch65
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/SmallFireball.java.patch75
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/SpectralArrow.java.patch14
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ThrowableItemProjectile.java.patch16
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ThrowableProjectile.java.patch13
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ThrownEgg.java.patch77
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch85
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java.patch24
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ThrownPotion.java.patch185
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ThrownTrident.java.patch27
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/WindCharge.java.patch1
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/WitherSkull.java.patch49
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/raid/Raid.java.patch199
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/raid/Raider.java.patch63
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/raid/Raids.java.patch36
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/vehicle/AbstractMinecart.java.patch218
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java.patch74
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/vehicle/Boat.java.patch114
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/vehicle/ChestBoat.java.patch68
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java.patch15
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/vehicle/MinecartTNT.java.patch40
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/vehicle/VehicleEntity.java.patch72
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/food/FoodData.java.patch109
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/AbstractContainerMenu.java.patch304
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/AbstractFurnaceMenu.java.patch56
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/AnvilMenu.java.patch171
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/BeaconMenu.java.patch67
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/BrewingStandMenu.java.patch55
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/CartographyTableMenu.java.patch83
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/ChestMenu.java.patch69
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/ContainerLevelAccess.java.patch44
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/CrafterMenu.java.patch38
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/CraftingMenu.java.patch75
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/DispenserMenu.java.patch63
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/EnchantmentMenu.java.patch261
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/FurnaceResultSlot.java.patch22
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/GrindstoneMenu.java.patch153
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/HopperMenu.java.patch50
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/HorseInventoryMenu.java.patch36
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/InventoryMenu.java.patch61
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/ItemCombinerMenu.java.patch20
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/LecternMenu.java.patch100
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/LoomMenu.java.patch121
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/MenuType.java.patch13
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/MerchantContainer.java.patch63
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/MerchantMenu.java.patch53
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/PlayerEnderChestContainer.java.patch25
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/ResultContainer.java.patch58
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/ShulkerBoxMenu.java.patch48
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/SmithingMenu.java.patch70
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/StonecutterMenu.java.patch97
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/TransientCraftingContainer.java.patch88
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/ArmorItem.java.patch64
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/ArmorStandItem.java.patch27
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/BlockItem.java.patch159
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/BoatItem.java.patch43
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/BoneMealItem.java.patch47
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/BowItem.java.patch34
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/BucketItem.java.patch138
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/ChorusFruitItem.java.patch30
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/CrossbowItem.java.patch34
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/DebugStickItem.java.patch1
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/DyeItem.java.patch38
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/EggItem.java.patch42
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/EndCrystalItem.java.patch33
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/EnderEyeItem.java.patch32
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/EnderpearlItem.java.patch37
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/FireChargeItem.java.patch47
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/FishingRodItem.java.patch42
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/FlintAndSteelItem.java.patch70
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/HangingEntityItem.java.patch44
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/ItemStack.java.patch327
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/LeadItem.java.patch99
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/MapItem.java.patch14
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/MilkBucketItem.java.patch11
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/MinecartItem.java.patch90
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/PotionItem.java.patch11
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/RecordItem.java.patch36
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/SignItem.java.patch42
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/SnowballItem.java.patch41
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/SpawnEggItem.java.patch13
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/StandingAndWallBlockItem.java.patch34
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/TridentItem.java.patch100
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/BlastingRecipe.java.patch35
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/CampfireCookingRecipe.java.patch35
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/CustomRecipe.java.patch29
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/Ingredient.java.patch35
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/Recipe.java.patch9
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/RecipeHolder.java.patch22
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/RecipeManager.java.patch165
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/ShapedRecipe.java.patch86
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/ShapelessRecipe.java.patch39
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/SmeltingRecipe.java.patch35
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/SmithingTransformRecipe.java.patch60
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/SmithingTrimRecipe.java.patch37
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/SmokingRecipe.java.patch35
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/StonecutterRecipe.java.patch34
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/enchantment/DamageEnchantment.java.patch20
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/enchantment/FrostWalkerEnchantment.java.patch36
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/trading/Merchant.java.patch9
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/item/trading/MerchantOffer.java.patch73
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/BaseCommandBlock.java.patch76
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/BaseSpawner.java.patch24
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/BlockGetter.java.patch48
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/ClipContext.java.patch11
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/Explosion.java.patch209
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/GameRules.java.patch1
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/Level.java.patch341
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/LevelAccessor.java.patch9
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/LevelWriter.java.patch13
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/NaturalSpawner.java.patch132
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/ServerLevelAccessor.java.patch21
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/AbstractCandleBlock.java.patch14
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BambooSaplingBlock.java.patch10
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BambooStalkBlock.java.patch75
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BaseFireBlock.java.patch47
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BasePressurePlateBlock.java.patch59
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BedBlock.java.patch82
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BeehiveBlock.java.patch22
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BellBlock.java.patch14
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BigDripleafBlock.java.patch113
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/Block.java.patch57
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BuddingAmethystBlock.java.patch16
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BushBlock.java.patch22
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ButtonBlock.java.patch79
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CactusBlock.java.patch43
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CakeBlock.java.patch27
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CampfireBlock.java.patch48
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CarvedPumpkinBlock.java.patch31
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CauldronBlock.java.patch31
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CaveVines.java.patch43
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CeilingHangingSignBlock.java.patch1
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ChangeOverTimeBlock.java.patch16
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ChestBlock.java.patch78
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ChorusFlowerBlock.java.patch95
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CocoaBlock.java.patch21
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CommandBlock.java.patch47
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ComparatorBlock.java.patch42
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ComposterBlock.java.patch158
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ConcretePowderBlock.java.patch87
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CoralBlock.java.patch14
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CoralFanBlock.java.patch16
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CoralPlantBlock.java.patch16
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CoralWallFanBlock.java.patch16
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CropBlock.java.patch49
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DaylightDetectorBlock.java.patch16
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DetectorRailBlock.java.patch29
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DiodeBlock.java.patch44
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DispenserBlock.java.patch27
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DoorBlock.java.patch46
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DoublePlantBlock.java.patch35
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DragonEggBlock.java.patch40
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DropExperienceBlock.java.patch21
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DropperBlock.java.patch53
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/EndPortalBlock.java.patch51
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/FarmBlock.java.patch79
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/FenceGateBlock.java.patch40
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/FireBlock.java.patch182
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/FungusBlock.java.patch21
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/GrowingPlantHeadBlock.java.patch20
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/IceBlock.java.patch16
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/InfestedBlock.java.patch28
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/LayeredCauldronBlock.java.patch99
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/LeavesBlock.java.patch25
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/LecternBlock.java.patch36
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/LeverBlock.java.patch40
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/LightningRodBlock.java.patch66
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/LiquidBlock.java.patch29
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/MagmaBlock.java.patch16
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/MultifaceSpreader.java.patch45
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/MushroomBlock.java.patch33
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/NetherPortalBlock.java.patch34
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/NetherWartBlock.java.patch13
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/NoteBlock.java.patch37
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/NyliumBlock.java.patch14
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ObserverBlock.java.patch38
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/PointedDripstoneBlock.java.patch92
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/PowderSnowBlock.java.patch18
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/PoweredRailBlock.java.patch26
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/PressurePlateBlock.java.patch63
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/RedStoneOreBlock.java.patch115
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/RedStoneWireBlock.java.patch29
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/RedstoneLampBlock.java.patch41
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/RedstoneTorchBlock.java.patch57
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/RespawnAnchorBlock.java.patch27
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/RootedDirtBlock.java.patch12
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SaplingBlock.java.patch62
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ScaffoldingBlock.java.patch22
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SculkBlock.java.patch42
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SculkCatalystBlock.java.patch21
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SculkSensorBlock.java.patch114
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SculkShriekerBlock.java.patch44
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SculkSpreader.java.patch41
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SculkVeinBlock.java.patch70
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SignBlock.java.patch15
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SnowLayerBlock.java.patch18
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SpawnerBlock.java.patch26
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SpongeBlock.java.patch122
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java.patch28
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/StemBlock.java.patch56
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SugarCaneBlock.java.patch18
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SweetBerryBushBlock.java.patch79
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/TntBlock.java.patch80
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/TrapDoorBlock.java.patch42
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/TripWireBlock.java.patch51
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/TripWireHookBlock.java.patch31
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/TurtleEggBlock.java.patch77
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/VineBlock.java.patch102
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/WallHangingSignBlock.java.patch1
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/WaterlilyBlock.java.patch24
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/WeightedPressurePlateBlock.java.patch46
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/WitherRoseBlock.java.patch17
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/WitherSkullBlock.java.patch58
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch268
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BannerBlockEntity.java.patch14
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch63
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java.patch15
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch123
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java.patch186
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BellBlockEntity.java.patch17
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BlockEntity.java.patch68
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch189
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BrushableBlockEntity.java.patch56
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/CampfireBlockEntity.java.patch76
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/ChestBlockEntity.java.patch67
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch98
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/CommandBlockEntity.java.patch17
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch74
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch94
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/CrafterBlockEntity.java.patch91
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java.patch62
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch51
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/HopperBlockEntity.java.patch231
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch88
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch158
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java.patch36
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch73
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/SignBlockEntity.java.patch154
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch28
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java.patch71
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch54
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/state/BlockBehaviour.java.patch28
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/border/WorldBorder.java.patch22
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/ChunkAccess.java.patch77
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/ChunkGenerator.java.patch104
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java.patch1
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/ChunkStatus.java.patch26
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/DataLayer.java.patch1
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/LevelChunk.java.patch235
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/LevelChunkSection.java.patch35
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/storage/ChunkSerializer.java.patch117
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch110
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/storage/RegionFile.java.patch1
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch76
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch55
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/entity/PersistentEntitySectionManager.java.patch130
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/gameevent/GameEventDispatcher.java.patch54
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java.patch38
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/FlatLevelSource.java.patch24
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java.patch1
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/PatrolSpawner.java.patch15
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java.patch38
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/StructurePiece.java.patch173
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/StructureStart.java.patch44
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java.patch18
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java.patch21
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java.patch23
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java.patch25
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java.patch37
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java.patch30
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java.patch150
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java.patch42
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java.patch27
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java.patch158
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/material/FlowingFluid.java.patch120
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/material/LavaFluid.java.patch57
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/portal/PortalForcer.java.patch176
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/portal/PortalInfo.java.patch33
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/portal/PortalShape.java.patch150
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/redstone/NeighborUpdater.java.patch34
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java.patch182
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/LevelStorageSource.java.patch110
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/PlayerDataStorage.java.patch73
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/PrimaryLevelData.java.patch177
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/loot/LootDataManager.java.patch29
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/loot/LootTable.java.patch65
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/loot/functions/LootingEnchantFunction.java.patch19
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/loot/parameters/LootContextParams.java.patch10
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/loot/predicates/ExplosionCondition.java.patch22
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/loot/predicates/LootItemRandomChanceWithLootingCondition.java.patch14
523 files changed, 40891 insertions, 0 deletions
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/CrashReport.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/CrashReport.java.patch
new file mode 100644
index 0000000000..3b58cd946f
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/CrashReport.java.patch
@@ -0,0 +1,10 @@
+--- a/net/minecraft/CrashReport.java
++++ b/net/minecraft/CrashReport.java
+@@ -35,6 +36,7 @@
+ 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-vineflower-stripped/net/minecraft/advancements/AdvancementHolder.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/advancements/AdvancementHolder.java.patch
new file mode 100644
index 0000000000..b9a4741667
--- /dev/null
+++ b/patch-remap/mache-vineflower-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) {
+ public void write(FriendlyByteBuf friendlyByteBuf) {
+@@ -35,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-vineflower-stripped/net/minecraft/advancements/AdvancementTree.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/advancements/AdvancementTree.java.patch
new file mode 100644
index 0000000000..31d8d7fc66
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/advancements/AdvancementTree.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/advancements/AdvancementTree.java
++++ b/net/minecraft/advancements/AdvancementTree.java
+@@ -62,7 +77,7 @@
+ }
+ }
+
+- 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-vineflower-stripped/net/minecraft/commands/CommandSource.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/commands/CommandSource.java.patch
new file mode 100644
index 0000000000..3b19ad7917
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/commands/CommandSource.java.patch
@@ -0,0 +1,23 @@
+--- a/net/minecraft/commands/CommandSource.java
++++ b/net/minecraft/commands/CommandSource.java
+@@ -22,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);
+@@ -35,4 +42,6 @@
+ default boolean alwaysAccepts() {
+ return false;
+ }
++
++ org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper); // CraftBukkit
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/commands/CommandSourceStack.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/commands/CommandSourceStack.java.patch
new file mode 100644
index 0000000000..9c3c14d70e
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/commands/CommandSourceStack.java.patch
@@ -0,0 +1,70 @@
+--- a/net/minecraft/commands/CommandSourceStack.java
++++ b/net/minecraft/commands/CommandSourceStack.java
+@@ -42,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 {
+ public static final SimpleCommandExceptionType ERROR_NOT_PLAYER = new SimpleCommandExceptionType(Component.translatable("permissions.requires.player"));
+@@ -61,6 +64,7 @@
+ private final Vec2 rotation;
+ private final CommandSigningContext signingContext;
+ private final TaskChainer chatMessageChainer;
++ public volatile CommandNode currentCommand; // CraftBukkit
+
+ public CommandSourceStack(
+ CommandSource source,
+@@ -389,9 +171,23 @@
+
+ @Override
+ 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;
+ }
+@@ -496,9 +315,13 @@
+ private void broadcastToAdmins(Component message) {
+ Component component = Component.translatable("chat.type.admin", this.getDisplayName(), message).withStyle(ChatFormatting.GRAY, ChatFormatting.ITALIC);
+ if (this.server.getGameRules().getBoolean(GameRules.RULE_SENDCOMMANDFEEDBACK)) {
+- for (ServerPlayer serverPlayer : this.server.getPlayerList().getPlayers()) {
+- if (serverPlayer != this.source && this.server.getPlayerList().isOp(serverPlayer.getGameProfile())) {
+- serverPlayer.sendSystemMessage(component);
++ Iterator iterator = this.server.getPlayerList().getPlayers().iterator();
++
++ while (iterator.hasNext()) {
++ ServerPlayer entityplayer = (ServerPlayer) iterator.next();
++
++ if (entityplayer != this.source && entityplayer.getBukkitEntity().hasPermission("minecraft.admin.command_feedback")) { // CraftBukkit
++ entityplayer.sendSystemMessage(ichatmutablecomponent);
+ }
+ }
+ }
+@@ -592,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-vineflower-stripped/net/minecraft/commands/Commands.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/commands/Commands.java.patch
new file mode 100644
index 0000000000..26b1a12ad0
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/commands/Commands.java.patch
@@ -0,0 +1,192 @@
+--- a/net/minecraft/commands/Commands.java
++++ b/net/minecraft/commands/Commands.java
+@@ -137,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<>();
+ private static final Logger LOGGER = LogUtils.getLogger();
+@@ -148,6 +154,7 @@
+ private final CommandDispatcher<CommandSourceStack> dispatcher = new CommandDispatcher<>();
+
+ public Commands(Commands.CommandSelection selection, CommandBuildContext context) {
++ this(); // CraftBukkit
+ AdvancementCommands.register(this.dispatcher);
+ AttributeCommand.register(this.dispatcher, context);
+ ExecuteCommand.register(this.dispatcher, context);
+@@ -248,6 +255,11 @@
+ PublishCommand.register(this.dispatcher);
+ }
+
++ // CraftBukkit start
++ }
++
++ public Commands() {
++ // CraftBukkkit end
+ this.dispatcher.setConsumer(ExecutionCommandSource.resultConsumer());
+ }
+
+@@ -257,16 +270,66 @@
+ return new ParseResults<>(commandContextBuilder, parseResults.getReader(), parseResults.getExceptions());
+ }
+
+- public void performPrefixedCommand(CommandSourceStack commandSourceStack, String string) {
+- string = string.startsWith("/") ? string.substring(1) : string;
+- this.performCommand(this.dispatcher.parse(string, commandSourceStack), string);
++ // 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 performCommand(ParseResults<CommandSourceStack> parseResults, String string) {
+- CommandSourceStack commandSourceStack = parseResults.getContext().getSource();
+- commandSourceStack.getServer().getProfiler().push(() -> "/" + string);
+- ContextChain<CommandSourceStack> contextChain = finishParsing(parseResults, string, commandSourceStack);
++ 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, commandlistenerwrapper), s, label);
++ // CraftBukkit end
++ }
++
++ public void performCommand(ParseResults<CommandSourceStack> parseresults, String s) {
++ this.performCommand(parseresults, s, s);
++ }
++
++ 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, commandlistenerwrapper, label); // CraftBukkit
++
+ try {
+ if (contextChain != null) {
+ executeCommandInContext(
+@@ -306,22 +362,22 @@
+ }
+
+ @Nullable
+- private static ContextChain<CommandSourceStack> finishParsing(
+- ParseResults<CommandSourceStack> parseResults, String string, CommandSourceStack commandSourceStack
+- ) {
++ private static ContextChain<CommandSourceStack> finishParsing(ParseResults<CommandSourceStack> parseresults, String s, CommandSourceStack commandlistenerwrapper, String label) { // CraftBukkit
+ try {
+- validateParseResults(parseResults);
+- return ContextChain.tryFlatten(parseResults.getContext().build(string))
+- .orElseThrow(() -> CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand().createWithContext(parseResults.getReader()));
+- } catch (CommandSyntaxException var7) {
+- commandSourceStack.sendFailure(ComponentUtils.fromMessage(var7.getRawMessage()));
+- if (var7.getInput() != null && var7.getCursor() >= 0) {
+- int min = Math.min(var7.getInput().length(), var7.getCursor());
+- MutableComponent mutableComponent = Component.empty()
+- .withStyle(ChatFormatting.GRAY)
+- .withStyle(style -> style.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + string)));
+- if (min > 10) {
+- mutableComponent.append(CommonComponents.ELLIPSIS);
++ validateParseResults(parseresults);
++ return (ContextChain) ContextChain.tryFlatten(parseresults.getContext().build(s)).orElseThrow(() -> {
++ return CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand().createWithContext(parseresults.getReader());
++ });
++ } catch (CommandSyntaxException commandsyntaxexception) {
++ commandlistenerwrapper.sendFailure(ComponentUtils.fromMessage(commandsyntaxexception.getRawMessage()));
++ if (commandsyntaxexception.getInput() != null && commandsyntaxexception.getCursor() >= 0) {
++ int i = Math.min(commandsyntaxexception.getInput().length(), commandsyntaxexception.getCursor());
++ MutableComponent ichatmutablecomponent = Component.empty().withStyle(ChatFormatting.GRAY).withStyle((chatmodifier) -> {
++ return chatmodifier.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, label)); // CraftBukkit
++ });
++
++ if (i > 10) {
++ ichatmutablecomponent.append(CommonComponents.ELLIPSIS);
+ }
+
+ mutableComponent.append(var7.getInput().substring(Math.max(0, min - 10), min));
+@@ -359,11 +432,37 @@
+ }
+
+ public void sendCommands(ServerPlayer player) {
+- Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> map = Maps.newHashMap();
+- RootCommandNode<SharedSuggestionProvider> rootCommandNode = new RootCommandNode<>();
+- map.put(this.dispatcher.getRoot(), rootCommandNode);
+- this.fillUsableCommands(this.dispatcher.getRoot(), rootCommandNode, player.createCommandSourceStack(), map);
+- player.connection.send(new ClientboundCommandsPacket(rootCommandNode));
++ // 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, 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(
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/commands/arguments/EntityArgument.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/commands/arguments/EntityArgument.java.patch
new file mode 100644
index 0000000000..ed40be16ac
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/commands/arguments/EntityArgument.java.patch
@@ -0,0 +1,24 @@
+--- a/net/minecraft/commands/arguments/EntityArgument.java
++++ b/net/minecraft/commands/arguments/EntityArgument.java
+@@ -96,10 +94,17 @@
+
+ @Override
+ public EntitySelector parse(StringReader reader) throws CommandSyntaxException {
+- int i = 0;
+- EntitySelectorParser entitySelectorParser = new EntitySelectorParser(reader);
+- EntitySelector entitySelector = entitySelectorParser.parse();
+- if (entitySelector.getMaxResults() > 1 && this.single) {
++ // CraftBukkit start
++ return parse(reader, false);
++ }
++
++ public EntitySelector parse(StringReader stringreader, boolean overridePermissions) throws CommandSyntaxException {
++ // CraftBukkit end
++ boolean flag = false;
++ EntitySelectorParser argumentparserselector = new EntitySelectorParser(stringreader);
++ EntitySelector entityselector = argumentparserselector.parse(overridePermissions); // CraftBukkit
++
++ if (entityselector.getMaxResults() > 1 && this.single) {
+ if (this.playersOnly) {
+ reader.setCursor(0);
+ throw ERROR_NOT_SINGLE_PLAYER.createWithContext(reader);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/commands/arguments/blocks/BlockStateParser.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/commands/arguments/blocks/BlockStateParser.java.patch
new file mode 100644
index 0000000000..480d8c4475
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -69,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-vineflower-stripped/net/minecraft/commands/arguments/selector/EntitySelector.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/commands/arguments/selector/EntitySelector.java.patch
new file mode 100644
index 0000000000..ca6a686ad3
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/commands/arguments/selector/EntitySelector.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/commands/arguments/selector/EntitySelector.java
++++ b/net/minecraft/commands/arguments/selector/EntitySelector.java
+@@ -105,7 +92,7 @@
+ }
+
+ private void checkPermissions(CommandSourceStack source) throws CommandSyntaxException {
+- if (this.usesSelector && !source.hasPermission(2)) {
++ if (this.usesSelector && !source.hasPermission(2, "minecraft.command.selector")) { // CraftBukkit
+ throw EntityArgument.ERROR_SELECTORS_NOT_ALLOWED.create();
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/commands/arguments/selector/EntitySelectorParser.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/commands/arguments/selector/EntitySelectorParser.java.patch
new file mode 100644
index 0000000000..39f5ad0c5f
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -197,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 ERROR_MISSING_SELECTOR_TYPE.createWithContext(this.reader);
+@@ -448,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() == '@') {
+@@ -456,7 +479,7 @@
+ }
+
+ this.reader.skip();
+- this.parseSelector();
++ this.parseSelector(overridePermissions); // CraftBukkit
+ } else {
+ this.parseNameOrUUID();
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/core/cauldron/CauldronInteraction.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/core/cauldron/CauldronInteraction.java.patch
new file mode 100644
index 0000000000..f0364dd0fa
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/core/cauldron/CauldronInteraction.java.patch
@@ -0,0 +1,300 @@
+--- a/net/minecraft/core/cauldron/CauldronInteraction.java
++++ b/net/minecraft/core/cauldron/CauldronInteraction.java
+@@ -30,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<>();
+ Codec<CauldronInteraction.InteractionMap> CODEC = ExtraCodecs.stringResolverCodec(CauldronInteraction.InteractionMap::name, INTERACTIONS::get);
+@@ -69,10 +39,22 @@
+ itemStack.setTag(stack.getTag().copy());
+ }
+
+- player.setItemInHand(hand, itemStack);
+- player.awardStat(Stats.CLEAN_SHULKER_BOX);
+- LayeredCauldronBlock.lowerFillLevel(blockState, level, blockPos);
+- }
++ Map<String, CauldronInteraction.a> INTERACTIONS = new Object2ObjectArrayMap();
++ // CraftBukkit start - decompile error
++ /*
++ Codec<CauldronInteraction.a> CODEC;
++ CauldronInteraction.a EMPTY;
++ CauldronInteraction.a WATER;
++ CauldronInteraction.a LAVA;
++ CauldronInteraction.a POWDER_SNOW;
++ CauldronInteraction FILL_WATER;
++ CauldronInteraction FILL_LAVA;
++ CauldronInteraction FILL_POWDER_SNOW;
++ CauldronInteraction SHULKER_BOX;
++ CauldronInteraction BANNER;
++ CauldronInteraction DYED_ITEM;
++ */
++ // CraftBukkit end
+
+ return InteractionResult.sidedSuccess(level.isClientSide);
+ }
+@@ -136,14 +78,20 @@
+ if (PotionUtils.getPotion(stack) != Potions.WATER) {
+ return InteractionResult.PASS;
+ } else {
+- if (!level.isClientSide) {
+- Item item = stack.getItem();
+- player.setItemInHand(hand, ItemUtils.createFilledResult(stack, 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(null, blockPos, SoundEvents.BOTTLE_EMPTY, SoundSource.BLOCKS, 1.0F, 1.0F);
+- level.gameEvent(null, GameEvent.FLUID_PLACE, blockPos);
++ 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();
++
++ 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);
+@@ -151,42 +100,44 @@
+ });
+ Map<Item, CauldronInteraction> map1 = WATER.map();
+ addDefaultInteractions(map1);
+- map1.put(
+- Items.BUCKET,
+- (blockState, level, blockPos, player, hand, stack) -> fillBucket(
+- blockState,
+- level,
+- blockPos,
+- player,
+- hand,
+- stack,
+- new ItemStack(Items.WATER_BUCKET),
+- blockState1 -> blockState1.getValue(LayeredCauldronBlock.LEVEL) == 3,
+- SoundEvents.BUCKET_FILL
+- )
+- );
+- map1.put(Items.GLASS_BOTTLE, (blockState, level, blockPos, player, hand, stack) -> {
+- if (!level.isClientSide) {
+- Item item = stack.getItem();
+- player.setItemInHand(hand, ItemUtils.createFilledResult(stack, 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(null, blockPos, SoundEvents.BOTTLE_FILL, SoundSource.BLOCKS, 1.0F, 1.0F);
+- level.gameEvent(null, GameEvent.FLUID_PICKUP, blockPos);
++ map1.put(Items.BUCKET, (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> {
++ return fillBucket(iblockdata, world, blockposition, entityhuman, enumhand, itemstack, new ItemStack(Items.WATER_BUCKET), (iblockdata1) -> {
++ return (Integer) iblockdata1.getValue(LayeredCauldronBlock.LEVEL) == 3;
++ }, SoundEvents.BUCKET_FILL);
++ });
++ 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();
++
++ 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, hand, stack) -> {
+- if (blockState.getValue(LayeredCauldronBlock.LEVEL) != 3 && PotionUtils.getPotion(stack) == Potions.WATER) {
+- if (!level.isClientSide) {
+- player.setItemInHand(hand, ItemUtils.createFilledResult(stack, player, new ItemStack(Items.GLASS_BOTTLE)));
+- player.awardStat(Stats.USE_CAULDRON);
+- player.awardStat(Stats.ITEM_USED.get(stack.getItem()));
+- level.setBlockAndUpdate(blockPos, blockState.cycle(LayeredCauldronBlock.LEVEL));
+- level.playSound(null, blockPos, SoundEvents.BOTTLE_EMPTY, SoundSource.BLOCKS, 1.0F, 1.0F);
+- level.gameEvent(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);
+@@ -278,13 +211,18 @@
+ return InteractionResult.PASS;
+ } else {
+ if (!level.isClientSide) {
++ // 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(hand, ItemUtils.createFilledResult(emptyStack, player, filledStack));
+ player.awardStat(Stats.USE_CAULDRON);
+ player.awardStat(Stats.ITEM_USED.get(item));
+- level.setBlockAndUpdate(pos, Blocks.CAULDRON.defaultBlockState());
+- level.playSound(null, pos, fillSound, SoundSource.BLOCKS, 1.0F, 1.0F);
+- level.gameEvent(null, GameEvent.FLUID_PICKUP, pos);
++ // 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);
+@@ -295,18 +232,127 @@
+ Level level, BlockPos pos, Player player, InteractionHand hand, ItemStack filledStack, BlockState state, SoundEvent emptySound
+ ) {
+ if (!level.isClientSide) {
++ // 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(hand, ItemUtils.createFilledResult(filledStack, player, new ItemStack(Items.BUCKET)));
+ player.awardStat(Stats.FILL_CAULDRON);
+ player.awardStat(Stats.ITEM_USED.get(item));
+- level.setBlockAndUpdate(pos, state);
+- level.playSound(null, pos, emptySound, SoundSource.BLOCKS, 1.0F, 1.0F);
+- level.gameEvent(null, GameEvent.FLUID_PLACE, pos);
++ // 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);
+ }
+
+- public static record InteractionMap(String name, Map<Item, CauldronInteraction> map) {
++ // CraftBukkit start - decompile errors
++ // static {
++ Codec<CauldronInteraction.a> CODEC = ExtraCodecs.stringResolverCodec(CauldronInteraction.a::name, CauldronInteraction.INTERACTIONS::get);
++ CauldronInteraction.a EMPTY = newInteractionMap("empty");
++ CauldronInteraction.a WATER = newInteractionMap("water");
++ CauldronInteraction.a LAVA = newInteractionMap("lava");
++ CauldronInteraction.a POWDER_SNOW = newInteractionMap("powder_snow");
++ CauldronInteraction FILL_WATER = (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> {
++ return emptyBucket(world, blockposition, entityhuman, enumhand, itemstack, (IBlockData) Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, 3), SoundEvents.BUCKET_EMPTY);
++ };
++ CauldronInteraction FILL_LAVA = (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> {
++ return emptyBucket(world, blockposition, entityhuman, enumhand, itemstack, Blocks.LAVA_CAULDRON.defaultBlockState(), SoundEvents.BUCKET_EMPTY_LAVA);
++ };
++ CauldronInteraction FILL_POWDER_SNOW = (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> {
++ return emptyBucket(world, blockposition, entityhuman, enumhand, itemstack, (IBlockData) Blocks.POWDER_SNOW_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, 3), SoundEvents.BUCKET_EMPTY_POWDER_SNOW);
++ };
++ 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 (!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());
++ }
++
++ entityhuman.setItemInHand(enumhand, itemstack1);
++ entityhuman.awardStat(Stats.CLEAN_SHULKER_BOX);
++ // LayeredCauldronBlock.lowerFillLevel(iblockdata, world, blockposition); // CraftBukkit
++ }
++
++ return InteractionResult.sidedSuccess(world.isClientSide);
++ }
++ };
++ CauldronInteraction BANNER = (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> { // CraftBukkit - decompile error
++ if (BannerBlockEntity.getPatternCount(itemstack) <= 0) {
++ return InteractionResult.PASS;
++ } else {
++ 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);
++ if (!entityhuman.getAbilities().instabuild) {
++ itemstack.shrink(1);
++ }
++
++ if (itemstack.isEmpty()) {
++ entityhuman.setItemInHand(enumhand, itemstack1);
++ } else if (entityhuman.getInventory().add(itemstack1)) {
++ entityhuman.inventoryMenu.sendAllDataToRemote();
++ } else {
++ entityhuman.drop(itemstack1, false);
++ }
++
++ entityhuman.awardStat(Stats.CLEAN_BANNER);
++ // LayeredCauldronBlock.lowerFillLevel(iblockdata, world, blockposition); // CraftBukkit
++ }
++
++ return InteractionResult.sidedSuccess(world.isClientSide);
++ }
++ };
++ CauldronInteraction DYED_ITEM = (iblockdata, world, blockposition, entityhuman, enumhand, itemstack) -> { // CraftBukkit - decompile error
++ Item item = itemstack.getItem();
++
++ if (!(item instanceof DyeableLeatherItem)) {
++ return InteractionResult.PASS;
++ } else {
++ DyeableLeatherItem idyeable = (DyeableLeatherItem) item;
++
++ if (!idyeable.hasCustomColor(itemstack)) {
++ return InteractionResult.PASS;
++ } else {
++ 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(world.isClientSide);
++ }
++ }
++ };
++ // } // CraftBukkit - decompile error
++
++ public static record a(String name, Map<Item, CauldronInteraction> map) {
++
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/core/dispenser/AbstractProjectileDispenseBehavior.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/core/dispenser/AbstractProjectileDispenseBehavior.java.patch
new file mode 100644
index 0000000000..1683198993
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/core/dispenser/AbstractProjectileDispenseBehavior.java.patch
@@ -0,0 +1,69 @@
+--- a/net/minecraft/core/dispenser/AbstractProjectileDispenseBehavior.java
++++ b/net/minecraft/core/dispenser/AbstractProjectileDispenseBehavior.java
+@@ -6,20 +6,55 @@
+ 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 {
+ @Override
+- public ItemStack execute(BlockSource blockSource, ItemStack itemStack) {
+- Level level = blockSource.level();
+- Position dispensePosition = DispenserBlock.getDispensePosition(blockSource);
+- Direction direction = blockSource.state().getValue(DispenserBlock.FACING);
+- Projectile projectile = this.getProjectile(level, dispensePosition, itemStack);
+- projectile.shoot(
+- (double)direction.getStepX(), (double)((float)direction.getStepY() + 0.1F), (double)direction.getStepZ(), this.getPower(), this.getUncertainty()
+- );
+- level.addFreshEntity(projectile);
+- itemStack.shrink(1);
+- return itemStack;
++ public ItemStack execute(SourceBlock sourceblock, ItemStack itemstack) {
++ ServerLevel worldserver = sourceblock.level();
++ IPosition iposition = DispenserBlock.getDispensePosition(sourceblock);
++ Direction enumdirection = (Direction) sourceblock.state().getValue(DispenserBlock.FACING);
++ Projectile iprojectile = this.getProjectile(worldserver, iposition, itemstack);
++
++ // 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;
+ }
+
+ @Override
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch
new file mode 100644
index 0000000000..4386593919
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java.patch
@@ -0,0 +1,64 @@
+--- a/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java
++++ b/net/minecraft/core/dispenser/BoatDispenseItemBehavior.java
+@@ -10,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 {
+ private final DefaultDispenseItemBehavior defaultDispenseItemBehavior = new DefaultDispenseItemBehavior();
+@@ -46,13 +56,42 @@
+ d4 = 0.0;
+ }
+
+- Boat boat = (Boat)(this.isChestBoat ? new ChestBoat(serverLevel, d1, d2 + d4, d3) : new Boat(serverLevel, d1, d2 + d4, d3));
+- EntityType.<Boat>createDefaultStackConfig(serverLevel, itemStack, null).accept(boat);
+- boat.setVariant(this.type);
+- boat.setYRot(direction.toYRot());
+- serverLevel.addFreshEntity(boat);
+- itemStack.shrink(1);
+- return itemStack;
++ // 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);
++
++ 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(enumdirection.toYRot());
++ if (!worldserver.addFreshEntity((Entity) object)) itemstack.grow(1); // CraftBukkit
++ // itemstack.shrink(1); // CraftBukkit - handled during event processing
++ return itemstack;
+ }
+
+ @Override
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch
new file mode 100644
index 0000000000..2f73c33085
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java.patch
@@ -0,0 +1,133 @@
+--- a/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java
++++ b/net/minecraft/core/dispenser/DefaultDispenseItemBehavior.java
+@@ -6,8 +6,25 @@
+ 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
+ public final ItemStack dispense(BlockSource blockSource, ItemStack itemStack) {
+ ItemStack itemStack1 = this.execute(blockSource, itemStack);
+@@ -16,36 +34,82 @@
+ return itemStack1;
+ }
+
+- protected ItemStack execute(BlockSource blockSource, ItemStack itemStack) {
+- Direction direction = blockSource.state().getValue(DispenserBlock.FACING);
+- Position dispensePosition = DispenserBlock.getDispensePosition(blockSource);
+- ItemStack itemStack1 = itemStack.split(1);
+- spawnItem(blockSource.level(), itemStack1, 6, direction, dispensePosition);
+- return itemStack;
++ protected ItemStack execute(SourceBlock sourceblock, ItemStack itemstack) {
++ Direction enumdirection = (Direction) sourceblock.state().getValue(DispenserBlock.FACING);
++ IPosition iposition = DispenserBlock.getDispensePosition(sourceblock);
++ ItemStack itemstack1 = itemstack.split(1);
++
++ // 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 stack, int speed, Direction facing, Position position) {
+- double d = position.x();
+- double d1 = position.y();
+- double d2 = position.z();
+- if (facing.getAxis() == Direction.Axis.Y) {
+- d1 -= 0.125;
++ 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);
++ }
++
++ 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.15625;
+ }
+
+- ItemEntity itemEntity = new ItemEntity(level, d, d1, d2, stack);
+- double d3 = level.random.nextDouble() * 0.1 + 0.2;
+- itemEntity.setDeltaMovement(
+- level.random.triangle((double)facing.getStepX() * d3, 0.0172275 * (double)speed),
+- level.random.triangle(0.2, 0.0172275 * (double)speed),
+- level.random.triangle((double)facing.getStepZ() * d3, 0.0172275 * (double)speed)
+- );
+- level.addFreshEntity(itemEntity);
++ ItemEntity entityitem = new ItemEntity(world, d0, d1, d2, itemstack);
++ double d3 = world.random.nextDouble() * 0.1D + 0.2D;
++
++ 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-vineflower-stripped/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch
new file mode 100644
index 0000000000..0b4328d04e
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/core/dispenser/DispenseItemBehavior.java.patch
@@ -0,0 +1,908 @@
+--- a/net/minecraft/core/dispenser/DispenseItemBehavior.java
++++ b/net/minecraft/core/dispenser/DispenseItemBehavior.java
+@@ -72,6 +79,17 @@
+ import net.minecraft.world.phys.AABB;
+ 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 {
+ Logger LOGGER = LogUtils.getLogger();
+@@ -181,6 +215,33 @@
+ Direction direction = blockSource.state().getValue(DispenserBlock.FACING);
+ EntityType<?> type = ((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 {
+ type.spawn(
+ blockSource.level(), itemStack, null, blockSource.pos().relative(direction), MobSpawnType.DISPENSER, direction != Direction.UP, false
+@@ -190,9 +249,10 @@
+ return ItemStack.EMPTY;
+ }
+
+- itemStack.shrink(1);
+- blockSource.level().gameEvent(null, GameEvent.ENTITY_PLACE, blockSource.pos());
+- return itemStack;
++ // itemstack.shrink(1); // Handled during event processing
++ // CraftBukkit end
++ sourceblock.level().gameEvent((Entity) null, GameEvent.ENTITY_PLACE, sourceblock.pos());
++ return itemstack;
+ }
+ };
+
+@@ -200,25 +263,52 @@
+ DispenserBlock.registerBehavior(spawnEggItem, defaultDispenseItemBehavior);
+ }
+
+- DispenserBlock.registerBehavior(
+- Items.ARMOR_STAND,
+- new DefaultDispenseItemBehavior() {
+- @Override
+- public ItemStack execute(BlockSource blockSource, ItemStack itemStack) {
+- Direction direction = blockSource.state().getValue(DispenserBlock.FACING);
+- BlockPos blockPos = blockSource.pos().relative(direction);
+- ServerLevel serverLevel = blockSource.level();
+- Consumer<ArmorStand> consumer = EntityType.appendDefaultStackConfig(
+- armorStand1 -> armorStand1.setYRot(direction.toYRot()), serverLevel, itemStack, null
+- );
+- ArmorStand armorStand = EntityType.ARMOR_STAND
+- .spawn(serverLevel, itemStack.getTag(), consumer, blockPos, MobSpawnType.DISPENSER, false, false);
+- if (armorStand != null) {
+- itemStack.shrink(1);
++ DispenserBlock.registerBehavior(Items.ARMOR_STAND, new DefaultDispenseItemBehavior() {
++ @Override
++ public ItemStack execute(SourceBlock sourceblock, ItemStack itemstack) {
++ Direction enumdirection = (Direction) sourceblock.state().getValue(DispenserBlock.FACING);
++ BlockPos blockposition = sourceblock.pos().relative(enumdirection);
++ ServerLevel worldserver = sourceblock.level();
++
++ // 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;
+ }
+
+ 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;
+ }
+ );
+ DispenserBlock.registerBehavior(
+@@ -241,6 +321,42 @@
+ } else {
+ return super.execute(blockSource, itemStack);
+ }
++ });
++
++ 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); // CraftBukkit - handled above
++ this.setSuccess(true);
++ return itemstack;
++ } else {
++ return super.execute(sourceblock, itemstack);
+ }
+ }
+ );
+@@ -256,9 +375,41 @@
+ this.setSuccess(true);
+ return itemStack;
+ }
++
++ entityhorseabstract = (AbstractHorse) iterator1.next();
++ } while (!entityhorseabstract.isArmor(itemstack) || entityhorseabstract.isWearingArmor() || !entityhorseabstract.isTamed());
++
++ // 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);
+ }
+
+- return super.execute(blockSource, itemStack);
++ 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;
+ }
+ };
+ DispenserBlock.registerBehavior(Items.LEATHER_HORSE_ARMOR, defaultDispenseItemBehavior1);
+@@ -296,50 +448,131 @@
+ return itemStack;
+ }
+ }
+-
+- return super.execute(blockSource, 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);
++
++ 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
+- public ItemStack execute(BlockSource blockSource, ItemStack itemStack) {
+- Direction direction = blockSource.state().getValue(DispenserBlock.FACING);
+- Vec3 entityPokingOutOfBlockPos = DispenseItemBehavior.getEntityPokingOutOfBlockPos(blockSource, EntityType.FIREWORK_ROCKET, direction);
+- FireworkRocketEntity fireworkRocketEntity = new FireworkRocketEntity(
+- blockSource.level(), itemStack, entityPokingOutOfBlockPos.x(), entityPokingOutOfBlockPos.y(), entityPokingOutOfBlockPos.z(), true
+- );
+- fireworkRocketEntity.shoot((double)direction.getStepX(), (double)direction.getStepY(), (double)direction.getStepZ(), 0.5F, 1.0F);
+- blockSource.level().addFreshEntity(fireworkRocketEntity);
+- itemStack.shrink(1);
+- return itemStack;
++ });
++ DispenserBlock.registerBehavior(Items.FIREWORK_ROCKET, new DefaultDispenseItemBehavior() {
++ @Override
++ 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);
++
++ 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);
+ }
+
+ @Override
+ protected void playSound(BlockSource blockSource) {
+ blockSource.level().levelEvent(1004, blockSource.pos(), 0);
+ }
++
++ 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;
+ }
+ );
+ DispenserBlock.registerBehavior(Items.FIRE_CHARGE, new DefaultDispenseItemBehavior() {
+ @Override
+- public ItemStack execute(BlockSource blockSource, ItemStack itemStack) {
+- Direction direction = blockSource.state().getValue(DispenserBlock.FACING);
+- Position dispensePosition = DispenserBlock.getDispensePosition(blockSource);
+- double d = dispensePosition.x() + (double)((float)direction.getStepX() * 0.3F);
+- double d1 = dispensePosition.y() + (double)((float)direction.getStepY() * 0.3F);
+- double d2 = dispensePosition.z() + (double)((float)direction.getStepZ() * 0.3F);
+- Level level = blockSource.level();
+- RandomSource randomSource = level.random;
+- double d3 = randomSource.triangle((double)direction.getStepX(), 0.11485000000000001);
+- double d4 = randomSource.triangle((double)direction.getStepY(), 0.11485000000000001);
+- double d5 = randomSource.triangle((double)direction.getStepZ(), 0.11485000000000001);
+- SmallFireball smallFireball = new SmallFireball(level, d, d1, d2, d3, d4, d5);
+- level.addFreshEntity(Util.make(smallFireball, entity -> entity.setItem(itemStack)));
+- itemStack.shrink(1);
+- return itemStack;
++ public ItemStack execute(SourceBlock sourceblock, ItemStack itemstack) {
++ Direction enumdirection = (Direction) sourceblock.state().getValue(DispenserBlock.FACING);
++ IPosition iposition = DispenserBlock.getDispensePosition(sourceblock);
++ double d0 = iposition.x() + (double) ((float) enumdirection.getStepX() * 0.3F);
++ double d1 = iposition.y() + (double) ((float) enumdirection.getStepY() * 0.3F);
++ double d2 = iposition.z() + (double) ((float) enumdirection.getStepZ() * 0.3F);
++ ServerLevel worldserver = sourceblock.level();
++ RandomSource randomsource = worldserver.random;
++ double d3 = randomsource.triangle((double) enumdirection.getStepX(), 0.11485000000000001D);
++ double d4 = randomsource.triangle((double) enumdirection.getStepY(), 0.11485000000000001D);
++ double d5 = randomsource.triangle((double) enumdirection.getStepZ(), 0.11485000000000001D);
++
++ // 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;
+ }
+
+ @Override
+@@ -369,13 +607,56 @@
+ private final DefaultDispenseItemBehavior defaultDispenseItemBehavior = new DefaultDispenseItemBehavior();
+
+ @Override
+- public ItemStack execute(BlockSource blockSource, ItemStack itemStack) {
+- DispensibleContainerItem dispensibleContainerItem = (DispensibleContainerItem)itemStack.getItem();
+- BlockPos blockPos = blockSource.pos().relative(blockSource.state().getValue(DispenserBlock.FACING));
+- Level level = blockSource.level();
+- if (dispensibleContainerItem.emptyContents(null, level, blockPos, null)) {
+- dispensibleContainerItem.checkExtraContent(null, level, itemStack, blockPos);
+- return new ItemStack(Items.BUCKET);
++ public ItemStack execute(SourceBlock sourceblock, ItemStack itemstack) {
++ DispensibleContainerItem dispensiblecontaineritem = (DispensibleContainerItem) itemstack.getItem();
++ BlockPos blockposition = sourceblock.pos().relative((Direction) sourceblock.state().getValue(DispenserBlock.FACING));
++ ServerLevel worldserver = sourceblock.level();
++
++ // 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);
+ }
+@@ -394,19 +676,50 @@
+ private final DefaultDispenseItemBehavior defaultDispenseItemBehavior = new DefaultDispenseItemBehavior();
+
+ @Override
+- public ItemStack execute(BlockSource blockSource, ItemStack itemStack) {
+- LevelAccessor levelAccessor = blockSource.level();
+- BlockPos blockPos = blockSource.pos().relative(blockSource.state().getValue(DispenserBlock.FACING));
+- BlockState blockState = levelAccessor.getBlockState(blockPos);
+- if (blockState.getBlock() instanceof BucketPickup bucketPickup) {
+- ItemStack itemStack1 = bucketPickup.pickupBlock(null, levelAccessor, blockPos, blockState);
+- if (itemStack1.isEmpty()) {
+- return super.execute(blockSource, itemStack);
++ public ItemStack execute(SourceBlock sourceblock, ItemStack itemstack) {
++ ServerLevel worldserver = sourceblock.level();
++ BlockPos blockposition = sourceblock.pos().relative((Direction) sourceblock.state().getValue(DispenserBlock.FACING));
++ IBlockData iblockdata = worldserver.getBlockState(blockposition);
++ Block block = iblockdata.getBlock();
++
++ if (block instanceof BucketPickup) {
++ BucketPickup ifluidsource = (BucketPickup) block;
++ ItemStack itemstack1 = ifluidsource.pickupBlock((Player) null, DummyGeneratorAccess.INSTANCE, blockposition, iblockdata); // CraftBukkit
++
++ if (itemstack1.isEmpty()) {
++ return super.execute(sourceblock, itemstack);
+ } else {
+- levelAccessor.gameEvent(null, GameEvent.FLUID_PICKUP, blockPos);
+- Item item = itemStack1.getItem();
+- itemStack.shrink(1);
+- if (itemStack.isEmpty()) {
++ worldserver.gameEvent((Entity) null, GameEvent.FLUID_PICKUP, blockposition);
++ 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);
+ } else {
+ if (blockSource.blockEntity().addItem(new ItemStack(item)) < 0) {
+@@ -423,21 +736,52 @@
+ });
+ DispenserBlock.registerBehavior(Items.FLINT_AND_STEEL, new OptionalDispenseItemBehavior() {
+ @Override
+- protected ItemStack execute(BlockSource blockSource, ItemStack itemStack) {
+- Level level = 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);
++
++ 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 = blockSource.state().getValue(DispenserBlock.FACING);
+- BlockPos blockPos = blockSource.pos().relative(direction);
+- BlockState blockState = level.getBlockState(blockPos);
+- if (BaseFireBlock.canBePlacedAt(level, blockPos, direction)) {
+- level.setBlockAndUpdate(blockPos, BaseFireBlock.getState(level, blockPos));
+- level.gameEvent(null, GameEvent.BLOCK_PLACE, blockPos);
+- } else if (CampfireBlock.canLight(blockState) || CandleBlock.canLight(blockState) || CandleCakeBlock.canLight(blockState)) {
+- level.setBlockAndUpdate(blockPos, blockState.setValue(BlockStateProperties.LIT, Boolean.valueOf(true)));
+- level.gameEvent(null, GameEvent.BLOCK_CHANGE, blockPos);
+- } else if (blockState.getBlock() instanceof TntBlock) {
+- TntBlock.explode(level, blockPos);
+- level.removeBlock(blockPos, false);
++ Direction enumdirection = (Direction) sourceblock.state().getValue(DispenserBlock.FACING);
++ BlockPos blockposition = sourceblock.pos().relative(enumdirection);
++ IBlockData iblockdata = worldserver.getBlockState(blockposition);
++
++ 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);
++ }
+ } else {
+ this.setSuccess(false);
+ }
+@@ -453,28 +798,109 @@
+ @Override
+ protected ItemStack execute(BlockSource blockSource, ItemStack itemStack) {
+ this.setSuccess(true);
+- Level level = blockSource.level();
+- BlockPos blockPos = blockSource.pos().relative(blockSource.state().getValue(DispenserBlock.FACING));
+- if (!BoneMealItem.growCrop(itemStack, level, blockPos) && !BoneMealItem.growWaterPlant(itemStack, level, blockPos, null)) {
++ 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);
++
++ 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 (!level.isClientSide) {
+ level.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);
++ }
+
+- return itemStack;
++ 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
+- protected ItemStack execute(BlockSource blockSource, ItemStack itemStack) {
+- Level level = blockSource.level();
+- BlockPos blockPos = blockSource.pos().relative(blockSource.state().getValue(DispenserBlock.FACING));
+- PrimedTnt primedTnt = new PrimedTnt(level, (double)blockPos.getX() + 0.5, (double)blockPos.getY(), (double)blockPos.getZ() + 0.5, null);
+- level.addFreshEntity(primedTnt);
+- level.playSound(null, primedTnt.getX(), primedTnt.getY(), primedTnt.getZ(), SoundEvents.TNT_PRIMED, SoundSource.BLOCKS, 1.0F, 1.0F);
+- level.gameEvent(null, GameEvent.ENTITY_PLACE, blockPos);
+- itemStack.shrink(1);
+- return itemStack;
++ 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);
++
++ 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;
+ }
+ });
+ DispenseItemBehavior dispenseItemBehavior1 = new OptionalDispenseItemBehavior() {
+@@ -484,55 +910,94 @@
+ return itemStack;
+ }
+ };
+- DispenserBlock.registerBehavior(Items.CREEPER_HEAD, dispenseItemBehavior1);
+- DispenserBlock.registerBehavior(Items.ZOMBIE_HEAD, dispenseItemBehavior1);
+- DispenserBlock.registerBehavior(Items.DRAGON_HEAD, dispenseItemBehavior1);
+- DispenserBlock.registerBehavior(Items.SKELETON_SKULL, dispenseItemBehavior1);
+- DispenserBlock.registerBehavior(Items.PIGLIN_HEAD, dispenseItemBehavior1);
+- DispenserBlock.registerBehavior(Items.PLAYER_HEAD, dispenseItemBehavior1);
+- DispenserBlock.registerBehavior(
+- Items.WITHER_SKELETON_SKULL,
+- new OptionalDispenseItemBehavior() {
+- @Override
+- protected ItemStack execute(BlockSource blockSource, ItemStack itemStack) {
+- Level level = blockSource.level();
+- Direction direction = blockSource.state().getValue(DispenserBlock.FACING);
+- BlockPos blockPos = blockSource.pos().relative(direction);
+- if (level.isEmptyBlock(blockPos) && WitherSkullBlock.canSpawnMob(level, blockPos, itemStack)) {
+- level.setBlock(
+- blockPos,
+- Blocks.WITHER_SKELETON_SKULL
+- .defaultBlockState()
+- .setValue(SkullBlock.ROTATION, Integer.valueOf(RotationSegment.convertToSegment(direction))),
+- 3
+- );
+- level.gameEvent(null, GameEvent.BLOCK_PLACE, blockPos);
+- BlockEntity blockEntity = level.getBlockEntity(blockPos);
+- if (blockEntity instanceof SkullBlockEntity) {
+- WitherSkullBlock.checkSpawn(level, blockPos, (SkullBlockEntity)blockEntity);
+- }
+-
+- itemStack.shrink(1);
+- this.setSuccess(true);
+- } else {
+- this.setSuccess(ArmorItem.dispenseArmor(blockSource, itemStack));
++
++ DispenserBlock.registerBehavior(Items.CREEPER_HEAD, dispensebehaviormaybe1);
++ DispenserBlock.registerBehavior(Items.ZOMBIE_HEAD, dispensebehaviormaybe1);
++ DispenserBlock.registerBehavior(Items.DRAGON_HEAD, dispensebehaviormaybe1);
++ DispenserBlock.registerBehavior(Items.SKELETON_SKULL, dispensebehaviormaybe1);
++ DispenserBlock.registerBehavior(Items.PIGLIN_HEAD, dispensebehaviormaybe1);
++ DispenserBlock.registerBehavior(Items.PLAYER_HEAD, dispensebehaviormaybe1);
++ DispenserBlock.registerBehavior(Items.WITHER_SKELETON_SKULL, new OptionalDispenseItemBehavior() {
++ @Override
++ protected ItemStack execute(SourceBlock sourceblock, ItemStack itemstack) {
++ ServerLevel worldserver = sourceblock.level();
++ Direction enumdirection = (Direction) sourceblock.state().getValue(DispenserBlock.FACING);
++ BlockPos blockposition = sourceblock.pos().relative(enumdirection);
++
++ // 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;
+ }
+
+ return itemStack;
+ }
++ // CraftBukkit end
++
++ if (worldserver.isEmptyBlock(blockposition) && WitherSkullBlock.canSpawnMob(worldserver, blockposition, itemstack)) {
++ worldserver.setBlock(blockposition, (IBlockData) Blocks.WITHER_SKELETON_SKULL.defaultBlockState().setValue(SkullBlock.ROTATION, RotationSegment.convertToSegment(enumdirection)), 3);
++ worldserver.gameEvent((Entity) null, GameEvent.BLOCK_PLACE, blockposition);
++ BlockEntity tileentity = worldserver.getBlockEntity(blockposition);
++
++ if (tileentity instanceof SkullBlockEntity) {
++ WitherSkullBlock.checkSpawn(worldserver, blockposition, (SkullBlockEntity) tileentity);
++ }
++
++ itemstack.shrink(1);
++ this.setSuccess(true);
++ } else {
++ this.setSuccess(ArmorItem.dispenseArmor(sourceblock, itemstack));
++ }
++
++ return itemstack;
+ }
+ );
+ DispenserBlock.registerBehavior(Blocks.CARVED_PUMPKIN, new OptionalDispenseItemBehavior() {
+ @Override
+- protected ItemStack execute(BlockSource blockSource, ItemStack itemStack) {
+- Level level = blockSource.level();
+- BlockPos blockPos = blockSource.pos().relative(blockSource.state().getValue(DispenserBlock.FACING));
+- CarvedPumpkinBlock carvedPumpkinBlock = (CarvedPumpkinBlock)Blocks.CARVED_PUMPKIN;
+- if (level.isEmptyBlock(blockPos) && carvedPumpkinBlock.canSpawnGolem(level, blockPos)) {
+- if (!level.isClientSide) {
+- level.setBlock(blockPos, carvedPumpkinBlock.defaultBlockState(), 3);
+- level.gameEvent(null, GameEvent.BLOCK_PLACE, blockPos);
++ protected ItemStack execute(SourceBlock sourceblock, ItemStack itemstack) {
++ ServerLevel worldserver = sourceblock.level();
++ BlockPos blockposition = sourceblock.pos().relative((Direction) sourceblock.state().getValue(DispenserBlock.FACING));
++ CarvedPumpkinBlock blockpumpkincarved = (CarvedPumpkinBlock) Blocks.CARVED_PUMPKIN;
++
++ // 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);
+@@ -567,26 +1037,51 @@
+ return itemStack;
+ }
+ }
+-
+- @Override
+- public ItemStack execute(BlockSource blockSource, ItemStack itemStack) {
+- this.setSuccess(false);
+- ServerLevel serverLevel = blockSource.level();
+- BlockPos blockPos = blockSource.pos().relative(blockSource.state().getValue(DispenserBlock.FACING));
+- BlockState blockState = serverLevel.getBlockState(blockPos);
+- if (blockState.is(BlockTags.BEEHIVES, hive -> hive.hasProperty(BeehiveBlock.HONEY_LEVEL) && hive.getBlock() instanceof BeehiveBlock)
+- && blockState.getValue(BeehiveBlock.HONEY_LEVEL) >= 5) {
+- ((BeehiveBlock)blockState.getBlock())
+- .releaseBeesAndResetHoneyLevel(serverLevel, blockState, blockPos, null, BeehiveBlockEntity.BeeReleaseStatus.BEE_RELEASED);
+- this.setSuccess(true);
+- return this.takeLiquid(blockSource, itemStack, new ItemStack(Items.HONEY_BOTTLE));
+- } else if (serverLevel.getFluidState(blockPos).is(FluidTags.WATER)) {
+- this.setSuccess(true);
+- return this.takeLiquid(blockSource, itemStack, PotionUtils.setPotion(new ItemStack(Items.POTION), Potions.WATER));
+- } else {
+- return super.execute(blockSource, itemStack);
++ }
++
++ @Override
++ public ItemStack execute(SourceBlock sourceblock, ItemStack itemstack) {
++ this.setSuccess(false);
++ ServerLevel worldserver = sourceblock.level();
++ BlockPos blockposition = sourceblock.pos().relative((Direction) sourceblock.state().getValue(DispenserBlock.FACING));
++ IBlockData iblockdata = worldserver.getBlockState(blockposition);
++
++ // 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(sourceblock, itemstack, new ItemStack(Items.HONEY_BOTTLE));
++ } else if (worldserver.getFluidState(blockposition).is(FluidTags.WATER)) {
++ this.setSuccess(true);
++ return this.takeLiquid(sourceblock, itemstack, PotionUtils.setPotion(new ItemStack(Items.POTION), Potions.WATER));
++ } else {
++ return super.execute(sourceblock, itemstack);
++ }
+ }
+ );
+ DispenserBlock.registerBehavior(Items.GLOWSTONE, new OptionalDispenseItemBehavior() {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java.patch
new file mode 100644
index 0000000000..bd4f8f33c6
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java.patch
@@ -0,0 +1,95 @@
+--- a/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java
++++ b/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java
+@@ -16,20 +21,51 @@
+ 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 {
+ @Override
+- protected ItemStack execute(BlockSource blockSource, ItemStack itemStack) {
+- ServerLevel serverLevel = blockSource.level();
+- if (!serverLevel.isClientSide()) {
+- BlockPos blockPos = blockSource.pos().relative(blockSource.state().getValue(DispenserBlock.FACING));
+- this.setSuccess(tryShearBeehive(serverLevel, blockPos) || tryShearLivingEntity(serverLevel, blockPos));
+- if (this.isSuccess() && itemStack.hurt(1, serverLevel.getRandom(), null)) {
+- itemStack.setCount(0);
++ 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);
++
++ 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
+
+- return itemStack;
++ 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);
++ }
++ }
++
++ return itemstack;
+ }
+
+ private static boolean tryShearBeehive(ServerLevel level, BlockPos pos) {
+@@ -51,12 +91,26 @@
+ return false;
+ }
+
+- private static boolean tryShearLivingEntity(ServerLevel level, BlockPos pos) {
+- for (LivingEntity livingEntity : level.getEntitiesOfClass(LivingEntity.class, new AABB(pos), EntitySelector.NO_SPECTATORS)) {
+- if (livingEntity instanceof Shearable shearable && shearable.readyForShearing()) {
+- shearable.shear(SoundSource.BLOCKS);
+- level.gameEvent(null, GameEvent.SHEAR, pos);
+- return true;
++ 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()) {
++ LivingEntity entityliving = (LivingEntity) iterator.next();
++
++ if (entityliving instanceof Shearable) {
++ Shearable ishearable = (Shearable) entityliving;
++
++ 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-vineflower-stripped/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/core/dispenser/ShulkerBoxDispenseBehavior.java.patch
new file mode 100644
index 0000000000..caf361635c
--- /dev/null
+++ b/patch-remap/mache-vineflower-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();
+
+@@ -22,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()
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/nbt/ByteArrayTag.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/nbt/ByteArrayTag.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/nbt/ByteArrayTag.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/nbt/IntArrayTag.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/nbt/IntArrayTag.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/nbt/IntArrayTag.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/nbt/NbtIo.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/nbt/NbtIo.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/nbt/NbtIo.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/network/Connection.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/network/Connection.java.patch
new file mode 100644
index 0000000000..43f5c67ab8
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/network/Connection.java.patch
@@ -0,0 +1,10 @@
+--- a/net/minecraft/network/Connection.java
++++ b/net/minecraft/network/Connection.java
+@@ -97,6 +105,7 @@
+ private volatile Component delayedDisconnect;
+ @Nullable
+ BandwidthDebugMonitor bandwidthDebugMonitor;
++ public String hostname = ""; // CraftBukkit - add field
+
+ public Connection(PacketFlow receiving) {
+ this.receiving = receiving;
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/network/FriendlyByteBuf.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/network/FriendlyByteBuf.java.patch
new file mode 100644
index 0000000000..471c61ca94
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/network/FriendlyByteBuf.java.patch
@@ -0,0 +1,54 @@
+--- a/net/minecraft/network/FriendlyByteBuf.java
++++ b/net/minecraft/network/FriendlyByteBuf.java
+@@ -80,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;
+ private final ByteBuf source;
+@@ -536,8 +586,8 @@
+ try {
+ NbtIo.writeAnyTag(tag, new ByteBufOutputStream(this));
+ return this;
+- } catch (IOException var3) {
+- throw new EncoderException(var3);
++ } catch (Exception ioexception) { // CraftBukkit - IOException -> Exception
++ throw new EncoderException(ioexception);
+ }
+ }
+
+@@ -562,7 +614,7 @@
+ }
+
+ public FriendlyByteBuf writeItem(ItemStack stack) {
+- if (stack.isEmpty()) {
++ if (stack.isEmpty() || stack.getItem() == null) { // CraftBukkit - NPE fix itemstack.getItem()
+ this.writeBoolean(false);
+ } else {
+ this.writeBoolean(true);
+@@ -584,11 +638,17 @@
+ if (!this.readBoolean()) {
+ return ItemStack.EMPTY;
+ } else {
+- Item item = this.readById(BuiltInRegistries.ITEM);
+- int _byte = this.readByte();
+- ItemStack itemStack = new ItemStack(item, _byte);
+- itemStack.setTag(this.readNbt());
+- return itemStack;
++ Item item = (Item) this.readById((IdMap) BuiltInRegistries.ITEM);
++ byte b0 = this.readByte();
++ 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-vineflower-stripped/net/minecraft/network/chat/Component.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/network/chat/Component.java.patch
new file mode 100644
index 0000000000..81502c0e4c
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -33,8 +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-vineflower-stripped/net/minecraft/network/chat/TextColor.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/network/chat/TextColor.java.patch
new file mode 100644
index 0000000000..cb8af9e589
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/network/chat/TextColor.java.patch
@@ -0,0 +1,36 @@
+--- a/net/minecraft/network/chat/TextColor.java
++++ b/net/minecraft/network/chat/TextColor.java
+@@ -15,15 +16,18 @@
+ public final class TextColor {
+ 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 = Stream.of(ChatFormatting.values())
+- .filter(ChatFormatting::isColor)
+- .collect(ImmutableMap.toImmutableMap(Function.identity(), chatFormatting -> new TextColor(chatFormatting.getColor(), chatFormatting.getName())));
+- private static final Map<String, TextColor> NAMED_COLORS = LEGACY_FORMAT_TO_COLOR.values()
+- .stream()
+- .collect(ImmutableMap.toImmutableMap(textColor -> textColor.name, Function.identity()));
++ 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((chathexcolor) -> {
++ return chathexcolor.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 value, String name) {
+ this.value = value & 16777215;
+@@ -34,6 +40,7 @@
+ this.value = value & 16777215;
+ this.name = null;
+ }
++ // CraftBukkit end
+
+ public int getValue() {
+ return this.value;
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/PacketUtils.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/PacketUtils.java.patch
new file mode 100644
index 0000000000..9c3a79df96
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/PacketUtils.java.patch
@@ -0,0 +1,65 @@
+--- 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;
+
+@@ -18,17 +26,19 @@
+
+ public static <T extends PacketListener> void ensureRunningOnSameThread(Packet<T> packet, T processor, BlockableEventLoop<?> executor) throws RunningOnDifferentThreadException {
+ if (!executor.isSameThread()) {
+- executor.executeIfPossible(
+- () -> {
+- if (processor.shouldHandleMessage(packet)) {
+- try {
+- packet.handle(processor);
+- } catch (Exception var6) {
+- if (var6 instanceof ReportedException reportedException && reportedException.getCause() instanceof OutOfMemoryError
+- || processor.shouldPropagateHandlingExceptions()) {
+- if (var6 instanceof ReportedException reportedException1) {
+- processor.fillCrashReport(reportedException1.getReport());
+- throw var6;
++ executor.executeIfPossible(() -> {
++ if (MinecraftServer.getServer().hasStopped() || (processor instanceof ServerCommonPacketListenerImpl && ((ServerCommonPacketListenerImpl) processor).processedDisconnect)) return; // CraftBukkit, MC-142590
++ if (processor.shouldHandleMessage(packet)) {
++ try {
++ packet.handle(processor);
++ } catch (Exception exception) {
++ label25:
++ {
++ if (exception instanceof ReportedException) {
++ ReportedException reportedexception = (ReportedException) exception;
++
++ if (reportedexception.getCause() instanceof OutOfMemoryError) {
++ break label25;
+ }
+
+ CrashReport crashReport = CrashReport.forThrowable(var6, "Main thread packet handler");
+@@ -42,8 +63,13 @@
+ LOGGER.debug("Ignoring packet due to disconnection: {}", packet);
+ }
+ }
+- );
+- 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-vineflower-stripped/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch
new file mode 100644
index 0000000000..ecaab61eb6
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch
@@ -0,0 +1,48 @@
+--- a/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java
++++ b/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java
+@@ -11,11 +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.<ResourceLocation, FriendlyByteBuf.Reader<? extends CustomPacketPayload>>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));
+@@ -26,11 +24,13 @@
+ return (CustomPacketPayload)(reader != null ? 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 handler) {
+ handler.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-vineflower-stripped/net/minecraft/network/protocol/game/ClientboundInitializeBorderPacket.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/game/ClientboundInitializeBorderPacket.java.patch
new file mode 100644
index 0000000000..11f0359b0a
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/game/ClientboundInitializeBorderPacket.java.patch
@@ -0,0 +1,15 @@
+--- a/net/minecraft/network/protocol/game/ClientboundInitializeBorderPacket.java
++++ b/net/minecraft/network/protocol/game/ClientboundInitializeBorderPacket.java
+@@ -26,8 +27,10 @@
+ }
+
+ public ClientboundInitializeBorderPacket(WorldBorder worldBorder) {
+- this.newCenterX = worldBorder.getCenterX();
+- this.newCenterZ = worldBorder.getCenterZ();
++ // 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();
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java.patch
new file mode 100644
index 0000000000..01382a5fae
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java.patch
@@ -0,0 +1,33 @@
+--- a/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java
++++ b/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java
+@@ -23,13 +22,26 @@
+ this.states = new BlockState[size];
+ int i = 0;
+
+- for (short s : positions) {
+- this.positions[i] = s;
+- this.states[i] = section.getBlockState(SectionPos.sectionRelativeX(s), SectionPos.sectionRelativeY(s), SectionPos.sectionRelativeZ(s));
+- i++;
++ this.positions = new short[i];
++ this.states = new IBlockData[i];
++ int j = 0;
++
++ for (ShortIterator shortiterator = positions.iterator(); shortiterator.hasNext(); ++j) {
++ short short0 = (Short) shortiterator.next();
++
++ this.positions[j] = 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
+ }
+ }
+
++ // CraftBukkit start - Add constructor
++ public ClientboundSectionBlocksUpdatePacket(SectionPos sectionposition, ShortSet shortset, IBlockData[] states) {
++ this.sectionPos = sectionposition;
++ this.positions = shortset.toShortArray();
++ this.states = states;
++ }
++ // CraftBukkit end
++
+ public ClientboundSectionBlocksUpdatePacket(FriendlyByteBuf buffer) {
+ this.sectionPos = SectionPos.of(buffer.readLong());
+ int varInt = buffer.readVarInt();
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/game/ClientboundSetBorderCenterPacket.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/game/ClientboundSetBorderCenterPacket.java.patch
new file mode 100644
index 0000000000..5f90f331a9
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/game/ClientboundSetBorderCenterPacket.java.patch
@@ -0,0 +1,15 @@
+--- a/net/minecraft/network/protocol/game/ClientboundSetBorderCenterPacket.java
++++ b/net/minecraft/network/protocol/game/ClientboundSetBorderCenterPacket.java
+@@ -9,8 +10,10 @@
+ private final double newCenterZ;
+
+ public ClientboundSetBorderCenterPacket(WorldBorder worldBorder) {
+- this.newCenterX = worldBorder.getCenterX();
+- this.newCenterZ = worldBorder.getCenterZ();
++ // 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 buffer) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/game/ServerboundUseItemOnPacket.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/game/ServerboundUseItemOnPacket.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/game/ServerboundUseItemOnPacket.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/game/ServerboundUseItemPacket.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/game/ServerboundUseItemPacket.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/game/ServerboundUseItemPacket.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/network/syncher/SynchedEntityData.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/network/syncher/SynchedEntityData.java.patch
new file mode 100644
index 0000000000..a1f82288d1
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -132,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;
+ }
+@@ -220,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;
+ T value;
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/Bootstrap.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/Bootstrap.java.patch
new file mode 100644
index 0000000000..d2517a5bd1
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/Bootstrap.java.patch
@@ -0,0 +1,100 @@
+--- a/net/minecraft/server/Bootstrap.java
++++ b/net/minecraft/server/Bootstrap.java
+@@ -38,8 +43,25 @@
+ public static final AtomicLong bootstrapDuration = new AtomicLong(-1L);
+
+ public static void bootStrap() {
+- if (!isBootstrapped) {
+- isBootstrapped = true;
++ 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();
+ if (BuiltInRegistries.REGISTRY.keySet().isEmpty()) {
+ throw new IllegalStateException("Unable to load registries");
+@@ -58,6 +81,69 @@
+ wrapStreams();
+ 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-vineflower-stripped/net/minecraft/server/Main.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/Main.java.patch
new file mode 100644
index 0000000000..9a3e9694d6
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/Main.java.patch
@@ -0,0 +1,316 @@
+--- a/net/minecraft/server/Main.java
++++ b/net/minecraft/server/Main.java
+@@ -59,28 +60,37 @@
+ 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();
+
+ @DontObfuscate
+- public static void main(String[] args) {
++ public static void main(final OptionSet optionset) { // CraftBukkit - replaces main(String[] astring)
+ SharedConstants.tryDetectVersion();
+- OptionParser optionParser = new OptionParser();
+- OptionSpec<Void> optionSpec = optionParser.accepts("nogui");
+- OptionSpec<Void> optionSpec1 = optionParser.accepts("initSettings", "Initializes 'server.properties' and 'eula.txt', then quits");
+- OptionSpec<Void> optionSpec2 = optionParser.accepts("demo");
+- OptionSpec<Void> optionSpec3 = optionParser.accepts("bonusChest");
+- OptionSpec<Void> optionSpec4 = optionParser.accepts("forceUpgrade");
+- OptionSpec<Void> optionSpec5 = optionParser.accepts("eraseCache");
+- OptionSpec<Void> optionSpec6 = optionParser.accepts("safeMode", "Loads level with vanilla datapack only");
+- OptionSpec<Void> optionSpec7 = optionParser.accepts("help").forHelp();
+- OptionSpec<String> optionSpec8 = optionParser.accepts("universe").withRequiredArg().defaultsTo(".");
+- OptionSpec<String> optionSpec9 = optionParser.accepts("world").withRequiredArg();
+- OptionSpec<Integer> optionSpec10 = optionParser.accepts("port").withRequiredArg().ofType(Integer.class).defaultsTo(-1);
+- OptionSpec<String> optionSpec11 = optionParser.accepts("serverId").withRequiredArg();
+- OptionSpec<Void> optionSpec12 = optionParser.accepts("jfrProfile");
+- OptionSpec<Path> optionSpec13 = optionParser.accepts("pidFile").withRequiredArg().withValuesConvertedBy(new PathConverter());
+- OptionSpec<String> optionSpec14 = optionParser.nonOptions();
++ /* 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");
++ OptionSpec<Void> optionspec2 = optionparser.accepts("demo");
++ OptionSpec<Void> optionspec3 = optionparser.accepts("bonusChest");
++ OptionSpec<Void> optionspec4 = optionparser.accepts("forceUpgrade");
++ OptionSpec<Void> optionspec5 = optionparser.accepts("eraseCache");
++ OptionSpec<Void> optionspec6 = optionparser.accepts("safeMode", "Loads level with vanilla datapack only");
++ OptionSpec<Void> optionspec7 = optionparser.accepts("help").forHelp();
++ OptionSpec<String> optionspec8 = optionparser.accepts("universe").withRequiredArg().defaultsTo(".", new String[0]);
++ OptionSpec<String> optionspec9 = optionparser.accepts("world").withRequiredArg();
++ OptionSpec<Integer> optionspec10 = optionparser.accepts("port").withRequiredArg().ofType(Integer.class).defaultsTo(-1, new Integer[0]);
++ OptionSpec<String> optionspec11 = optionparser.accepts("serverId").withRequiredArg();
++ OptionSpec<Void> optionspec12 = optionparser.accepts("jfrProfile");
++ OptionSpec<Path> optionspec13 = optionparser.accepts("pidFile").withRequiredArg().withValuesConvertedBy(new PathConverter(new PathProperties[0]));
++ NonOptionArgumentSpec nonoptionargumentspec = optionparser.nonOptions();
+
+ try {
+ OptionSet optionSet = optionParser.parse(args);
+@@ -88,14 +102,18 @@
+ optionParser.printHelpOn(System.err);
+ return;
+ }
++ */ // CraftBukkit end
+
+- Path path = optionSet.valueOf(optionSpec13);
++ try {
++
++ 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);
+ }
+
+@@ -103,12 +121,27 @@
+ Bootstrap.validate();
+ Util.startTimerHackThread();
+ Path path1 = Paths.get("server.properties");
+- DedicatedServerSettings dedicatedServerSettings = new DedicatedServerSettings(path1);
+- dedicatedServerSettings.forceSave();
++ 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)) {
+- LOGGER.info("Initialized '{}' and '{}'", path1.toAbsolutePath(), path2.toAbsolutePath());
++
++ 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;
+ }
+
+@@ -117,14 +150,18 @@
+ return;
+ }
+
+- File file = new File(optionSet.valueOf(optionSpec8));
++ File file = (File) optionset.valueOf("universe"); // CraftBukkit
+ Services services = Services.create(new YggdrasilAuthenticationService(Proxy.NO_PROXY), file);
+- String string = Optional.ofNullable(optionSet.valueOf(optionSpec9)).orElse(dedicatedServerSettings.getProperties().levelName);
+- LevelStorageSource levelStorageSource = LevelStorageSource.createDefault(file.toPath());
+- LevelStorageSource.LevelStorageAccess levelStorageAccess = levelStorageSource.validateAndCreateAccess(string);
+- Dynamic<?> dataTag;
+- if (levelStorageAccess.hasWorldData()) {
+- LevelSummary summary;
++ // 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 (convertable_conversionsession.hasWorldData()) {
++ LevelSummary worldinfo;
++
+ try {
+ dataTag = levelStorageAccess.getDataTag();
+ summary = levelStorageAccess.getSummary(dataTag);
+@@ -162,72 +196,73 @@
+ dataTag = null;
+ }
+
+- Dynamic<?> dynamic = dataTag;
+- boolean hasOptionSpec = optionSet.has(optionSpec6);
+- if (hasOptionSpec) {
+- LOGGER.warn("Safe mode active, only vanilla datapack will be loaded");
++ Dynamic<?> dynamic1 = dynamic;
++ boolean flag = optionset.has("safeMode"); // CraftBukkit
++
++ if (flag) {
++ Main.LOGGER.warn("Safe mode active, only vanilla datapack will be loaded");
+ }
+
+- PackRepository packRepository = ServerPacksSource.createPackRepository(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;
+ try {
+- WorldLoader.InitConfig initConfig = loadOrCreateConfig(dedicatedServerSettings.getProperties(), dynamic, hasOptionSpec, packRepository);
+- worldStem = Util.<WorldStem>blockUntilDone(
+- executor -> WorldLoader.load(
+- initConfig,
+- context -> {
+- Registry<LevelStem> registry = context.datapackDimensions().registryOrThrow(Registries.LEVEL_STEM);
+- if (dynamic != null) {
+- LevelDataAndDimensions levelDataAndDimensions = LevelStorageSource.getLevelDataAndDimensions(
+- dynamic, context.dataConfiguration(), registry, context.datapackWorldgen()
+- );
+- return new WorldLoader.DataLoadOutput<>(
+- levelDataAndDimensions.worldData(), levelDataAndDimensions.dimensions().dimensionsRegistryAccess()
+- );
+- } else {
+- LOGGER.info("No existing world data, creating new world");
+- LevelSettings levelSettings;
+- WorldOptions worldOptions;
+- WorldDimensions worldDimensions;
+- if (optionSet.has(optionSpec2)) {
+- levelSettings = MinecraftServer.DEMO_SETTINGS;
+- worldOptions = WorldOptions.DEMO_OPTIONS;
+- worldDimensions = WorldPresets.createNormalWorldDimensions(context.datapackWorldgen());
+- } else {
+- DedicatedServerProperties properties = dedicatedServerSettings.getProperties();
+- levelSettings = new LevelSettings(
+- properties.levelName,
+- properties.gamemode,
+- properties.hardcore,
+- properties.difficulty,
+- false,
+- new GameRules(),
+- context.dataConfiguration()
+- );
+- worldOptions = optionSet.has(optionSpec3) ? properties.worldOptions.withBonusChest(true) : properties.worldOptions;
+- worldDimensions = properties.createDimensions(context.datapackWorldgen());
+- }
+-
+- WorldDimensions.Complete complete = worldDimensions.bake(registry);
+- Lifecycle lifecycle = complete.lifecycle().add(context.datapackWorldgen().allRegistriesLifecycle());
+- return new WorldLoader.DataLoadOutput<>(
+- new PrimaryLevelData(levelSettings, worldOptions, complete.specialWorldProperty(), lifecycle),
+- complete.dimensionsRegistryAccess()
+- );
+- }
+- },
+- WorldStem::new,
+- Util.backgroundExecutor(),
+- executor
+- )
+- )
+- .get();
+- } catch (Exception var37) {
+- LOGGER.warn(
+- "Failed to load datapacks, can't proceed with server load. You can either fix your datapacks or reset to vanilla with --safeMode",
+- (Throwable)var37
+- );
++ WorldLoader.c worldloader_c = loadOrCreateConfig(dedicatedserversettings.getProperties(), dynamic1, flag, resourcepackrepository);
++
++ worldstem = (WorldStem) Util.blockUntilDone((executor) -> {
++ 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_a.dataConfiguration(), iregistry, worldloader_a.datapackWorldgen());
++
++ return new WorldLoader.b<>(leveldataanddimensions.worldData(), leveldataanddimensions.dimensions().dimensionsRegistryAccess());
++ } else {
++ Main.LOGGER.info("No existing world data, creating new world");
++ LevelSettings worldsettings;
++ WorldOptions worldoptions;
++ WorldDimensions worlddimensions;
++
++ if (optionset.has("demo")) { // CraftBukkit
++ worldsettings = MinecraftServer.DEMO_SETTINGS;
++ worldoptions = WorldOptions.DEMO_OPTIONS;
++ worlddimensions = WorldPresets.createNormalWorldDimensions(worldloader_a.datapackWorldgen());
++ } else {
++ DedicatedServerProperties dedicatedserverproperties = dedicatedserversettings.getProperties();
++
++ 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.b worlddimensions_b = worlddimensions.bake(iregistry);
++ Lifecycle lifecycle = worlddimensions_b.lifecycle().add(worldloader_a.datapackWorldgen().allRegistriesLifecycle());
++
++ return new WorldLoader.b<>(new PrimaryLevelData(worldsettings, worldoptions, worlddimensions_b.specialWorldProperty(), lifecycle), worlddimensions_b.dimensionsRegistryAccess());
++ }
++ }, WorldStem::new, Util.backgroundExecutor(), executor);
++ }).get();
++ } catch (Exception exception) {
++ Main.LOGGER.warn("Failed to load datapacks, can't proceed with server load. You can either fix your datapacks or reset to vanilla with --safeMode", exception);
+ return;
+ }
+
+@@ -262,7 +300,10 @@
+
+ return dedicatedServer1;
+ }
+- );
++
++ return dedicatedserver1;
++ });
++ /* CraftBukkit start
+ Thread thread = new Thread("Server Shutdown Thread") {
+ @Override
+ public void run() {
+@@ -271,8 +312,9 @@
+ };
+ thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER));
+ Runtime.getRuntime().addShutdownHook(thread);
+- } catch (Exception var40) {
+- LOGGER.error(LogUtils.FATAL_MARKER, "Failed to start the minecraft server", (Throwable)var40);
++ */ // CraftBukkit end
++ } catch (Exception exception1) {
++ Main.LOGGER.error(LogUtils.FATAL_MARKER, "Failed to start the minecraft server", exception1);
+ }
+ }
+
+@@ -303,16 +348,10 @@
+ return new WorldLoader.InitConfig(packConfig, Commands.CommandSelection.DEDICATED, dedicatedServerProperties.functionPermissionLevel);
+ }
+
+- private static void forceUpgrade(
+- LevelStorageSource.LevelStorageAccess levelStorage,
+- DataFixer dataFixer,
+- boolean eraseCache,
+- BooleanSupplier upgradeWorld,
+- Registry<LevelStem> dimesions
+- ) {
+- LOGGER.info("Forcing world upgrade!");
+- WorldUpgrader worldUpgrader = new WorldUpgrader(levelStorage, dataFixer, dimesions, eraseCache);
+- 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 status = worldUpgrader.getStatus();
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/MinecraftServer.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/MinecraftServer.java.patch
new file mode 100644
index 0000000000..6f529f3152
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/MinecraftServer.java.patch
@@ -0,0 +1,673 @@
+--- a/net/minecraft/server/MinecraftServer.java
++++ b/net/minecraft/server/MinecraftServer.java
+@@ -145,6 +150,23 @@
+ import net.minecraft.world.level.levelgen.PhantomSpawner;
+ import net.minecraft.world.level.levelgen.WorldOptions;
+ 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;
+@@ -157,14 +180,18 @@
+ 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 {
+ private static final Logger LOGGER = LogUtils.getLogger();
+ 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;
+@@ -250,6 +275,19 @@
+ protected final WorldData worldData;
+ private volatile boolean isSaving;
+
++ // 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(() -> atomicReference.get().runServer(), "Server thread");
+@@ -275,9 +310,26 @@
+ ChunkProgressListenerFactory progressListenerFactory
+ ) {
+ super("Server");
+- this.registries = worldStem.registries();
+- this.worldData = worldStem.worldData();
+- if (!this.registries.compositeAccess().registryOrThrow(Registries.LEVEL_STEM).containsKey(LevelStem.OVERWORLD)) {
++ this.metricsRecorder = InactiveMetricsRecorder.INSTANCE;
++ this.profiler = this.metricsRecorder.getProfiler();
++ this.onMetricsRecordingStopped = (methodprofilerresults) -> {
++ this.stopRecordingMetrics();
++ };
++ this.onMetricsRecordingFinished = (path) -> {
++ };
++ this.random = RandomSource.create();
++ this.port = -1;
++ this.levels = Maps.newLinkedHashMap();
++ this.running = true;
++ this.ticksUntilAutosave = 6000;
++ this.tickTimesNanos = new long[100];
++ this.aggregatedTickTimesNanos = 0L;
++ this.nextTickTimeNanos = Util.getNanos();
++ this.scoreboard = new ServerScoreboard(this);
++ this.customBossEvents = new CustomBossEvents();
++ this.registries = worldstem.registries();
++ this.worldData = worldstem.worldData();
++ 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;
+@@ -304,6 +353,33 @@
+ this.serverThread = serverThread;
+ 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 dataStorage) {
+@@ -312,19 +388,17 @@
+
+ protected abstract boolean initServer() throws IOException;
+
+- protected void loadLevel() {
++ protected void loadLevel(String s) { // CraftBukkit
+ if (!JvmProfiler.INSTANCE.isRunning()) {
+ }
+
+ boolean flag = false;
+- ProfiledDuration profiledDuration = JvmProfiler.INSTANCE.onWorldLoadedStarted();
+- this.worldData.setModdedInfo(this.getServerModName(), this.getModdedStatus().shouldReportAsModified());
+- ChunkProgressListener chunkProgressListener = this.progressListenerFactory.create(11);
+- this.createLevels(chunkProgressListener);
+- this.forceDifficulty();
+- this.prepareLevels(chunkProgressListener);
+- if (profiledDuration != null) {
+- profiledDuration.finish();
++ ProfiledDuration profiledduration = JvmProfiler.INSTANCE.onWorldLoadedStarted();
++
++ loadWorld0(s); // CraftBukkit
++
++ if (profiledduration != null) {
++ profiledduration.finish();
+ }
+
+ if (flag) {
+@@ -339,31 +412,24 @@
+ protected void forceDifficulty() {
+ }
+
+- protected void createLevels(ChunkProgressListener listener) {
+- ServerLevelData serverLevelData = this.worldData.overworldData();
+- boolean isDebugWorld = this.worldData.isDebugWorld();
+- Registry<LevelStem> registry = this.registries.compositeAccess().registryOrThrow(Registries.LEVEL_STEM);
+- WorldOptions worldOptions = this.worldData.worldGenOptions();
+- long l = worldOptions.seed();
+- long l1 = BiomeManager.obfuscateSeed(l);
+- List<CustomSpawner> list = ImmutableList.of(
+- new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(serverLevelData)
+- );
+- LevelStem levelStem = registry.get(LevelStem.OVERWORLD);
+- ServerLevel serverLevel = new ServerLevel(
+- this, this.executor, this.storageSource, serverLevelData, Level.OVERWORLD, levelStem, listener, isDebugWorld, l1, list, true, null
+- );
+- this.levels.put(Level.OVERWORLD, serverLevel);
+- DimensionDataStorage dataStorage = serverLevel.getDataStorage();
+- this.readScoreboard(dataStorage);
+- this.commandStorage = new CommandStorage(dataStorage);
+- WorldBorder worldBorder = serverLevel.getWorldBorder();
+- if (!serverLevelData.isInitialized()) {
+- try {
+- setInitialSpawn(serverLevel, serverLevelData, worldOptions.generateBonusChest(), isDebugWorld);
+- serverLevelData.setInitialized(true);
+- if (isDebugWorld) {
+- this.setupDebugLevel(this.worldData);
++ protected void forceDifficulty() {}
++
++ // CraftBukkit start
++ private void loadWorld0(String s) {
++ LevelStorageSource.LevelStorageAccess worldSession = this.storageSource;
++
++ Registry<LevelStem> dimensions = this.registries.compositeAccess().registryOrThrow(Registries.LEVEL_STEM);
++ for (LevelStem worldDimension : dimensions) {
++ ResourceKey<LevelStem> dimensionKey = dimensions.getResourceKey(worldDimension).get();
++
++ ServerLevel world;
++ int dimension = 0;
++
++ if (dimensionKey == LevelStem.NETHER) {
++ if (isNetherEnabled()) {
++ dimension = -1;
++ } else {
++ continue;
+ }
+ } catch (Throwable var23) {
+ CrashReport crashReport = CrashReport.forThrowable(var23, "Exception initializing level");
+@@ -376,13 +523,101 @@
+ throw new ReportedException(crashReport);
+ }
+
+- serverLevelData.setInitialized(true);
++ 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.getPlayerList().addWorldborderListener(serverLevel);
+- if (this.worldData.getCustomBossEvents() != null) {
+- this.getCustomBossEvents().load(this.worldData.getCustomBossEvents());
++ 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
+
+ RandomSequences randomSequences = serverLevel.getRandomSequences();
+
+@@ -412,18 +648,30 @@
+
+ worldBorder.applySettings(serverLevelData.getWorldBorder());
+ }
++ // CraftBukkit end
+
+ private static void setInitialSpawn(ServerLevel level, ServerLevelData levelData, boolean generateBonusChest, boolean debug) {
+ if (debug) {
+ levelData.setSpawn(BlockPos.ZERO.above(80), 0.0F);
+ } else {
+- ServerChunkCache chunkSource = level.getChunkSource();
+- ChunkPos chunkPos = new ChunkPos(chunkSource.randomState().sampler().findSpawnPosition());
+- int spawnHeight = chunkSource.getGenerator().getSpawnHeight(level);
+- if (spawnHeight < level.getMinBuildHeight()) {
+- BlockPos worldPosition = chunkPos.getWorldPosition();
+- spawnHeight = level.getHeight(Heightmap.Types.WORLD_SURFACE, worldPosition.getX() + 8, worldPosition.getZ() + 8);
++ 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 (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);
+
+ levelData.setSpawn(chunkPos.getWorldPosition().offset(8, spawnHeight, 8), 0.0F);
+ int i = 0;
+@@ -479,14 +730,22 @@
+ serverLevelData.setGameType(GameType.SPECTATOR);
+ }
+
+- private void prepareLevels(ChunkProgressListener listener) {
+- ServerLevel serverLevel = this.overworld();
+- LOGGER.info("Preparing start region for dimension {}", serverLevel.dimension().location());
+- BlockPos sharedSpawnPos = serverLevel.getSharedSpawnPos();
+- listener.updateSpawnPos(new ChunkPos(sharedSpawnPos));
+- ServerChunkCache chunkSource = serverLevel.getChunkSource();
++ // 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 {}", worldserver.dimension().location());
++ BlockPos blockposition = worldserver.getSharedSpawnPos();
++
++ worldloadlistener.updateSpawnPos(new ChunkPos(blockposition));
++ ServerChunkCache chunkproviderserver = worldserver.getChunkSource();
++
+ this.nextTickTimeNanos = Util.getNanos();
+- chunkSource.addRegionTicket(TicketType.START, new ChunkPos(sharedSpawnPos), 11, Unit.INSTANCE);
++ // CraftBukkit start
++ if (worldserver.getWorld().getKeepSpawnInMemory()) {
++ chunkproviderserver.addRegionTicket(TicketType.START, new ChunkPos(blockposition), 11, Unit.INSTANCE);
+
+ while (chunkSource.getTickingGenerated() != 441) {
+ this.nextTickTimeNanos = Util.getNanos() + PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
+@@ -496,10 +757,10 @@
+ this.nextTickTimeNanos = Util.getNanos() + PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
+ this.waitUntilNextTick();
+
+- for (ServerLevel serverLevel1 : this.levels.values()) {
+- ForcedChunksSavedData forcedChunksSavedData = serverLevel1.getDataStorage().get(ForcedChunksSavedData.factory(), "chunks");
+- if (forcedChunksSavedData != null) {
+- LongIterator longIterator = forcedChunksSavedData.getChunks().iterator();
++ if (true) {
++ ServerLevel worldserver1 = worldserver;
++ // CraftBukkit end
++ ForcedChunksSavedData forcedchunk = (ForcedChunksSavedData) worldserver1.getDataStorage().get(ForcedChunksSavedData.factory(), "chunks");
+
+ while (longIterator.hasNext()) {
+ long l = longIterator.nextLong();
+@@ -509,10 +774,17 @@
+ }
+ }
+
+- this.nextTickTimeNanos = Util.getNanos() + PREPARE_LEVELS_DEFAULT_DELAY_NANOS;
+- this.waitUntilNextTick();
+- listener.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() {
+@@ -541,11 +814,16 @@
+ flag = true;
+ }
+
+- ServerLevel serverLevel1 = this.overworld();
+- ServerLevelData serverLevelData = this.worldData.overworldData();
+- serverLevelData.setWorldBorder(serverLevel1.getWorldBorder().createSettings());
++ // CraftBukkit start - moved to WorldServer.save
++ /*
++ WorldServer worldserver1 = this.overworld();
++ IWorldDataServer iworlddataserver = this.worldData.overworldData();
++
++ iworlddataserver.setWorldBorder(worldserver1.getWorldBorder().createSettings());
+ this.worldData.setCustomBossEvents(this.getCustomBossEvents().save());
+ this.storageSource.saveDataTag(this.registryAccess(), this.worldData, this.getPlayerList().getSingleplayerData());
++ */
++ // CraftBukkit end
+ if (flush) {
+ for (ServerLevel serverLevel2 : this.getAllLevels()) {
+ LOGGER.info("ThreadedAnvilChunkStorage ({}): All chunks are saved", serverLevel2.getChunkSource().chunkMap.getStorageName());
+@@ -575,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();
+ }
+
+- LOGGER.info("Stopping server");
++ 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) {
+ 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
+ }
+
+ LOGGER.info("Saving worlds");
+@@ -670,13 +990,15 @@
+ this.nextTickTimeNanos = Util.getNanos();
+ this.lastOverloadWarningNanos = this.nextTickTimeNanos;
+ } else {
+- l = this.tickRateManager.nanosecondsPerTick();
+- long l1 = Util.getNanos() - this.nextTickTimeNanos;
+- if (l1 > OVERLOADED_THRESHOLD_NANOS + 20L * l
+- && this.nextTickTimeNanos - this.lastOverloadWarningNanos >= OVERLOADED_WARNING_INTERVAL_NANOS + 100L * l) {
+- long l2 = l1 / l;
+- LOGGER.warn("Can't keep up! Is the server overloaded? Running {}ms or {} ticks behind", l1 / TimeUtil.NANOSECONDS_PER_MILLISECOND, l2);
+- this.nextTickTimeNanos += l2 * l;
++ i = this.tickRateManager.nanosecondsPerTick();
++ long j = Util.getNanos() - this.nextTickTimeNanos;
++
++ 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;
+ }
+ }
+@@ -687,7 +1010,8 @@
+ this.debugCommandProfiler = new MinecraftServer.TimeProfiler(Util.getNanos(), this.tickCount);
+ }
+
+- this.nextTickTimeNanos += l;
++ MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit
++ this.nextTickTimeNanos += i;
+ this.startMetricsRecordingTick();
+ this.profiler.push("tick");
+ this.tickServer(flag ? () -> false : this::haveTime);
+@@ -727,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();
+ }
+ }
+@@ -755,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(() -> !this.haveTime());
+@@ -849,10 +1206,12 @@
+ this.status = this.buildServerStatus();
+ }
+
+- this.ticksUntilAutosave--;
+- if (this.ticksUntilAutosave <= 0) {
+- this.ticksUntilAutosave = this.computeNextAutosaveInterval();
+- LOGGER.debug("Autosave started");
++ --this.ticksUntilAutosave;
++ // 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);
+ this.profiler.pop();
+@@ -926,18 +1288,41 @@
+ }
+
+ public void tickChildren(BooleanSupplier hasTimeLeft) {
+- this.getPlayerList().getPlayers().forEach(serverPlayer1 -> serverPlayer1.connection.suspendFlushing());
++ this.getPlayerList().getPlayers().forEach((entityplayer) -> {
++ entityplayer.connection.suspendFlushing();
++ });
++ this.server.getScheduler().mainThreadHeartbeat(this.tickCount); // CraftBukkit
+ this.profiler.push("commandFunctions");
+ this.getFunctions().tick();
+ this.profiler.popPush("levels");
+
+- for (ServerLevel serverLevel : this.getAllLevels()) {
+- this.profiler.push(() -> serverLevel + " " + serverLevel.dimension().location());
++ // 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 worldserver = (ServerLevel) iterator.next();
++
++ this.profiler.push(() -> {
++ return worldserver + " " + worldserver.dimension().location();
++ });
++ /* Drop global time updates
+ if (this.tickCount % 20 == 0) {
+ this.profiler.push("timeSync");
+ this.synchronizeTime(serverLevel);
+ this.profiler.pop();
+ }
++ // CraftBukkit end */
+
+ this.profiler.push("tick");
+
+@@ -1024,6 +1413,22 @@
+ return this.levels.get(dimension);
+ }
+
++ // 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();
+ }
+@@ -1053,7 +1458,7 @@
+
+ @DontObfuscate
+ public String getServerModName() {
+- return "vanilla";
++ return server.getName(); // CraftBukkit - cb > vanilla!
+ }
+
+ public SystemReport fillSystemReport(SystemReport systemReport) {
+@@ -1714,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(
+@@ -1842,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-vineflower-stripped/net/minecraft/server/PlayerAdvancements.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/PlayerAdvancements.java.patch
new file mode 100644
index 0000000000..83c81e405f
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/PlayerAdvancements.java.patch
@@ -0,0 +1,39 @@
+--- a/net/minecraft/server/PlayerAdvancements.java
++++ b/net/minecraft/server/PlayerAdvancements.java
+@@ -142,11 +191,13 @@
+ }
+ }
+
+- private void applyFrom(ServerAdvancementManager serverAdvancementManager, PlayerAdvancements.Data data) {
+- data.forEach((resourceLocation, advancementProgress) -> {
+- AdvancementHolder advancementHolder = serverAdvancementManager.get(resourceLocation);
+- if (advancementHolder == null) {
+- LOGGER.warn("Ignored advancement '{}' in progress file {} - it doesn't exist anymore?", resourceLocation, this.playerSavePath);
++ private void applyFrom(ServerAdvancementManager advancementdataworld, PlayerAdvancements.a advancementdataplayer_a) {
++ advancementdataplayer_a.forEach((minecraftkey, advancementprogress) -> {
++ AdvancementHolder advancementholder = advancementdataworld.get(minecraftkey);
++
++ if (advancementholder == null) {
++ 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);
+@@ -173,11 +227,12 @@
+ this.unregisterListeners(advancementHolder);
+ this.progressChanged.add(advancementHolder);
+ flag = true;
+- if (!isDone && orStartProgress.isDone()) {
+- advancementHolder.value().rewards().grant(this.player);
+- advancementHolder.value().display().ifPresent(displayInfo -> {
+- if (displayInfo.shouldAnnounceChat() && this.player.level().getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) {
+- this.playerList.broadcastSystemMessage(displayInfo.getType().createAnnouncement(advancementHolder, this.player), false);
++ 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((advancementdisplay) -> {
++ if (advancementdisplay.shouldAnnounceChat() && this.player.level().getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) {
++ this.playerList.broadcastSystemMessage(advancementdisplay.getType().createAnnouncement(advancementholder, this.player), false);
+ }
+ });
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/ServerAdvancementManager.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/ServerAdvancementManager.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/ServerAdvancementManager.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/ServerFunctionManager.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/ServerFunctionManager.java.patch
new file mode 100644
index 0000000000..bbd09dbee1
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/ServerFunctionManager.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/server/ServerFunctionManager.java
++++ b/net/minecraft/server/ServerFunctionManager.java
+@@ -33,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-vineflower-stripped/net/minecraft/server/ServerScoreboard.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/ServerScoreboard.java.patch
new file mode 100644
index 0000000000..7f18620a5e
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/ServerScoreboard.java.patch
@@ -0,0 +1,194 @@
+--- a/net/minecraft/server/ServerScoreboard.java
++++ b/net/minecraft/server/ServerScoreboard.java
+@@ -34,14 +37,10 @@
+ }
+
+ @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();
+@@ -54,37 +53,38 @@
+ }
+
+ @Override
+- public void onPlayerRemoved(ScoreHolder scoreHolder) {
+- super.onPlayerRemoved(scoreHolder);
+- this.server.getPlayerList().broadcastAll(new ClientboundResetScorePacket(scoreHolder.getScoreboardName(), null));
++ public void onPlayerRemoved(ScoreHolder scoreholder) {
++ super.onPlayerRemoved(scoreholder);
++ this.broadcastAll(new ClientboundResetScorePacket(scoreholder.getScoreboardName(), (String) null)); // CraftBukkit
+ this.setDirty();
+ }
+
+ @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();
+ }
+
+ @Override
+- public void setDisplayObjective(DisplaySlot displaySlot, @Nullable Objective objective) {
+- Objective displayObjective = this.getDisplayObjective(displaySlot);
+- super.setDisplayObjective(displaySlot, objective);
+- if (displayObjective != objective && displayObjective != null) {
+- if (this.getObjectiveDisplaySlotCount(displayObjective) > 0) {
+- this.server.getPlayerList().broadcastAll(new ClientboundSetDisplayObjectivePacket(displaySlot, objective));
++ public void setDisplayObjective(DisplaySlot displayslot, @Nullable Objective scoreboardobjective) {
++ Objective scoreboardobjective1 = this.getDisplayObjective(displayslot);
++
++ super.setDisplayObjective(displayslot, scoreboardobjective);
++ if (scoreboardobjective1 != scoreboardobjective && scoreboardobjective1 != null) {
++ if (this.getObjectiveDisplaySlotCount(scoreboardobjective1) > 0) {
++ this.broadcastAll(new ClientboundSetDisplayObjectivePacket(displayslot, scoreboardobjective)); // CraftBukkit
+ } else {
+ this.stopTrackingObjective(displayObjective);
+ }
+ }
+
+- 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);
+ }
+@@ -96,9 +96,7 @@
+ @Override
+ public boolean addPlayerToTeam(String playerName, PlayerTeam team) {
+ if (super.addPlayerToTeam(playerName, team)) {
+- this.server
+- .getPlayerList()
+- .broadcastAll(ClientboundSetPlayerTeamPacket.createPlayerPacket(team, playerName, ClientboundSetPlayerTeamPacket.Action.ADD));
++ this.broadcastAll(ClientboundSetPlayerTeamPacket.createPlayerPacket(team, playerName, ClientboundSetPlayerTeamPacket.a.ADD)); // CraftBukkit
+ this.setDirty();
+ return true;
+ } else {
+@@ -109,9 +107,7 @@
+ @Override
+ public void removePlayerFromTeam(String username, PlayerTeam playerTeam) {
+ super.removePlayerFromTeam(username, playerTeam);
+- this.server
+- .getPlayerList()
+- .broadcastAll(ClientboundSetPlayerTeamPacket.createPlayerPacket(playerTeam, username, ClientboundSetPlayerTeamPacket.Action.REMOVE));
++ this.broadcastAll(ClientboundSetPlayerTeamPacket.createPlayerPacket(playerTeam, username, ClientboundSetPlayerTeamPacket.a.REMOVE)); // CraftBukkit
+ this.setDirty();
+ }
+
+@@ -125,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();
+@@ -144,21 +140,21 @@
+ @Override
+ public void onTeamAdded(PlayerTeam playerTeam) {
+ super.onTeamAdded(playerTeam);
+- this.server.getPlayerList().broadcastAll(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(playerTeam, true));
++ this.broadcastAll(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(playerTeam, true)); // CraftBukkit
+ this.setDirty();
+ }
+
+ @Override
+ public void onTeamChanged(PlayerTeam playerTeam) {
+ super.onTeamChanged(playerTeam);
+- this.server.getPlayerList().broadcastAll(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(playerTeam, false));
++ this.broadcastAll(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(playerTeam, false)); // CraftBukkit
+ this.setDirty();
+ }
+
+ @Override
+ public void onTeamRemoved(PlayerTeam playerTeam) {
+ super.onTeamRemoved(playerTeam);
+- this.server.getPlayerList().broadcastAll(ClientboundSetPlayerTeamPacket.createRemovePacket(playerTeam));
++ this.broadcastAll(ClientboundSetPlayerTeamPacket.createRemovePacket(playerTeam)); // CraftBukkit
+ this.setDirty();
+ }
+
+@@ -200,9 +203,15 @@
+ public void startTrackingObjective(Objective objective) {
+ List<Packet<?>> startTrackingPackets = this.getStartTrackingPackets(objective);
+
+- for (ServerPlayer serverPlayer : this.server.getPlayerList().getPlayers()) {
+- for (Packet<?> packet : startTrackingPackets) {
+- serverPlayer.connection.send(packet);
++ while (iterator.hasNext()) {
++ 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()) {
++ Packet<?> packet = (Packet) iterator1.next();
++
++ entityplayer.connection.send(packet);
+ }
+ }
+
+@@ -225,9 +240,15 @@
+ public void stopTrackingObjective(Objective objective) {
+ List<Packet<?>> stopTrackingPackets = this.getStopTrackingPackets(objective);
+
+- for (ServerPlayer serverPlayer : this.server.getPlayerList().getPlayers()) {
+- for (Packet<?> packet : stopTrackingPackets) {
+- serverPlayer.connection.send(packet);
++ while (iterator.hasNext()) {
++ 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()) {
++ Packet<?> packet = (Packet) iterator1.next();
++
++ entityplayer.connection.send(packet);
+ }
+ }
+
+@@ -260,8 +287,20 @@
+ return this.createData().load(tag);
+ }
+
+- public static enum Method {
+- CHANGE,
+- REMOVE;
++ // 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
++
++ public static enum Action {
++
++ CHANGE, REMOVE;
++
++ private Action() {}
++ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/ServerTickRateManager.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/ServerTickRateManager.java.patch
new file mode 100644
index 0000000000..c245ad7f1a
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/ServerTickRateManager.java.patch
@@ -0,0 +1,55 @@
+--- a/net/minecraft/server/ServerTickRateManager.java
++++ b/net/minecraft/server/ServerTickRateManager.java
+@@ -58,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;
+@@ -76,14 +84,21 @@
+ return flag;
+ }
+
+- private void finishTickSprint() {
+- long l = this.scheduledCurrentSprintTicks - this.remainingSprintTicks;
+- double d = Math.max(1.0, (double)this.sprintTimeSpend) / (double)TimeUtil.NANOSECONDS_PER_MILLISECOND;
+- int i = (int)((double)(TimeUtil.MILLISECONDS_PER_SECOND * l) / d);
+- String string = String.format("%.2f", l == 0L ? (double)this.millisecondsPerTick() : d / (double)l);
++ 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);
++ String s = String.format("%.2f", i == 0L ? (double) this.millisecondsPerTick() : d0 / (double) i);
++
+ this.scheduledCurrentSprintTicks = 0L;
+ this.sprintTimeSpend = 0L;
+- this.server.createCommandSourceStack().sendSuccess(() -> Component.translatable("commands.tick.sprint.report", i, string), 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();
+@@ -97,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-vineflower-stripped/net/minecraft/server/bossevents/CustomBossEvent.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/bossevents/CustomBossEvent.java.patch
new file mode 100644
index 0000000000..33f0bf143a
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/bossevents/CustomBossEvent.java.patch
@@ -0,0 +1,30 @@
+--- a/net/minecraft/server/bossevents/CustomBossEvent.java
++++ b/net/minecraft/server/bossevents/CustomBossEvent.java
+@@ -17,13 +17,27 @@
+ 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 {
+ private final ResourceLocation id;
+ private final Set<UUID> players = Sets.newHashSet();
+ private int value;
+ private int max = 100;
++ // CraftBukkit start
++ private KeyedBossBar bossBar;
+
++ 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;
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/DifficultyCommand.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/DifficultyCommand.java.patch
new file mode 100644
index 0000000000..b15cddbe16
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/DifficultyCommand.java.patch
@@ -0,0 +1,24 @@
+--- a/net/minecraft/server/commands/DifficultyCommand.java
++++ b/net/minecraft/server/commands/DifficultyCommand.java
+@@ -31,12 +43,16 @@
+ }
+
+ public static int setDifficulty(CommandSourceStack source, Difficulty difficulty) throws CommandSyntaxException {
+- MinecraftServer server = source.getServer();
+- if (server.getWorldData().getDifficulty() == difficulty) {
+- throw ERROR_ALREADY_DIFFICULT.create(difficulty.getKey());
++ MinecraftServer minecraftserver = source.getServer();
++ net.minecraft.server.level.ServerLevel worldServer = source.getLevel(); // CraftBukkit
++
++ if (worldServer.getDifficulty() == difficulty) { // CraftBukkit
++ throw DifficultyCommand.ERROR_ALREADY_DIFFICULT.create(difficulty.getKey());
+ } else {
+- server.setDifficulty(difficulty, true);
+- source.sendSuccess(() -> Component.translatable("commands.difficulty.success", difficulty.getDisplayName()), true);
++ worldServer.serverLevelData.setDifficulty(difficulty); // CraftBukkit
++ source.sendSuccess(() -> {
++ return Component.translatable("commands.difficulty.success", difficulty.getDisplayName());
++ }, true);
+ return 0;
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/EffectCommands.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/EffectCommands.java.patch
new file mode 100644
index 0000000000..30036ad39a
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/EffectCommands.java.patch
@@ -0,0 +1,46 @@
+--- a/net/minecraft/server/commands/EffectCommands.java
++++ b/net/minecraft/server/commands/EffectCommands.java
+@@ -180,9 +82,10 @@
+
+ for (Entity entity : targets) {
+ if (entity instanceof LivingEntity) {
+- MobEffectInstance mobEffectInstance = new MobEffectInstance(mobEffect, i1, amplifier, false, showParticles);
+- if (((LivingEntity)entity).addEffect(mobEffectInstance, source.getEntity())) {
+- i++;
++ MobEffectInstance mobeffect = new MobEffectInstance(mobeffectlist, k, amplifier, false, showParticles);
++
++ if (((LivingEntity) entity).addEffect(mobeffect, source.getEntity(), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND)) { // CraftBukkit
++ ++j;
+ }
+ }
+ }
+@@ -210,9 +111,11 @@
+ private static int clearEffects(CommandSourceStack source, Collection<? extends Entity> targets) throws CommandSyntaxException {
+ int i = 0;
+
+- for (Entity entity : targets) {
+- if (entity instanceof LivingEntity && ((LivingEntity)entity).removeAllEffects()) {
+- i++;
++ while (iterator.hasNext()) {
++ Entity entity = (Entity) iterator.next();
++
++ if (entity instanceof LivingEntity && ((LivingEntity) entity).removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND)) { // CraftBukkit
++ ++i;
+ }
+ }
+
+@@ -235,9 +141,11 @@
+ MobEffect mobEffect = effect.value();
+ int i = 0;
+
+- for (Entity entity : targets) {
+- if (entity instanceof LivingEntity && ((LivingEntity)entity).removeEffect(mobEffect)) {
+- i++;
++ while (iterator.hasNext()) {
++ Entity entity = (Entity) iterator.next();
++
++ if (entity instanceof LivingEntity && ((LivingEntity) entity).removeEffect(mobeffectlist, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.COMMAND)) { // CraftBukkit
++ ++i;
+ }
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/GameRuleCommand.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/GameRuleCommand.java.patch
new file mode 100644
index 0000000000..7c5cf0e4ab
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/GameRuleCommand.java.patch
@@ -0,0 +1,33 @@
+--- a/net/minecraft/server/commands/GameRuleCommand.java
++++ b/net/minecraft/server/commands/GameRuleCommand.java
+@@ -27,16 +30,22 @@
+ }
+
+ static <T extends GameRules.Value<T>> int setRule(CommandContext<CommandSourceStack> source, GameRules.Key<T> gameRule) {
+- CommandSourceStack commandSourceStack = source.getSource();
+- T rule = commandSourceStack.getServer().getGameRules().getRule(gameRule);
+- rule.setFromArgument(source, "value");
+- commandSourceStack.sendSuccess(() -> Component.translatable("commands.gamerule.set", gameRule.getId(), rule.toString()), true);
+- return rule.getCommandResult();
++ CommandSourceStack commandlistenerwrapper = (CommandSourceStack) source.getSource();
++ T t0 = commandlistenerwrapper.getLevel().getGameRules().getRule(gameRule); // CraftBukkit
++
++ t0.setFromArgument(source, "value");
++ commandlistenerwrapper.sendSuccess(() -> {
++ return Component.translatable("commands.gamerule.set", gameRule.getId(), t0.toString());
++ }, true);
++ return t0.getCommandResult();
+ }
+
+ static <T extends GameRules.Value<T>> int queryRule(CommandSourceStack source, GameRules.Key<T> gameRule) {
+- T rule = source.getServer().getGameRules().getRule(gameRule);
+- source.sendSuccess(() -> Component.translatable("commands.gamerule.query", gameRule.getId(), rule.toString()), false);
+- return rule.getCommandResult();
++ T t0 = source.getLevel().getGameRules().getRule(gameRule); // CraftBukkit
++
++ source.sendSuccess(() -> {
++ return Component.translatable("commands.gamerule.query", gameRule.getId(), t0.toString());
++ }, false);
++ return t0.getCommandResult();
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/GiveCommand.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/GiveCommand.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/GiveCommand.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/ListPlayersCommand.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/ListPlayersCommand.java.patch
new file mode 100644
index 0000000000..6bf629b9b6
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/ListPlayersCommand.java.patch
@@ -0,0 +1,28 @@
+--- a/net/minecraft/server/commands/ListPlayersCommand.java
++++ b/net/minecraft/server/commands/ListPlayersCommand.java
+@@ -32,10 +34,20 @@
+ }
+
+ private static int format(CommandSourceStack source, Function<ServerPlayer, Component> nameExtractor) {
+- PlayerList playerList = source.getServer().getPlayerList();
+- List<ServerPlayer> players = playerList.getPlayers();
+- Component component = ComponentUtils.formatList(players, nameExtractor);
+- source.sendSuccess(() -> Component.translatable("commands.list.players", players.size(), playerList.getMaxPlayers(), component), false);
+- return players.size();
++ 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);
++
++ source.sendSuccess(() -> {
++ return Component.translatable("commands.list.players", list.size(), playerlist.getMaxPlayers(), ichatbasecomponent);
++ }, false);
++ return list.size();
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/LootCommand.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/LootCommand.java.patch
new file mode 100644
index 0000000000..1ab5f0f7ac
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/LootCommand.java.patch
@@ -0,0 +1,22 @@
+--- a/net/minecraft/server/commands/LootCommand.java
++++ b/net/minecraft/server/commands/LootCommand.java
+@@ -392,11 +245,14 @@
+ }
+
+ private static int dropInWorld(CommandSourceStack source, Vec3 pos, List<ItemStack> items, LootCommand.Callback callback) throws CommandSyntaxException {
+- ServerLevel level = source.getLevel();
+- items.forEach(itemStack -> {
+- ItemEntity itemEntity = new ItemEntity(level, pos.x, pos.y, pos.z, itemStack.copy());
+- itemEntity.setDefaultPickUpDelay();
+- level.addFreshEntity(itemEntity);
++ ServerLevel worldserver = source.getLevel();
++
++ 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());
++
++ entityitem.setDefaultPickUpDelay();
++ worldserver.addFreshEntity(entityitem);
+ });
+ callback.accept(items);
+ return items.size();
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/PlaceCommand.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/PlaceCommand.java.patch
new file mode 100644
index 0000000000..56cb1597d9
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/PlaceCommand.java.patch
@@ -0,0 +1,47 @@
+--- a/net/minecraft/server/commands/PlaceCommand.java
++++ b/net/minecraft/server/commands/PlaceCommand.java
+@@ -278,30 +130,20 @@
+ if (!structureStart.isValid()) {
+ throw 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()));
+- checkLoaded(level, chunkPos, chunkPos1);
+- ChunkPos.rangeClosed(chunkPos, chunkPos1)
+- .forEach(
+- chunkPos2 -> structureStart.placeInChunk(
+- level,
+- level.structureManager(),
+- generator,
+- level.getRandom(),
+- new BoundingBox(
+- chunkPos2.getMinBlockX(),
+- level.getMinBuildHeight(),
+- chunkPos2.getMinBlockZ(),
+- chunkPos2.getMaxBlockX(),
+- level.getMaxBuildHeight(),
+- chunkPos2.getMaxBlockZ()
+- ),
+- chunkPos2
+- )
+- );
+- String string = structure.key().location().toString();
+- source.sendSuccess(() -> Component.translatable("commands.place.structure.success", string, pos.getX(), pos.getY(), pos.getZ()), true);
++ 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(worldserver, chunkcoordintpair, chunkcoordintpair1);
++ ChunkPos.rangeClosed(chunkcoordintpair, chunkcoordintpair1).forEach((chunkcoordintpair2) -> {
++ structurestart.placeInChunk(worldserver, worldserver.structureManager(), chunkgenerator, worldserver.getRandom(), new BoundingBox(chunkcoordintpair2.getMinBlockX(), worldserver.getMinBuildHeight(), chunkcoordintpair2.getMinBlockZ(), chunkcoordintpair2.getMaxBlockX(), worldserver.getMaxBuildHeight(), chunkcoordintpair2.getMaxBlockZ()), chunkcoordintpair2);
++ });
++ String s = structure.key().location().toString();
++
++ source.sendSuccess(() -> {
++ return Component.translatable("commands.place.structure.success", s, pos.getX(), pos.getY(), pos.getZ());
++ }, true);
+ return 1;
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/ReloadCommand.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/ReloadCommand.java.patch
new file mode 100644
index 0000000000..a69371ae12
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/ReloadCommand.java.patch
@@ -0,0 +1,19 @@
+--- a/net/minecraft/server/commands/ReloadCommand.java
++++ b/net/minecraft/server/commands/ReloadCommand.java
+@@ -38,6 +44,16 @@
+ return list;
+ }
+
++ // 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(Commands.literal("reload").requires(source -> source.hasPermission(2)).executes(context -> {
+ CommandSourceStack commandSourceStack = context.getSource();
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/ScheduleCommand.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/ScheduleCommand.java.patch
new file mode 100644
index 0000000000..52ffe0022a
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/ScheduleCommand.java.patch
@@ -0,0 +1,64 @@
+--- a/net/minecraft/server/commands/ScheduleCommand.java
++++ b/net/minecraft/server/commands/ScheduleCommand.java
+@@ -98,33 +53,34 @@
+ if (time == 0) {
+ throw ERROR_SAME_TICK.create();
+ } else {
+- long l = source.getLevel().getGameTime() + (long)time;
+- ResourceLocation resourceLocation = function.getFirst();
+- TimerQueue<MinecraftServer> scheduledEvents = source.getServer().getWorldData().overworldData().getScheduledEvents();
+- function.getSecond()
+- .ifLeft(
+- commandFunction -> {
+- String string = resourceLocation.toString();
+- if (append) {
+- scheduledEvents.remove(string);
+- }
+-
+- scheduledEvents.schedule(string, l, new FunctionCallback(resourceLocation));
+- source.sendSuccess(
+- () -> Component.translatable("commands.schedule.created.function", Component.translationArg(resourceLocation), time, l), true
+- );
+- }
+- )
+- .ifRight(functions -> {
+- String string = "#" + resourceLocation;
+- if (append) {
+- scheduledEvents.remove(string);
+- }
+-
+- scheduledEvents.schedule(string, l, new FunctionTagCallback(resourceLocation));
+- source.sendSuccess(() -> Component.translatable("commands.schedule.created.tag", Component.translationArg(resourceLocation), time, l), true);
+- });
+- return Math.floorMod(l, Integer.MAX_VALUE);
++ 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) function.getSecond()).ifLeft((net_minecraft_commands_functions_commandfunction) -> {
++ String s = minecraftkey.toString();
++
++ if (append) {
++ customfunctioncallbacktimerqueue.remove(s);
++ }
++
++ customfunctioncallbacktimerqueue.schedule(s, j, new FunctionCallback(minecraftkey));
++ source.sendSuccess(() -> {
++ return Component.translatable("commands.schedule.created.function", Component.translationArg(minecraftkey), time, j);
++ }, true);
++ }).ifRight((collection) -> {
++ String s = "#" + minecraftkey;
++
++ if (append) {
++ customfunctioncallbacktimerqueue.remove(s);
++ }
++
++ customfunctioncallbacktimerqueue.schedule(s, j, new FunctionTagCallback(minecraftkey));
++ source.sendSuccess(() -> {
++ return Component.translatable("commands.schedule.created.tag", Component.translationArg(minecraftkey), time, j);
++ }, true);
++ });
++ return Math.floorMod(j, Integer.MAX_VALUE);
+ }
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/SetSpawnCommand.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/SetSpawnCommand.java.patch
new file mode 100644
index 0000000000..356262cf04
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/SetSpawnCommand.java.patch
@@ -0,0 +1,15 @@
+--- a/net/minecraft/server/commands/SetSpawnCommand.java
++++ b/net/minecraft/server/commands/SetSpawnCommand.java
+@@ -67,8 +38,10 @@
+ private static int setSpawn(CommandSourceStack source, Collection<ServerPlayer> targets, BlockPos pos, float angle) {
+ ResourceKey<Level> resourceKey = source.getLevel().dimension();
+
+- for (ServerPlayer serverPlayer : targets) {
+- serverPlayer.setRespawnPosition(resourceKey, pos, angle, true, false);
++ while (iterator.hasNext()) {
++ ServerPlayer entityplayer = (ServerPlayer) iterator.next();
++
++ entityplayer.setRespawnPosition(resourcekey, pos, angle, true, false, org.bukkit.event.player.PlayerSpawnChangeEvent.Cause.COMMAND); // CraftBukkit
+ }
+
+ String string = resourceKey.location().toString();
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/SpreadPlayersCommand.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/SpreadPlayersCommand.java.patch
new file mode 100644
index 0000000000..d4a282363e
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/SpreadPlayersCommand.java.patch
@@ -0,0 +1,23 @@
+--- a/net/minecraft/server/commands/SpreadPlayersCommand.java
++++ b/net/minecraft/server/commands/SpreadPlayersCommand.java
+@@ -247,16 +203,10 @@
+ position = positions[i++];
+ }
+
+- entity.teleportTo(
+- level,
+- (double)Mth.floor(position.x) + 0.5,
+- (double)position.getSpawnY(level, maxHeight),
+- (double)Mth.floor(position.z) + 0.5,
+- Set.of(),
+- entity.getYRot(),
+- entity.getXRot()
+- );
+- double d1 = Double.MAX_VALUE;
++ 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[] acommandspreadplayers_a1 = positions;
++ int k = positions.length;
+
+ for (SpreadPlayersCommand.Position position1 : positions) {
+ if (position != position1) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/SummonCommand.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/SummonCommand.java.patch
new file mode 100644
index 0000000000..78a9b9643e
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/SummonCommand.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/server/commands/SummonCommand.java
++++ b/net/minecraft/server/commands/SummonCommand.java
+@@ -93,8 +67,8 @@
+ .finalizeSpawn(source.getLevel(), source.getLevel().getCurrentDifficultyAt(entity.blockPosition()), MobSpawnType.COMMAND, null, null);
+ }
+
+- if (!level.tryAddFreshEntityWithPassengers(entity)) {
+- throw ERROR_DUPLICATE_UUID.create();
++ 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-vineflower-stripped/net/minecraft/server/commands/TeleportCommand.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/TeleportCommand.java.patch
new file mode 100644
index 0000000000..7c48c84c90
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/TeleportCommand.java.patch
@@ -0,0 +1,71 @@
+--- a/net/minecraft/server/commands/TeleportCommand.java
++++ b/net/minecraft/server/commands/TeleportCommand.java
+@@ -31,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 {
+ private static final SimpleCommandExceptionType INVALID_POSITION = new SimpleCommandExceptionType(
+@@ -278,15 +158,48 @@
+ if (!Level.isInSpawnableBounds(blockPos)) {
+ throw INVALID_POSITION.create();
+ } else {
+- float f = Mth.wrapDegrees(yaw);
+- float f1 = Mth.wrapDegrees(pitch);
+- if (entity.teleportTo(level, x, y, z, relativeList, f, f1)) {
+- if (facing != null) {
+- facing.perform(source, entity);
++ float f2 = Mth.wrapDegrees(z);
++ float f3 = Mth.wrapDegrees(f1);
++
++ // 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;
+ }
+
+- if (!(entity instanceof LivingEntity livingEntity) || !livingEntity.isFallFlying()) {
+- entity.setDeltaMovement(entity.getDeltaMovement().multiply(1.0, 0.0, 1.0));
++ 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) {
++ LivingEntity entityliving = (LivingEntity) entity;
++
++ if (entityliving.isFallFlying()) {
++ break label23;
++ }
++ }
++
++ entity.setDeltaMovement(entity.getDeltaMovement().multiply(1.0D, 0.0D, 1.0D));
+ entity.setOnGround(true);
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/TimeCommand.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/TimeCommand.java.patch
new file mode 100644
index 0000000000..2b1aacbdab
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/TimeCommand.java.patch
@@ -0,0 +1,55 @@
+--- a/net/minecraft/server/commands/TimeCommand.java
++++ b/net/minecraft/server/commands/TimeCommand.java
+@@ -8,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 {
+ public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
+@@ -57,8 +53,18 @@
+ }
+
+ public static int setTime(CommandSourceStack source, int time) {
+- for (ServerLevel serverLevel : source.getServer().getAllLevels()) {
+- serverLevel.setDayTime((long)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 worldserver = (ServerLevel) iterator.next();
++
++ // 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
+ }
+
+ source.sendSuccess(() -> Component.translatable("commands.time.set", time), true);
+@@ -66,8 +74,18 @@
+ }
+
+ public static int addTime(CommandSourceStack source, int amount) {
+- for (ServerLevel serverLevel : source.getServer().getAllLevels()) {
+- serverLevel.setDayTime(serverLevel.getDayTime() + (long)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 worldserver = (ServerLevel) iterator.next();
++
++ // 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 dayTime = getDayTime(source.getLevel());
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/WorldBorderCommand.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/WorldBorderCommand.java.patch
new file mode 100644
index 0000000000..62a4be2c2b
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/commands/WorldBorderCommand.java.patch
@@ -0,0 +1,122 @@
+--- a/net/minecraft/server/commands/WorldBorderCommand.java
++++ b/net/minecraft/server/commands/WorldBorderCommand.java
+@@ -137,9 +57,10 @@
+ }
+
+ private static int setDamageBuffer(CommandSourceStack source, float distance) throws CommandSyntaxException {
+- WorldBorder worldBorder = source.getServer().overworld().getWorldBorder();
+- if (worldBorder.getDamageSafeZone() == (double)distance) {
+- throw ERROR_SAME_DAMAGE_BUFFER.create();
++ WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit
++
++ if (worldborder.getDamageSafeZone() == (double) distance) {
++ throw WorldBorderCommand.ERROR_SAME_DAMAGE_BUFFER.create();
+ } else {
+ worldBorder.setDamageSafeZone((double)distance);
+ source.sendSuccess(() -> Component.translatable("commands.worldborder.damage.buffer.success", String.format(Locale.ROOT, "%.2f", distance)), true);
+@@ -148,9 +71,10 @@
+ }
+
+ private static int setDamageAmount(CommandSourceStack source, float damagePerBlock) throws CommandSyntaxException {
+- WorldBorder worldBorder = source.getServer().overworld().getWorldBorder();
+- if (worldBorder.getDamagePerBlock() == (double)damagePerBlock) {
+- throw ERROR_SAME_DAMAGE_AMOUNT.create();
++ WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit
++
++ if (worldborder.getDamagePerBlock() == (double) damagePerBlock) {
++ throw WorldBorderCommand.ERROR_SAME_DAMAGE_AMOUNT.create();
+ } else {
+ worldBorder.setDamagePerBlock((double)damagePerBlock);
+ source.sendSuccess(
+@@ -161,9 +85,10 @@
+ }
+
+ private static int setWarningTime(CommandSourceStack source, int time) throws CommandSyntaxException {
+- WorldBorder worldBorder = source.getServer().overworld().getWorldBorder();
+- if (worldBorder.getWarningTime() == time) {
+- throw ERROR_SAME_WARNING_TIME.create();
++ WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit
++
++ if (worldborder.getWarningTime() == time) {
++ throw WorldBorderCommand.ERROR_SAME_WARNING_TIME.create();
+ } else {
+ worldBorder.setWarningTime(time);
+ source.sendSuccess(() -> Component.translatable("commands.worldborder.warning.time.success", time), true);
+@@ -172,9 +99,10 @@
+ }
+
+ private static int setWarningDistance(CommandSourceStack source, int distance) throws CommandSyntaxException {
+- WorldBorder worldBorder = source.getServer().overworld().getWorldBorder();
+- if (worldBorder.getWarningBlocks() == distance) {
+- throw ERROR_SAME_WARNING_DISTANCE.create();
++ WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit
++
++ if (worldborder.getWarningBlocks() == distance) {
++ throw WorldBorderCommand.ERROR_SAME_WARNING_DISTANCE.create();
+ } else {
+ worldBorder.setWarningBlocks(distance);
+ source.sendSuccess(() -> Component.translatable("commands.worldborder.warning.distance.success", distance), true);
+@@ -183,38 +113,40 @@
+ }
+
+ private static int getSize(CommandSourceStack source) {
+- double size = source.getServer().overworld().getWorldBorder().getSize();
+- source.sendSuccess(() -> Component.translatable("commands.worldborder.get", String.format(Locale.ROOT, "%.0f", size)), false);
+- return Mth.floor(size + 0.5);
++ double d0 = source.getLevel().getWorldBorder().getSize(); // CraftBukkit
++
++ source.sendSuccess(() -> {
++ return Component.translatable("commands.worldborder.get", String.format(Locale.ROOT, "%.0f", d0));
++ }, false);
++ return Mth.floor(d0 + 0.5D);
+ }
+
+ private static int setCenter(CommandSourceStack source, Vec2 pos) throws CommandSyntaxException {
+- WorldBorder worldBorder = source.getServer().overworld().getWorldBorder();
+- if (worldBorder.getCenterX() == (double)pos.x && worldBorder.getCenterZ() == (double)pos.y) {
+- throw ERROR_SAME_CENTER.create();
+- } else if (!((double)Math.abs(pos.x) > 2.9999984E7) && !((double)Math.abs(pos.y) > 2.9999984E7)) {
+- worldBorder.setCenter((double)pos.x, (double)pos.y);
+- source.sendSuccess(
+- () -> Component.translatable(
+- "commands.worldborder.center.success", String.format(Locale.ROOT, "%.2f", pos.x), String.format(Locale.ROOT, "%.2f", pos.y)
+- ),
+- true
+- );
++ WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit
++
++ if (worldborder.getCenterX() == (double) pos.x && worldborder.getCenterZ() == (double) pos.y) {
++ throw WorldBorderCommand.ERROR_SAME_CENTER.create();
++ } else if ((double) Math.abs(pos.x) <= 2.9999984E7D && (double) Math.abs(pos.y) <= 2.9999984E7D) {
++ worldborder.setCenter((double) pos.x, (double) pos.y);
++ source.sendSuccess(() -> {
++ return Component.translatable("commands.worldborder.center.success", String.format(Locale.ROOT, "%.2f", pos.x), String.format(Locale.ROOT, "%.2f", pos.y));
++ }, true);
+ return 0;
+ } else {
+ throw ERROR_TOO_FAR_OUT.create();
+ }
+ }
+
+- private static int setSize(CommandSourceStack source, double newSize, long time) throws CommandSyntaxException {
+- WorldBorder worldBorder = source.getServer().overworld().getWorldBorder();
+- double size = worldBorder.getSize();
+- if (size == newSize) {
+- throw ERROR_SAME_SIZE.create();
+- } else if (newSize < 1.0) {
+- throw ERROR_TOO_SMALL.create();
+- } else if (newSize > 5.999997E7F) {
+- throw ERROR_TOO_BIG.create();
++ private static int setSize(CommandSourceStack source, double newSize, long i) throws CommandSyntaxException {
++ WorldBorder worldborder = source.getLevel().getWorldBorder(); // CraftBukkit
++ double d1 = worldborder.getSize();
++
++ if (d1 == newSize) {
++ throw WorldBorderCommand.ERROR_SAME_SIZE.create();
++ } else if (newSize < 1.0D) {
++ throw WorldBorderCommand.ERROR_TOO_SMALL.create();
++ } else if (newSize > 5.9999968E7D) {
++ throw WorldBorderCommand.ERROR_TOO_BIG.create();
+ } else {
+ if (time > 0L) {
+ worldBorder.lerpSizeBetween(size, newSize, time);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/dedicated/DedicatedServer.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/dedicated/DedicatedServer.java.patch
new file mode 100644
index 0000000000..6aba594883
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/dedicated/DedicatedServer.java.patch
@@ -0,0 +1,302 @@
+--- 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();
+ private static final int CONVERSION_RETRY_DELAY_MS = 5000;
+@@ -60,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;
+@@ -69,20 +79,13 @@
+ @Nullable
+ private final TextFilterClient textFilterClient;
+
+- public DedicatedServer(
+- Thread serverThread,
+- LevelStorageSource.LevelStorageAccess storageSource,
+- PackRepository packRepository,
+- WorldStem worldStem,
+- DedicatedServerSettings settings,
+- DataFixer fixerUpper,
+- Services services,
+- ChunkProgressListenerFactory progressListenerFactory
+- ) {
+- super(serverThread, storageSource, packRepository, worldStem, Proxy.NO_PROXY, fixerUpper, services, progressListenerFactory);
+- this.settings = settings;
+- this.rconConsoleSource = new RconConsoleSource(this);
+- this.textFilterClient = TextFilterClient.createFromConfig(settings.getProperties().textFilteringConfig);
++ // 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 RemoteControlCommandListener(this); // CraftBukkit - remove field
++ this.textFilterClient = TextFilterClient.createFromConfig(dedicatedserversettings.getProperties().textFilteringConfig);
+ }
+
+ @Override
+@@ -90,18 +92,72 @@
+ 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;
+
+ String string1;
+ try {
+- while (!DedicatedServer.this.isStopped() && DedicatedServer.this.isRunning() && (string1 = bufferedReader.readLine()) != null) {
+- DedicatedServer.this.handleConsoleInput(string1, DedicatedServer.this.createCommandSourceStack());
++ System.in.available();
++ } catch (IOException ex) {
++ return;
++ }
++ // CraftBukkit end
++
++ String s;
++
++ try {
++ // 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 var4) {
+ DedicatedServer.LOGGER.error("Exception handling console input", (Throwable)var4);
+ }
+ }
+ };
++
++ // 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(LOGGER));
+ thread.start();
+@@ -120,14 +178,15 @@
+ this.setLocalIp(properties.serverIp);
+ }
+
+- this.setPvpAllowed(properties.pvp);
+- this.setFlightAllowed(properties.allowFlight);
+- this.setMotd(properties.motd);
+- super.setPlayerIdleTimeout(properties.playerIdleTimeout.get());
+- this.setEnforceWhitelist(properties.enforceWhitelist);
+- this.worldData.setGameType(properties.gamemode);
+- LOGGER.info("Default game type: {}", properties.gamemode);
+- InetAddress inetAddress = null;
++ this.setPvpAllowed(dedicatedserverproperties.pvp);
++ this.setFlightAllowed(dedicatedserverproperties.allowFlight);
++ this.setMotd(dedicatedserverproperties.motd);
++ super.setPlayerIdleTimeout((Integer) dedicatedserverproperties.playerIdleTimeout.get());
++ this.setEnforceWhitelist(dedicatedserverproperties.enforceWhitelist);
++ // this.worldData.setGameType(dedicatedserverproperties.gamemode); // CraftBukkit - moved to world loading
++ DedicatedServer.LOGGER.info("Default game type: {}", dedicatedserverproperties.gamemode);
++ InetAddress inetaddress = null;
++
+ if (!this.getLocalIp().isEmpty()) {
+ inetAddress = InetAddress.getByName(this.getLocalIp());
+ }
+@@ -148,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()) {
+ LOGGER.warn("**** SERVER IS RUNNING IN OFFLINE/INSECURE MODE!");
+ LOGGER.warn("The server will make no attempt to authenticate usernames. Beware.");
+@@ -164,17 +227,19 @@
+ if (!OldUsersConverter.serverReadyAfterUserconversion(this)) {
+ return false;
+ } else {
+- this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage));
+- long nanos = Util.getNanos();
++ // 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());
+- LOGGER.info("Preparing level \"{}\"", this.getLevelIdName());
+- this.loadLevel();
+- long l = Util.getNanos() - nanos;
+- String string = String.format(Locale.ROOT, "%.3fs", (double)l / 1.0E9);
+- LOGGER.info("Done ({})! For help, type \"help\"", string);
+- if (properties.announcePlayerAchievements != null) {
+- this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS).set(properties.announcePlayerAchievements, this);
++ DedicatedServer.LOGGER.info("Preparing level \"{}\"", this.getLevelIdName());
++ this.loadLevel(storageSource.getLevelId()); // CraftBukkit
++ long j = Util.getNanos() - i;
++ String s = String.format(Locale.ROOT, "%.3fs", (double) j / 1.0E9D);
++
++ DedicatedServer.LOGGER.info("Done ({})! For help, type \"help\"", s);
++ if (dedicatedserverproperties.announcePlayerAchievements != null) {
++ ((GameRules.BooleanValue) this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)).set(dedicatedserverproperties.announcePlayerAchievements, this);
+ }
+
+ if (properties.enableQuery) {
+@@ -278,6 +364,8 @@
+ if (this.queryThreadGs4 != null) {
+ this.queryThreadGs4.stop();
+ }
++
++ System.exit(0); // CraftBukkit
+ }
+
+ @Override
+@@ -297,8 +385,17 @@
+
+ public void handleConsoleInputs() {
+ while (!this.consoleInput.isEmpty()) {
+- ConsoleInput consoleInput = this.consoleInput.remove(0);
+- this.getCommands().performPrefixedCommand(consoleInput.source, consoleInput.msg);
++ ConsoleInput servercommand = (ConsoleInput) this.consoleInput.remove(0);
++
++ // 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
+ }
+ }
+
+@@ -516,16 +622,54 @@
+
+ @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
+ public String runCommand(String command) {
+- this.rconConsoleSource.prepareForCommand();
+- this.executeBlocking(() -> this.getCommands().performPrefixedCommand(this.rconConsoleSource.createCommandSourceStack(), command));
+- return this.rconConsoleSource.getCommandResponse();
++ // CraftBukkit start - fire RemoteServerCommandEvent
++ throw new UnsupportedOperationException("Not supported - remote source required.");
+ }
+
++ public String runCommand(RconConsoleSource rconConsoleSource, String s) {
++ rconConsoleSource.prepareForCommand();
++ this.executeBlocking(() -> {
++ CommandSourceStack wrapper = rconConsoleSource.createCommandSourceStack();
++ RemoteServerCommandEvent event = new RemoteServerCommandEvent(rconConsoleSource.getBukkitSender(wrapper), s);
++ server.getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ return;
++ }
++ ConsoleInput serverCommand = new ConsoleInput(event.getCommand(), wrapper);
++ server.dispatchServerCommand(event.getSender(), serverCommand);
++ });
++ return rconConsoleSource.getCommandResponse();
++ // CraftBukkit end
++ }
++
+ public void storeUsingWhiteList(boolean isStoreUsingWhiteList) {
+ this.settings.update(dedicatedServerProperties -> dedicatedServerProperties.whiteList.update(this.registryAccess(), isStoreUsingWhiteList));
+ }
+@@ -572,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-vineflower-stripped/net/minecraft/server/dedicated/DedicatedServerProperties.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/dedicated/DedicatedServerProperties.java.patch
new file mode 100644
index 0000000000..83af36188f
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/dedicated/DedicatedServerProperties.java.patch
@@ -0,0 +1,95 @@
+--- a/net/minecraft/server/dedicated/DedicatedServerProperties.java
++++ b/net/minecraft/server/dedicated/DedicatedServerProperties.java
+@@ -41,10 +44,15 @@
+ 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", "");
+@@ -101,9 +108,53 @@
+ private final DedicatedServerProperties.WorldDimensionData worldDimensionData;
+ public final WorldOptions worldOptions;
+
+- public DedicatedServerProperties(Properties properties) {
+- super(properties);
+- String string = this.get("level-seed", "");
++ // 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");
++ this.serverPort = this.get("server-port", 25565);
++ this.announcePlayerAchievements = this.getLegacyBoolean("announce-player-achievements");
++ this.enableQuery = this.get("enable-query", false);
++ this.queryPort = this.get("query.port", 25565);
++ this.enableRcon = this.get("enable-rcon", false);
++ this.rconPort = this.get("rcon.port", 25575);
++ this.rconPassword = this.get("rcon.password", "");
++ this.hardcore = this.get("hardcore", false);
++ this.allowNether = this.get("allow-nether", true);
++ this.spawnMonsters = this.get("spawn-monsters", true);
++ this.useNativeTransport = this.get("use-native-transport", true);
++ this.enableCommandBlock = this.get("enable-command-block", false);
++ this.spawnProtection = this.get("spawn-protection", 16);
++ this.opPermissionLevel = this.get("op-permission-level", 4);
++ this.functionPermissionLevel = this.get("function-permission-level", 2);
++ this.maxTickTime = this.get("max-tick-time", TimeUnit.MINUTES.toMillis(1L));
++ this.maxChainedNeighborUpdates = this.get("max-chained-neighbor-updates", 1000000);
++ this.rateLimitPacketsPerSecond = this.get("rate-limit", 0);
++ this.viewDistance = this.get("view-distance", 10);
++ this.simulationDistance = this.get("simulation-distance", 10);
++ this.maxPlayers = this.get("max-players", 20);
++ this.networkCompressionThreshold = this.get("network-compression-threshold", 256);
++ this.broadcastRconToOps = this.get("broadcast-rcon-to-ops", true);
++ this.broadcastConsoleToOps = this.get("broadcast-console-to-ops", true);
++ this.maxWorldSize = this.get("max-world-size", (integer) -> {
++ return Mth.clamp(integer, 1, 29999984);
++ }, 29999984);
++ this.syncChunkWrites = this.get("sync-chunk-writes", true);
++ this.enableJmxMonitoring = this.get("enable-jmx-monitoring", false);
++ this.enableStatus = this.get("enable-status", true);
++ this.hideOnlinePlayers = this.get("hide-online-players", false);
++ this.entityBroadcastRangePercentage = this.get("entity-broadcast-range-percentage", (integer) -> {
++ return Mth.clamp(integer, 10, 1000);
++ }, 100);
++ this.textFilteringConfig = this.get("text-filtering-config", "");
++ this.playerIdleTimeout = this.getMutable("player-idle-timeout", 0);
++ this.whiteList = this.getMutable("white-list", false);
++ this.enforceSecureProfile = this.get("enforce-secure-profile", true);
++ this.logIPs = this.get("log-ips", true);
++ String s = this.get("level-seed", "");
+ boolean flag = this.get("generate-structures", true);
+ long l = WorldOptions.parseSeed(string).orElse(WorldOptions.randomSeed());
+ this.worldOptions = new WorldOptions(l, flag, false);
+@@ -125,13 +168,15 @@
+ );
+ }
+
+- 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
+- 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-vineflower-stripped/net/minecraft/server/dedicated/DedicatedServerSettings.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/dedicated/DedicatedServerSettings.java.patch
new file mode 100644
index 0000000000..a79b915d5f
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/dedicated/DedicatedServerSettings.java.patch
@@ -0,0 +1,26 @@
+--- a/net/minecraft/server/dedicated/DedicatedServerSettings.java
++++ b/net/minecraft/server/dedicated/DedicatedServerSettings.java
+@@ -3,13 +3,20 @@
+ 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 source) {
+- this.source = source;
+- this.properties = DedicatedServerProperties.fromFile(source);
++ // 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-vineflower-stripped/net/minecraft/server/dedicated/Settings.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/dedicated/Settings.java.patch
new file mode 100644
index 0000000000..35a501511c
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/dedicated/Settings.java.patch
@@ -0,0 +1,140 @@
+--- a/net/minecraft/server/dedicated/Settings.java
++++ b/net/minecraft/server/dedicated/Settings.java
+@@ -23,16 +22,38 @@
+ 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;
++
+ try {
+ Properties var13;
+ try (InputStream inputStream = Files.newInputStream(path)) {
+@@ -65,10 +116,33 @@
+ }
+
+ public void store(Path path) {
+- try (Writer bufferedWriter = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) {
+- this.properties.store(bufferedWriter, "Minecraft server properties");
+- } catch (IOException var7) {
+- LOGGER.error("Failed to store properties to file: {}", 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 {
++ this.properties.store(bufferedwriter, "Minecraft server properties");
++ } catch (Throwable throwable) {
++ if (bufferedwriter != null) {
++ try {
++ bufferedwriter.close();
++ } catch (Throwable throwable1) {
++ throwable.addSuppressed(throwable1);
++ }
++ }
++
++ throw throwable;
++ }
++
++ if (bufferedwriter != null) {
++ bufferedwriter.close();
++ }
++ } catch (IOException ioexception) {
++ Settings.LOGGER.error("Failed to store properties to file: {}", path);
+ }
+ }
+
+@@ -94,7 +169,7 @@
+
+ @Nullable
+ private String getStringRaw(String key) {
+- return (String)this.properties.get(key);
++ return (String) getOverride(key, this.properties.getProperty(key)); // CraftBukkit
+ }
+
+ @Nullable
+@@ -109,12 +185,23 @@
+ }
+
+ protected <V> V get(String key, Function<String, V> mapper, Function<V, String> toString, V value) {
+- String stringRaw = this.getStringRaw(key);
+- V object = MoreObjects.firstNonNull(stringRaw != null ? mapper.apply(stringRaw) : null, value);
+- this.properties.put(key, toString.apply(object));
+- return object;
++ // 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);
++
++ this.properties.put(s, function1.apply(v1));
++ return v1;
++ }
++
+ protected <V> Settings<T>.MutableValue<V> getMutable(String key, Function<String, V> mapper, Function<V, String> toString, V value) {
+ String stringRaw = this.getStringRaw(key);
+ V object = MoreObjects.firstNonNull(stringRaw != null ? mapper.apply(stringRaw) : null, value);
+@@ -181,7 +271,7 @@
+ return map;
+ }
+
+- 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> {
+ private final String key;
+@@ -200,9 +290,10 @@
+ }
+
+ public T update(RegistryAccess registryAccess, V newValue) {
+- Properties map = Settings.this.cloneProperties();
+- map.put(this.key, this.serializer.apply(newValue));
+- return Settings.this.reload(registryAccess, map);
++ Properties properties = Settings.this.cloneProperties();
++
++ properties.put(this.key, this.serializer.apply(newValue));
++ return Settings.this.reload(registryAccess, properties, Settings.this.options); // CraftBukkit
+ }
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/gui/MinecraftServerGui.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/gui/MinecraftServerGui.java.patch
new file mode 100644
index 0000000000..bdce3205fc
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/gui/MinecraftServerGui.java.patch
@@ -0,0 +1,22 @@
+--- a/net/minecraft/server/gui/MinecraftServerGui.java
++++ b/net/minecraft/server/gui/MinecraftServerGui.java
+@@ -151,6 +166,7 @@
+ this.finalizers.forEach(Runnable::run);
+ }
+
++ 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(textArea, scrollPane, line));
+@@ -164,8 +182,9 @@
+ }
+
+ try {
+- document.insertString(document.getLength(), line, null);
+- } catch (BadLocationException var8) {
++ document.insertString(document.getLength(), ANSI.matcher(line).replaceAll(""), (AttributeSet) null); // CraftBukkit
++ } catch (BadLocationException badlocationexception) {
++ ;
+ }
+
+ if (flag) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/level/ChunkHolder.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/level/ChunkHolder.java.patch
new file mode 100644
index 0000000000..81d8e68f87
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/level/ChunkHolder.java.patch
@@ -0,0 +1,153 @@
+--- a/net/minecraft/server/level/ChunkHolder.java
++++ b/net/minecraft/server/level/ChunkHolder.java
+@@ -37,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);
+ public static final CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> UNLOADED_CHUNK_FUTURE = CompletableFuture.completedFuture(
+@@ -93,14 +94,17 @@
+ this.changedBlocksPerSection = new ShortSet[levelHeightAccessor.getSectionsCount()];
+ }
+
+- public CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getFutureIfPresentUnchecked(ChunkStatus chunkStatus) {
+- CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completableFuture = this.futures.get(chunkStatus.getIndex());
+- return completableFuture == null ? UNLOADED_CHUNK_FUTURE : completableFuture;
++ // 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 CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getFutureIfPresent(ChunkStatus chunkStatus) {
+ return ChunkLevel.generationStatus(this.ticketLevel).isOrAfter(chunkStatus) ? this.getFutureIfPresentUnchecked(chunkStatus) : UNLOADED_CHUNK_FUTURE;
+ }
++ // CraftBukkit end
+
+ public CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> getTickingChunkFuture() {
+ return this.tickingChunkFuture;
+@@ -171,10 +192,13 @@
+ }
+
+ public void blockChanged(BlockPos pos) {
+- LevelChunk tickingChunk = this.getTickingChunk();
+- if (tickingChunk != null) {
+- int sectionIndex = this.levelHeightAccessor.getSectionIndex(pos.getY());
+- if (this.changedBlocksPerSection[sectionIndex] == null) {
++ LevelChunk chunk = this.getTickingChunk();
++
++ if (chunk != null) {
++ int i = this.levelHeightAccessor.getSectionIndex(pos.getY());
++
++ if (i < 0 || i >= this.changedBlocksPerSection.length) return; // CraftBukkit - SPIGOT-6086, SPIGOT-6296
++ if (this.changedBlocksPerSection[i] == null) {
+ this.hasChangedSections = true;
+ this.changedBlocksPerSection[sectionIndex] = new ShortOpenHashSet();
+ }
+@@ -238,14 +272,16 @@
+ this.broadcast(players, new ClientboundBlockUpdatePacket(blockPos, blockState));
+ this.broadcastBlockEntityIfNeeded(players, level, blockPos, blockState);
+ } else {
+- LevelChunkSection section = chunk.getSection(i);
+- ClientboundSectionBlocksUpdatePacket clientboundSectionBlocksUpdatePacket = new ClientboundSectionBlocksUpdatePacket(
+- sectionPos, set, section
+- );
+- this.broadcast(players, clientboundSectionBlocksUpdatePacket);
+- clientboundSectionBlocksUpdatePacket.runUpdates(
+- (blockPos1, blockState1) -> this.broadcastBlockEntityIfNeeded(players, level, blockPos1, blockState1)
+- );
++ LevelChunkSection chunksection = chunk.getSection(i);
++ ClientboundSectionBlocksUpdatePacket packetplayoutmultiblockchange = new ClientboundSectionBlocksUpdatePacket(sectionposition, shortset, chunksection);
++
++ this.broadcast(list, packetplayoutmultiblockchange);
++ // CraftBukkit start
++ List finalList = list;
++ packetplayoutmultiblockchange.runUpdates((blockposition1, iblockdata1) -> {
++ this.broadcastBlockEntityIfNeeded(finalList, world, blockposition1, iblockdata1);
++ // CraftBukkit end
++ });
+ }
+ }
+ }
+@@ -368,15 +427,39 @@
+ }
+
+ protected void updateFutures(ChunkMap chunkMap, Executor executor) {
+- ChunkStatus chunkStatus = ChunkLevel.generationStatus(this.oldTicketLevel);
+- ChunkStatus chunkStatus1 = ChunkLevel.generationStatus(this.ticketLevel);
+- boolean isLoaded = ChunkLevel.isLoaded(this.oldTicketLevel);
+- boolean isLoaded1 = ChunkLevel.isLoaded(this.ticketLevel);
+- FullChunkStatus fullChunkStatus = ChunkLevel.fullStatus(this.oldTicketLevel);
+- FullChunkStatus fullChunkStatus1 = ChunkLevel.fullStatus(this.ticketLevel);
+- if (isLoaded) {
+- Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either = Either.right(new ChunkHolder.ChunkLoadingFailure() {
+- @Override
++ ChunkStatus chunkstatus = ChunkLevel.generationStatus(this.oldTicketLevel);
++ ChunkStatus chunkstatus1 = ChunkLevel.generationStatus(this.ticketLevel);
++ boolean flag = ChunkLevel.isLoaded(this.oldTicketLevel);
++ boolean flag1 = ChunkLevel.isLoaded(this.ticketLevel);
++ FullChunkStatus fullchunkstatus = ChunkLevel.fullStatus(this.oldTicketLevel);
++ FullChunkStatus fullchunkstatus1 = ChunkLevel.fullStatus(this.ticketLevel);
++ // CraftBukkit start
++ // ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins.
++ if (fullchunkstatus.isOrAfter(FullChunkStatus.FULL) && !fullchunkstatus1.isOrAfter(FullChunkStatus.FULL)) {
++ this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> {
++ LevelChunk chunk = (LevelChunk)either.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.Failure> either = Either.right(new ChunkHolder.Failure() {
+ public String toString() {
+ return "Unloaded ticket level " + ChunkHolder.this.pos;
+ }
+@@ -440,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-vineflower-stripped/net/minecraft/server/level/ChunkMap.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/level/ChunkMap.java.patch
new file mode 100644
index 0000000000..663459c926
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/level/ChunkMap.java.patch
@@ -0,0 +1,171 @@
+--- a/net/minecraft/server/level/ChunkMap.java
++++ b/net/minecraft/server/level/ChunkMap.java
+@@ -95,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 {
+ private static final byte CHUNK_TYPE_REPLACEABLE = -1;
+@@ -137,31 +146,46 @@
+ private final Queue<Runnable> unloadQueue = Queues.newConcurrentLinkedQueue();
+ private int serverViewDistance;
+
+- 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
+- ) {
++ // 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.structureTemplateManager = structureManager;
+ Path dimensionPath = levelStorageAccess.getDimensionPath(level.dimension());
+ this.storageName = dimensionPath.getFileName().toString();
+ this.level = level;
+ this.generator = generator;
+- RegistryAccess registryAccess = level.registryAccess();
+- long seed = level.getSeed();
+- if (generator instanceof NoiseBasedChunkGenerator noiseBasedChunkGenerator) {
+- this.randomState = RandomState.create(noiseBasedChunkGenerator.generatorSettings().value(), registryAccess.lookupOrThrow(Registries.NOISE), seed);
++ // 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 (generator instanceof NoiseBasedChunkGenerator) {
++ NoiseBasedChunkGenerator chunkgeneratorabstract = (NoiseBasedChunkGenerator) generator;
++
++ this.randomState = RandomState.create((NoiseGeneratorSettings) chunkgeneratorabstract.generatorSettings().value(), (HolderGetter) iregistrycustom.lookupOrThrow(Registries.NOISE), j);
+ } else {
+ this.randomState = RandomState.create(NoiseGeneratorSettings.dummy(), registryAccess.lookupOrThrow(Registries.NOISE), seed);
+ }
+@@ -320,7 +364,11 @@
+ List<ChunkAccess> list3 = Lists.newArrayList();
+ int i4 = 0;
+
+- for (final Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either : list2) {
++ 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");
+ }
+@@ -713,7 +780,21 @@
+
+ private static void postLoadProtoChunk(ServerLevel level, List<CompoundTag> tags) {
+ if (!tags.isEmpty()) {
+- level.addWorldGenChunkEntities(EntityType.loadEntitiesRecursive(tags, level));
++ // 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
+ }
+ }
+
+@@ -986,11 +1092,15 @@
+ }
+
+ private CompletableFuture<Optional<CompoundTag>> readChunk(ChunkPos pos) {
+- return this.read(pos).thenApplyAsync(optional -> optional.map(this::upgradeChunkTag), Util.backgroundExecutor());
++ return this.read(pos).thenApplyAsync((optional) -> {
++ return optional.map((nbttagcompound) -> upgradeChunkTag(nbttagcompound, pos)); // CraftBukkit
++ }, Util.backgroundExecutor());
+ }
+
+- private CompoundTag upgradeChunkTag(CompoundTag tag) {
+- return this.upgradeChunkTag(this.level.dimension(), this.overworldDataStorage, tag, 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) {
+@@ -1306,8 +1508,8 @@
+ SectionPos lastSectionPos;
+ private final Set<ServerPlayerConnection> seenBy = Sets.newIdentityHashSet();
+
+- public TrackedEntity(Entity entity, int range, int updateInterval, boolean trackDelta) {
+- this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, updateInterval, trackDelta, this::broadcast);
++ public TrackedEntity(Entity entity, int i, int j, boolean flag) {
++ this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, seenBy); // CraftBukkit
+ this.entity = entity;
+ this.range = range;
+ this.lastSectionPos = SectionPos.of(entity);
+@@ -1350,14 +1562,18 @@
+
+ public void updatePlayer(ServerPlayer player) {
+ if (player != this.entity) {
+- Vec3 vec3 = player.position().subtract(this.entity.position());
+- int playerViewDistance = ChunkMap.this.getPlayerViewDistance(player);
+- double d = (double)Math.min(this.getEffectiveRange(), playerViewDistance * 16);
+- double d1 = vec3.x * vec3.x + vec3.z * vec3.z;
+- double d2 = d * d;
+- boolean flag = d1 <= d2
+- && this.entity.broadcastToPlayer(player)
+- && ChunkMap.this.isChunkTracked(player, this.entity.chunkPosition().x, this.entity.chunkPosition().z);
++ Vec3 vec3d = player.position().subtract(this.entity.position());
++ int i = ChunkMap.this.getPlayerViewDistance(player);
++ double d0 = (double) Math.min(this.getEffectiveRange(), i * 16);
++ double d1 = vec3d.x * vec3d.x + vec3d.z * vec3d.z;
++ double d2 = d0 * d0;
++ boolean flag = d1 <= d2 && this.entity.broadcastToPlayer(player) && ChunkMap.this.isChunkTracked(player, 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(player.connection)) {
+ this.serverEntity.addPairing(player);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/level/DistanceManager.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/level/DistanceManager.java.patch
new file mode 100644
index 0000000000..064463f5c7
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/level/DistanceManager.java.patch
@@ -0,0 +1,173 @@
+--- a/net/minecraft/server/level/DistanceManager.java
++++ b/net/minecraft/server/level/DistanceManager.java
+@@ -114,8 +122,25 @@
+ }
+
+ if (!this.chunksToUpdateFutures.isEmpty()) {
+- this.chunksToUpdateFutures.forEach(chunkHolder -> chunkHolder.updateFutures(chunkManager, 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()) {
+@@ -146,26 +176,33 @@
+ }
+ }
+
+- void addTicket(long chunkPos, Ticket<?> ticket) {
+- SortedArraySet<Ticket<?>> tickets = this.getTickets(chunkPos);
+- int ticketLevelAt = getTicketLevelAt(tickets);
+- Ticket<?> ticket1 = tickets.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() < ticketLevelAt) {
+ this.ticketTracker.update(chunkPos, ticket.getTicketLevel(), true);
+ }
++
++ return ticket == ticket1; // CraftBukkit
+ }
+
+- void removeTicket(long chunkPos, Ticket<?> ticket) {
+- SortedArraySet<Ticket<?>> tickets = this.getTickets(chunkPos);
+- if (tickets.remove(ticket)) {
++ boolean removeTicket(long i, Ticket<?> ticket) { // CraftBukkit - void -> boolean
++ SortedArraySet<Ticket<?>> arraysetsorted = this.getTickets(i);
++
++ boolean removed = false; // CraftBukkit
++ if (arraysetsorted.remove(ticket)) {
++ removed = true; // CraftBukkit
+ }
+
+ if (tickets.isEmpty()) {
+ this.tickets.remove(chunkPos);
+ }
+
+- this.ticketTracker.update(chunkPos, getTicketLevelAt(tickets), false);
++ this.ticketTracker.update(i, getTicketLevelAt(arraysetsorted), false);
++ return removed; // CraftBukkit
+ }
+
+ public <T> void addTicket(TicketType<T> type, ChunkPos pos, int level, T value) {
+@@ -178,19 +216,35 @@
+ }
+
+ public <T> void addRegionTicket(TicketType<T> type, ChunkPos pos, int distance, T value) {
+- Ticket<T> ticket = new Ticket<>(type, ChunkLevel.byStatus(FullChunkStatus.FULL) - distance, value);
+- long l = pos.toLong();
+- this.addTicket(l, ticket);
+- this.tickingTicketsTracker.addTicket(l, ticket);
++ // 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 = chunkcoordintpair.toLong();
++
++ boolean added = this.addTicket(j, ticket); // CraftBukkit
++ this.tickingTicketsTracker.addTicket(j, ticket);
++ return added; // CraftBukkit
++ }
++
+ public <T> void removeRegionTicket(TicketType<T> type, ChunkPos pos, int distance, T value) {
+- Ticket<T> ticket = new Ticket<>(type, ChunkLevel.byStatus(FullChunkStatus.FULL) - distance, value);
+- long l = pos.toLong();
+- this.removeTicket(l, ticket);
+- this.tickingTicketsTracker.removeTicket(l, ticket);
++ // 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 = chunkcoordintpair.toLong();
++
++ boolean removed = this.removeTicket(j, ticket); // CraftBukkit
++ this.tickingTicketsTracker.removeTicket(j, ticket);
++ return removed; // CraftBukkit
++ }
++
+ private SortedArraySet<Ticket<?>> getTickets(long chunkPos) {
+ return this.tickets.computeIfAbsent(chunkPos, l -> SortedArraySet.create(4));
+ }
+@@ -217,15 +278,17 @@
+ }
+
+ public void removePlayer(SectionPos sectionPos, ServerPlayer player) {
+- ChunkPos chunkPos = sectionPos.chunk();
+- long l = chunkPos.toLong();
+- ObjectSet<ServerPlayer> set = this.playersPerChunk.get(l);
+- set.remove(player);
+- if (set.isEmpty()) {
+- this.playersPerChunk.remove(l);
+- this.naturalSpawnChunkCounter.update(l, Integer.MAX_VALUE, false);
+- this.playerTicketManager.update(l, Integer.MAX_VALUE, false);
+- this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkPos, this.getPlayerTicketLevel(), chunkPos);
++ ChunkPos chunkcoordintpair = sectionPos.chunk();
++ long i = chunkcoordintpair.toLong();
++ ObjectSet<ServerPlayer> objectset = (ObjectSet) this.playersPerChunk.get(i);
++ if (objectset == null) return; // CraftBukkit - SPIGOT-6208
++
++ objectset.remove(player);
++ if (objectset.isEmpty()) {
++ this.playersPerChunk.remove(i);
++ this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false);
++ this.playerTicketManager.update(i, Integer.MAX_VALUE, false);
++ this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair);
+ }
+ }
+
+@@ -324,7 +411,28 @@
+ return !this.tickets.isEmpty();
+ }
+
+- class ChunkTicketTracker extends ChunkTracker {
++ // 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;
+
+ public ChunkTicketTracker() {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/level/ServerChunkCache.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/level/ServerChunkCache.java.patch
new file mode 100644
index 0000000000..7d99b3540f
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/level/ServerChunkCache.java.patch
@@ -0,0 +1,167 @@
+--- a/net/minecraft/server/level/ServerChunkCache.java
++++ b/net/minecraft/server/level/ServerChunkCache.java
+@@ -107,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
+ public ThreadedLevelLightEngine getLightEngine() {
+ return this.lightEngine;
+@@ -143,11 +129,16 @@
+ profiler.incrementCounter("getChunk");
+ long _long = ChunkPos.asLong(chunkX, chunkZ);
+
+- for (int i = 0; i < 4; i++) {
+- if (_long == this.lastChunkPos[i] && requiredStatus == this.lastChunkStatus[i]) {
+- ChunkAccess chunkAccess = this.lastChunk[i];
+- if (chunkAccess != null || !load) {
+- return chunkAccess;
++ gameprofilerfiller.incrementCounter("getChunk");
++ long k = ChunkPos.asLong(chunkX, chunkZ);
++
++ ChunkAccess ichunkaccess;
++
++ for (int l = 0; l < 4; ++l) {
++ 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;
+ }
+ }
+ }
+@@ -229,18 +232,26 @@
+ return chunkFutureMainThread;
+ }
+
+- private CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getChunkFutureMainThread(
+- int x, int y, ChunkStatus chunkStatus, boolean load
+- ) {
+- ChunkPos chunkPos = new ChunkPos(x, y);
+- long l = chunkPos.toLong();
+- int i = ChunkLevel.byStatus(chunkStatus);
+- ChunkHolder visibleChunkIfPresent = this.getVisibleChunkIfPresent(l);
+- if (load) {
+- this.distanceManager.addTicket(TicketType.UNKNOWN, chunkPos, i, chunkPos);
+- if (this.chunkAbsent(visibleChunkIfPresent, i)) {
+- ProfilerFiller profiler = this.level.getProfiler();
+- profiler.push("chunkLoad");
++ private CompletableFuture<Either<ChunkAccess, ChunkHolder.Failure>> getChunkFutureMainThread(int x, int y, ChunkStatus chunkStatus, boolean load) {
++ ChunkPos chunkcoordintpair = new ChunkPos(x, y);
++ long k = chunkcoordintpair.toLong();
++ int l = ChunkLevel.byStatus(chunkStatus);
++ ChunkHolder playerchunk = this.getVisibleChunkIfPresent(k);
++
++ // 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();
++
++ gameprofilerfiller.push("chunkLoad");
+ this.runDistanceManagerUpdates();
+ visibleChunkIfPresent = this.getVisibleChunkIfPresent(l);
+ profiler.pop();
+@@ -256,7 +265,7 @@
+ }
+
+ private boolean chunkAbsent(@Nullable ChunkHolder chunkHolder, int status) {
+- return chunkHolder == null || chunkHolder.getTicketLevel() > status;
++ return chunkHolder == null || chunkHolder.oldTicketLevel > status; // CraftBukkit using oldTicketLevel for isLoaded checks
+ }
+
+ @Override
+@@ -331,11 +346,31 @@
+
+ @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
+ public void tick(BooleanSupplier hasTimeLeft, boolean tickChunks) {
+ this.level.getProfiler().push("purge");
+@@ -371,17 +411,18 @@
+ }
+
+ if (this.level.getServer().tickRateManager().runsNormally()) {
+- profiler.popPush("naturalSpawnCount");
+- int naturalSpawnChunkCount = this.distanceManager.getNaturalSpawnChunkCount();
+- NaturalSpawner.SpawnState spawnState = NaturalSpawner.createState(
+- naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, new LocalMobCapCalculator(this.chunkMap)
+- );
+- this.lastSpawnState = spawnState;
+- profiler.popPush("spawnAndTick");
+- boolean _boolean = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING);
++ gameprofilerfiller.popPush("naturalSpawnCount");
++ int k = this.distanceManager.getNaturalSpawnChunkCount();
++ NaturalSpawner.SpawnState spawnercreature_d = NaturalSpawner.createState(k, this.level.getAllEntities(), this::getFullChunk, new LocalMobCapCalculator(this.chunkMap));
++
++ 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 _int = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING);
+- boolean flag = this.level.getLevelData().getGameTime() % 400L == 0L;
++ int l = this.level.getGameRules().getInt(GameRules.RULE_RANDOMTICKING);
++ boolean flag1 = this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) != 0L && this.level.getLevelData().getGameTime() % this.level.ticksPerSpawnCategory.getLong(org.bukkit.entity.SpawnCategory.ANIMAL) == 0L; // CraftBukkit
++ Iterator iterator1 = list.iterator();
+
+ for (ServerChunkCache.ChunkAndHolder chunkAndHolder : list) {
+ LevelChunk levelChunk = chunkAndHolder.chunk;
+@@ -574,6 +624,7 @@
+ }
+
+ @Override
++ // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task
+ public boolean pollTask() {
+ if (ServerChunkCache.this.runDistanceManagerUpdates()) {
+ return true;
+@@ -582,5 +636,7 @@
+ return super.pollTask();
+ }
+ }
++ // CraftBukkit end
++ }
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/level/ServerEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/level/ServerEntity.java.patch
new file mode 100644
index 0000000000..dbe60b2aa8
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/level/ServerEntity.java.patch
@@ -0,0 +1,199 @@
+--- a/net/minecraft/server/level/ServerEntity.java
++++ b/net/minecraft/server/level/ServerEntity.java
+@@ -40,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();
+ private static final int TOLERANCE_LEVEL_ROTATION = 1;
+@@ -63,10 +72,16 @@
+ private boolean wasOnGround;
+ @Nullable
+ private List<SynchedEntityData.DataValue<?>> trackedDataValues;
++ // CraftBukkit start
++ private final Set<ServerPlayerConnection> trackedPlayers;
+
+- public ServerEntity(ServerLevel level, Entity entity, int updateInterval, boolean trackDelta, Consumer<Packet<?>> broadcast) {
+- this.level = level;
+- this.broadcast = broadcast;
++ 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 = worldserver;
++ this.broadcast = consumer;
+ this.entity = entity;
+ this.updateInterval = updateInterval;
+ this.trackDelta = trackDelta;
+@@ -79,32 +94,45 @@
+ }
+
+ public void sendChanges() {
+- List<Entity> passengers = this.entity.getPassengers();
+- if (!passengers.equals(this.lastPassengers)) {
+- this.broadcast.accept(new ClientboundSetPassengersPacket(this.entity));
+- removedPassengers(passengers, this.lastPassengers)
+- .forEach(
+- entity -> {
+- if (entity instanceof ServerPlayer serverPlayer1) {
+- serverPlayer1.connection
+- .teleport(serverPlayer1.getX(), serverPlayer1.getY(), serverPlayer1.getZ(), serverPlayer1.getYRot(), serverPlayer1.getXRot());
+- }
+- }
+- );
+- this.lastPassengers = passengers;
++ List<Entity> list = this.entity.getPassengers();
++
++ if (!list.equals(this.lastPassengers)) {
++ this.broadcastAndSend(new ClientboundSetPassengersPacket(this.entity)); // CraftBukkit
++ removedPassengers(list, this.lastPassengers).forEach((entity) -> {
++ if (entity instanceof ServerPlayer) {
++ ServerPlayer entityplayer = (ServerPlayer) entity;
++
++ entityplayer.connection.teleport(entityplayer.getX(), entityplayer.getY(), entityplayer.getZ(), entityplayer.getYRot(), entityplayer.getXRot());
++ }
++
++ });
++ this.lastPassengers = list;
+ }
+
+- if (this.entity instanceof ItemFrame itemFrame && this.tickCount % 10 == 0) {
+- ItemStack item = itemFrame.getItem();
+- if (item.getItem() instanceof MapItem) {
+- Integer mapId = MapItem.getMapId(item);
+- MapItemSavedData savedData = MapItem.getSavedData(mapId, this.level);
+- if (savedData != null) {
+- for (ServerPlayer serverPlayer : this.level.players()) {
+- savedData.tickCarriedBy(serverPlayer, item);
+- Packet<?> updatePacket = savedData.getUpdatePacket(mapId, serverPlayer);
+- if (updatePacket != null) {
+- serverPlayer.connection.send(updatePacket);
++ Entity entity = this.entity;
++
++ if (entity instanceof ItemFrame) {
++ ItemFrame entityitemframe = (ItemFrame) entity;
++
++ if (true || this.tickCount % 10 == 0) { // CraftBukkit - Moved below, should always enter this block
++ ItemStack itemstack = entityitemframe.getItem();
++
++ if (this.tickCount % 10 == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks
++ Integer integer = MapItem.getMapId(itemstack);
++ MapItemSavedData worldmap = MapItem.getSavedData(integer, this.level);
++
++ if (worldmap != null) {
++ Iterator<ServerPlayerConnection> iterator = this.trackedPlayers.iterator(); // CraftBukkit
++
++ while (iterator.hasNext()) {
++ ServerPlayer entityplayer = iterator.next().getPlayer(); // CraftBukkit
++
++ worldmap.tickCarriedBy(entityplayer, itemstack);
++ Packet<?> packet = worldmap.getUpdatePacket(integer, entityplayer);
++
++ if (packet != null) {
++ entityplayer.connection.send(packet);
++ }
+ }
+ }
+ }
+@@ -206,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;
+ }
+ }
+@@ -229,7 +286,10 @@
+
+ public void sendPairingData(ServerPlayer player, Consumer<Packet<ClientGamePacketListener>> consumer) {
+ if (this.entity.isRemoved()) {
+- 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> addEntityPacket = this.entity.getAddEntityPacket();
+@@ -241,10 +303,13 @@
+
+ boolean flag = this.trackDelta;
+ if (this.entity instanceof LivingEntity) {
+- Collection<AttributeInstance> syncableAttributes = ((LivingEntity)this.entity).getAttributes().getSyncableAttributes();
+- if (!syncableAttributes.isEmpty()) {
+- consumer.accept(new ClientboundUpdateAttributesPacket(this.entity.getId(), syncableAttributes));
++ 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 (((LivingEntity)this.entity).isFallFlying()) {
+ flag = true;
+@@ -269,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));
+ }
+@@ -293,9 +381,15 @@
+ }
+
+ if (this.entity instanceof LivingEntity) {
+- Set<AttributeInstance> dirtyAttributes = ((LivingEntity)this.entity).getAttributes().getDirtyAttributes();
+- if (!dirtyAttributes.isEmpty()) {
+- this.broadcastAndSend(new ClientboundUpdateAttributesPacket(this.entity.getId(), dirtyAttributes));
++ 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));
+ }
+
+ dirtyAttributes.clear();
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/level/ServerLevel.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/level/ServerLevel.java.patch
new file mode 100644
index 0000000000..03bd29e1b2
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/level/ServerLevel.java.patch
@@ -0,0 +1,849 @@
+--- a/net/minecraft/server/level/ServerLevel.java
++++ b/net/minecraft/server/level/ServerLevel.java
+@@ -158,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 {
+ public static final BlockPos END_SPAWN_POINT = new BlockPos(100, 50, 0);
+@@ -171,9 +191,9 @@
+ final List<ServerPlayer> players = Lists.newArrayList();
+ private final ServerChunkCache chunkSource;
+ private final MinecraftServer server;
+- private final ServerLevelData serverLevelData;
+- final EntityTickList entityTickList = new EntityTickList();
+- private final PersistentEntitySectionManager<Entity> entityManager;
++ public final PrimaryLevelData serverLevelData; // CraftBukkit - type
++ final EntityTickList entityTickList;
++ public final PersistentEntitySectionManager<Entity> entityManager;
+ private final GameEventDispatcher gameEventDispatcher;
+ public boolean noSave;
+ private final SleepStatus sleepStatus;
+@@ -196,56 +216,73 @@
+ private final boolean tickTime;
+ private final RandomSequences randomSequences;
+
+- public ServerLevel(
+- MinecraftServer server,
+- Executor dispatcher,
+- LevelStorageSource.LevelStorageAccess levelStorageAccess,
+- ServerLevelData serverLevelData,
+- ResourceKey<Level> dimension,
+- LevelStem levelStem,
+- ChunkProgressListener progressListener,
+- boolean isDebug,
+- long biomeZoomSeed,
+- List<CustomSpawner> customSpawners,
+- boolean tickTime,
+- @Nullable RandomSequences randomSequences
+- ) {
+- super(
+- serverLevelData,
+- dimension,
+- server.registryAccess(),
+- levelStem.type(),
+- server::getProfiler,
+- false,
+- isDebug,
+- biomeZoomSeed,
+- server.getMaxChainedNeighborUpdates()
+- );
+- this.tickTime = tickTime;
+- this.server = server;
+- this.customSpawners = customSpawners;
+- this.serverLevelData = serverLevelData;
+- ChunkGenerator chunkGenerator = levelStem.generator();
+- boolean flag = server.forceSynchronousWrites();
+- DataFixer fixerUpper = server.getFixerUpper();
+- EntityPersistentStorage<Entity> entityPersistentStorage = new EntityStorage(
+- this, levelStorageAccess.getDimensionPath(dimension).resolve("entities"), fixerUpper, flag, server
+- );
+- this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entityPersistentStorage);
+- this.chunkSource = new ServerChunkCache(
+- this,
+- levelStorageAccess,
+- fixerUpper,
+- server.getStructureManager(),
+- dispatcher,
+- chunkGenerator,
+- server.getPlayerList().getViewDistance(),
+- server.getPlayerList().getSimulationDistance(),
+- flag,
+- progressListener,
+- this.entityManager::updateChunkStatus,
+- () -> server.overworld().getDataStorage()
+- );
++ // CraftBukkit start
++ public final LevelStorageSource.LevelStorageAccess convertable;
++ public final UUID uuid;
++
++ 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());
++ this.fluidTicks = new LevelTicks<>(this::isPositionTickingWithEntitiesLoaded, this.getProfilerSupplier());
++ this.navigatingMobs = new ObjectOpenHashSet();
++ this.blockEvents = new ObjectLinkedOpenHashSet();
++ this.blockEventsToReschedule = new ArrayList(64);
++ this.dragonParts = new Int2ObjectOpenHashMap();
++ this.tickTime = flag1;
++ this.server = minecraftserver;
++ this.customSpawners = list;
++ 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, convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), datafixer, flag2, minecraftserver);
++
++ this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.a(), entitypersistentstorage);
++ StructureTemplateManager structuretemplatemanager = minecraftserver.getStructureManager();
++ int j = minecraftserver.getPlayerList().getViewDistance();
++ int k = minecraftserver.getPlayerList().getSimulationDistance();
++ PersistentEntitySectionManager persistententitysectionmanager = this.entityManager;
++
++ Objects.requireNonNull(this.entityManager);
++ this.chunkSource = new ServerChunkCache(this, convertable_conversionsession, datafixer, structuretemplatemanager, executor, chunkgenerator, j, k, flag2, worldloadlistener, persistententitysectionmanager::updateChunkStatus, () -> {
++ return minecraftserver.overworld().getDataStorage();
++ });
+ this.chunkSource.getGeneratorState().ensureStructuresGenerated();
+ this.portalForcer = new PortalForcer(this);
+ this.updateSkyBrightness();
+@@ -256,31 +293,22 @@
+ serverLevelData.setGameType(server.getDefaultGameType());
+ }
+
+- long l = server.getWorldData().worldGenOptions().seed();
+- this.structureCheck = new StructureCheck(
+- this.chunkSource.chunkScanner(),
+- this.registryAccess(),
+- server.getStructureManager(),
+- dimension,
+- chunkGenerator,
+- this.chunkSource.randomState(),
+- this,
+- chunkGenerator.getBiomeSource(),
+- l,
+- fixerUpper
+- );
+- this.structureManager = new StructureManager(this, server.getWorldData().worldGenOptions(), this.structureCheck);
+- if (this.dimension() == Level.END && this.dimensionTypeRegistration().is(BuiltinDimensionTypes.END)) {
+- this.dragonFight = new EndDragonFight(this, l, server.getWorldData().endDragonFightData());
++ 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, 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;
+ }
+
+ this.sleepStatus = new SleepStatus();
+ this.gameEventDispatcher = new GameEventDispatcher(this);
+- this.randomSequences = Objects.requireNonNullElseGet(
+- randomSequences, () -> this.getDataStorage().computeIfAbsent(RandomSequences.factory(l), "random_sequences")
+- );
++ this.randomSequences = (RandomSequences) Objects.requireNonNullElseGet(randomsequences, () -> {
++ return (RandomSequences) this.getDataStorage().computeIfAbsent(RandomSequences.factory(l), "random_sequences");
++ });
++ this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit
+ }
+
+ @Deprecated
+@@ -318,14 +349,22 @@
+ this.advanceWeatherCycle();
+ }
+
+- int _int = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE);
+- if (this.sleepStatus.areEnoughSleeping(_int) && this.sleepStatus.areEnoughDeepSleeping(_int, this.players)) {
++ int i = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE);
++ 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)) {
+ long l = this.levelData.getDayTime() + 24000L;
+ this.setDayTime(l - l % 24000L);
+ }
+
+- this.wakeUpAllPlayers();
++ if (!event.isCancelled()) {
++ this.wakeUpAllPlayers();
++ }
++ // CraftBukkit end
+ if (this.getGameRules().getBoolean(GameRules.RULE_WEATHER_CYCLE) && this.isRaining()) {
+ this.resetWeatherCycle();
+ }
+@@ -359,8 +400,9 @@
+ }
+
+ this.handlingTick = false;
+- profiler.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();
+ }
+@@ -375,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)) {
+ profiler.push("checkDespawn");
+@@ -444,34 +495,37 @@
+ }
+
+ public void tickChunk(LevelChunk chunk, int randomTickSpeed) {
+- ChunkPos pos = chunk.getPos();
+- boolean isRaining = this.isRaining();
+- int minBlockX = pos.getMinBlockX();
+- int minBlockZ = pos.getMinBlockZ();
+- ProfilerFiller profiler = this.getProfiler();
+- profiler.push("thunder");
+- if (isRaining && this.isThundering() && this.random.nextInt(100000) == 0) {
+- BlockPos blockPos = this.findLightningTargetAround(this.getBlockRandomPos(minBlockX, 0, minBlockZ, 15));
+- if (this.isRainingAt(blockPos)) {
+- DifficultyInstance currentDifficultyAt = this.getCurrentDifficultyAt(blockPos);
+- boolean flag = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)
+- && this.random.nextDouble() < (double)currentDifficultyAt.getEffectiveDifficulty() * 0.01
+- && !this.getBlockState(blockPos.below()).is(Blocks.LIGHTNING_ROD);
+- if (flag) {
+- 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);
++ ChunkPos chunkcoordintpair = chunk.getPos();
++ boolean flag = this.isRaining();
++ int j = chunkcoordintpair.getMinBlockX();
++ int k = chunkcoordintpair.getMinBlockZ();
++ ProfilerFiller gameprofilerfiller = this.getProfiler();
++
++ gameprofilerfiller.push("thunder");
++ if (flag && this.isThundering() && this.random.nextInt(100000) == 0) {
++ BlockPos blockposition = this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15));
++
++ if (this.isRainingAt(blockposition)) {
++ DifficultyInstance difficultydamagescaler = this.getCurrentDifficultyAt(blockposition);
++ boolean flag1 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.getEffectiveDifficulty() * 0.01D && !this.getBlockState(blockposition.below()).is(Blocks.LIGHTNING_ROD);
++
++ if (flag1) {
++ SkeletonHorse entityhorseskeleton = (SkeletonHorse) EntityType.SKELETON_HORSE.create(this);
++
++ 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 = EntityType.LIGHTNING_BOLT.create(this);
+- if (lightningBolt != null) {
+- lightningBolt.moveTo(Vec3.atBottomCenterOf(blockPos));
+- lightningBolt.setVisualOnly(flag);
+- this.addFreshEntity(lightningBolt);
++ LightningBolt entitylightning = (LightningBolt) EntityType.LIGHTNING_BOLT.create(this);
++
++ if (entitylightning != null) {
++ entitylightning.moveTo(Vec3.atBottomCenterOf(blockposition));
++ entitylightning.setVisualOnly(flag1);
++ this.strikeLightning(entitylightning, org.bukkit.event.weather.LightningStrikeEvent.Cause.WEATHER); // CraftBukkit
+ }
+ }
+ }
+@@ -519,27 +575,32 @@
+ }
+
+ @VisibleForTesting
+- public void tickPrecipitation(BlockPos blockPos) {
+- BlockPos heightmapPos = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, blockPos);
+- BlockPos blockPos1 = heightmapPos.below();
+- Biome biome = this.getBiome(heightmapPos).value();
+- if (biome.shouldFreeze(this, blockPos1)) {
+- this.setBlockAndUpdate(blockPos1, Blocks.ICE.defaultBlockState());
++ public void tickPrecipitation(BlockPos blockposition) {
++ BlockPos blockposition1 = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, blockposition);
++ BlockPos blockposition2 = blockposition1.below();
++ Biome biomebase = (Biome) this.getBiome(blockposition1).value();
++
++ if (biomebase.shouldFreeze(this, blockposition2)) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition2, Blocks.ICE.defaultBlockState(), null); // CraftBukkit
+ }
+
+ if (this.isRaining()) {
+- int _int = this.getGameRules().getInt(GameRules.RULE_SNOW_ACCUMULATION_HEIGHT);
+- if (_int > 0 && biome.shouldSnow(this, heightmapPos)) {
+- BlockState blockState = this.getBlockState(heightmapPos);
+- if (blockState.is(Blocks.SNOW)) {
+- int i = blockState.getValue(SnowLayerBlock.LAYERS);
+- if (i < Math.min(_int, 8)) {
+- BlockState blockState1 = blockState.setValue(SnowLayerBlock.LAYERS, Integer.valueOf(i + 1));
+- Block.pushEntitiesUp(blockState, blockState1, this, heightmapPos);
+- this.setBlockAndUpdate(heightmapPos, blockState1);
++ int i = this.getGameRules().getInt(GameRules.RULE_SNOW_ACCUMULATION_HEIGHT);
++
++ if (i > 0 && biomebase.shouldSnow(this, blockposition1)) {
++ IBlockData iblockdata = this.getBlockState(blockposition1);
++
++ if (iblockdata.is(Blocks.SNOW)) {
++ int j = (Integer) iblockdata.getValue(SnowLayerBlock.LAYERS);
++
++ if (j < Math.min(i, 8)) {
++ IBlockData iblockdata1 = (IBlockData) iblockdata.setValue(SnowLayerBlock.LAYERS, j + 1);
++
++ Block.pushEntitiesUp(iblockdata, iblockdata1, this, blockposition1);
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition1, iblockdata1, null); // CraftBukkit
+ }
+ } else {
+- this.setBlockAndUpdate(heightmapPos, Blocks.SNOW.defaultBlockState());
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition1, Blocks.SNOW.defaultBlockState(), null); // CraftBukkit
+ }
+ }
+
+@@ -684,6 +761,7 @@
+ this.rainLevel = Mth.clamp(this.rainLevel, 0.0F, 1.0F);
+ }
+
++ /* CraftBukkit start
+ if (this.oRainLevel != this.rainLevel) {
+ this.server
+ .getPlayerList()
+@@ -706,14 +780,48 @@
+ this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, this.rainLevel));
+ this.server.getPlayerList().broadcastAll(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, this.thunderLevel));
+ }
++ // */
++ for (int idx = 0; idx < this.players.size(); ++idx) {
++ if (((ServerPlayer) this.players.get(idx)).level() == this) {
++ ((ServerPlayer) this.players.get(idx)).tickWeather();
++ }
++ }
++
++ 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() {
+@@ -741,6 +856,7 @@
+ this.getProfiler().push(() -> BuiltInRegistries.ENTITY_TYPE.getKey(entity.getType()).toString());
+ profiler.incrementCounter("tickNonPassenger");
+ entity.tick();
++ entity.postTick(); // CraftBukkit
+ this.getProfiler().pop();
+
+ for (Entity entity1 : entity.getPassengers()) {
+@@ -760,8 +875,21 @@
+ passengerEntity.rideTick();
+ profiler.pop();
+
+- for (Entity entity : passengerEntity.getPassengers()) {
+- this.tickPassenger(passengerEntity, entity);
++ gameprofilerfiller.push(() -> {
++ return BuiltInRegistries.ENTITY_TYPE.getKey(passengerEntity.getType()).toString();
++ });
++ gameprofilerfiller.incrementCounter("tickPassenger");
++ passengerEntity.rideTick();
++ passengerEntity.postTick(); // CraftBukkit
++ gameprofilerfiller.pop();
++ Iterator iterator = passengerEntity.getPassengers().iterator();
++
++ while (iterator.hasNext()) {
++ Entity entity2 = (Entity) iterator.next();
++
++ this.tickPassenger(passengerEntity, entity2);
++ }
++
+ }
+ }
+ }
+@@ -774,6 +905,7 @@
+ public void save(@Nullable ProgressListener progress, boolean flush, boolean skipSave) {
+ ServerChunkCache chunkSource = this.getChunkSource();
+ if (!skipSave) {
++ org.bukkit.Bukkit.getPluginManager().callEvent(new org.bukkit.event.world.WorldSaveEvent(getWorld())); // CraftBukkit
+ if (progress != null) {
+ progress.progressStartNoAbort(Component.translatable("menu.savingLevel"));
+ }
+@@ -790,11 +923,19 @@
+ this.entityManager.autoSave();
+ }
+ }
++
++ // 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();
+@@ -854,17 +1000,39 @@
+
+ @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 addDuringTeleport(Entity entity, CreatureSpawnEvent.SpawnReason reason) {
++ this.addEntity(entity, reason);
++ // CraftBukkit end
++ }
++
+ public void addDuringCommandTeleport(ServerPlayer player) {
+ this.addPlayer(player);
+ }
+@@ -892,20 +1061,37 @@
+ this.entityManager.addNewEntity(player);
+ }
+
+- private boolean addEntity(Entity entity) {
++ // CraftBukkit start
++ private boolean addEntity(Entity entity, CreatureSpawnEvent.SpawnReason spawnReason) {
+ if (entity.isRemoved()) {
+- 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) {
+- if (entity.getSelfAndPassengers().map(Entity::getUUID).anyMatch(this.entityManager::isLoaded)) {
++ // 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;
+ }
+ }
+@@ -919,16 +1105,49 @@
+ player.remove(reason);
+ }
+
++ // 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
+ public void destroyBlockProgress(int breakerId, BlockPos pos, int progress) {
+- for (ServerPlayer serverPlayer : this.server.getPlayerList().getPlayers()) {
+- if (serverPlayer != null && serverPlayer.level() == this && serverPlayer.getId() != breakerId) {
+- double d = (double)pos.getX() - serverPlayer.getX();
+- double d1 = (double)pos.getY() - serverPlayer.getY();
+- double d2 = (double)pos.getZ() - serverPlayer.getZ();
+- if (d * d + d1 * d1 + d2 * d2 < 1024.0) {
+- serverPlayer.connection.send(new ClientboundBlockDestructionPacket(breakerId, pos, progress));
++ 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 entityplayer = (ServerPlayer) iterator.next();
++
++ if (entityplayer != null && entityplayer.level() == this && entityplayer.getId() != breakerId) {
++ double d0 = (double) pos.getX() - entityplayer.getX();
++ double d1 = (double) pos.getY() - entityplayer.getY();
++ double d2 = (double) pos.getZ() - entityplayer.getZ();
++
++ // CraftBukkit start
++ if (entityhuman != null && !entityplayer.getBukkitEntity().canSee(entityhuman.getBukkitEntity())) {
++ continue;
+ }
++ // CraftBukkit end
++
++ if (d0 * d0 + d1 * d1 + d2 * d2 < 1024.0D) {
++ entityplayer.connection.send(new ClientboundBlockDestructionPacket(breakerId, pos, progress));
++ }
+ }
+ }
+ }
+@@ -1011,11 +1199,28 @@
+ if (Shapes.joinIsNotEmpty(collisionShape, collisionShape1, BooleanOp.NOT_SAME)) {
+ List<PathNavigation> list = new ObjectArrayList<>();
+
+- for (Mob mob : this.navigatingMobs) {
+- PathNavigation navigation = mob.getNavigation();
+- if (navigation.shouldRecomputePath(pos)) {
+- list.add(navigation);
++ if (Shapes.joinIsNotEmpty(voxelshape, voxelshape1, BooleanOp.NOT_SAME)) {
++ List<PathNavigation> list = new ObjectArrayList();
++ Iterator iterator = this.navigatingMobs.iterator();
++
++ while (iterator.hasNext()) {
++ // 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 (navigationabstract.shouldRecomputePath(pos)) {
++ list.add(navigationabstract);
++ }
+ }
+
+ try {
+@@ -1066,23 +1275,14 @@
+ }
+
+ @Override
+- public Explosion explode(
+- @Nullable Entity entity,
+- @Nullable DamageSource damageSource,
+- @Nullable ExplosionDamageCalculator explosionDamageCalculator,
+- double d,
+- double d1,
+- double d2,
+- float f,
+- boolean flag,
+- Level.ExplosionInteraction explosionInteraction,
+- ParticleOptions particleOptions,
+- ParticleOptions particleOptions1,
+- SoundEvent soundEvent
+- ) {
+- Explosion explosion = this.explode(
+- entity, damageSource, explosionDamageCalculator, d, d1, d2, f, flag, 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();
+ }
+@@ -1171,18 +1353,22 @@
+ return this.server.getStructureManager();
+ }
+
+- public <T extends ParticleOptions> int sendParticles(
+- T type, double posX, double posY, double posZ, int particleCount, double xOffset, double yOffset, double zOffset, double speed
+- ) {
+- ClientboundLevelParticlesPacket clientboundLevelParticlesPacket = new ClientboundLevelParticlesPacket(
+- type, false, posX, posY, posZ, (float)xOffset, (float)yOffset, (float)zOffset, (float)speed, particleCount
+- );
+- int i = 0;
++ 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);
++ }
+
+- for (int i1 = 0; i1 < this.players.size(); i1++) {
+- ServerPlayer serverPlayer = this.players.get(i1);
+- if (this.sendParticles(serverPlayer, false, posX, posY, posZ, clientboundLevelParticlesPacket)) {
+- i++;
++ 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 entityplayer = (ServerPlayer) this.players.get(k);
++ if (sender != null && !entityplayer.getBukkitEntity().canSee(sender.getBukkitEntity())) continue; // CraftBukkit
++
++ if (this.sendParticles(entityplayer, force, d0, d1, d2, packetplayoutworldparticles)) { // CraftBukkit
++ ++j;
+ }
+ }
+
+@@ -1242,7 +1418,7 @@
+
+ @Nullable
+ public BlockPos findNearestMapStructure(TagKey<Structure> structureTag, BlockPos pos, int radius, boolean skipExistingChunks) {
+- if (!this.server.getWorldData().worldGenOptions().generateStructures()) {
++ if (!this.serverLevelData.worldGenOptions().generateStructures()) { // CraftBukkit
+ return null;
+ } else {
+ Optional<HolderSet.Named<Structure>> tag = this.registryAccess().registryOrThrow(Registries.STRUCTURE).getTag(structureTag);
+@@ -1287,11 +1460,22 @@
+ @Nullable
+ @Override
+ public MapItemSavedData getMapData(String mapName) {
+- return this.getServer().overworld().getDataStorage().get(MapItemSavedData.factory(), 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
+ 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);
+ }
+
+@@ -1491,6 +1773,11 @@
+ @Override
+ public void blockUpdated(BlockPos pos, Block block) {
+ if (!this.isDebug()) {
++ // CraftBukkit start
++ if (populating) {
++ return;
++ }
++ // CraftBukkit end
+ this.updateNeighborsAt(pos, block);
+ }
+ }
+@@ -1510,12 +1797,12 @@
+ }
+
+ public boolean isFlat() {
+- return this.server.getWorldData().isFlatWorld();
++ return this.serverLevelData.isFlatWorld(); // CraftBukkit
+ }
+
+ @Override
+ public long getSeed() {
+- return this.server.getWorldData().worldGenOptions().seed();
++ return this.serverLevelData.worldGenOptions().seed(); // CraftBukkit
+ }
+
+ @Nullable
+@@ -1565,16 +1845,35 @@
+ }
+
+ public static void makeObsidianPlatform(ServerLevel serverLevel) {
+- BlockPos blockPos = END_SPAWN_POINT;
+- int x = blockPos.getX();
+- int i = blockPos.getY() - 2;
+- int z = blockPos.getZ();
+- BlockPos.betweenClosed(x - 2, i + 1, z - 2, x + 2, i + 3, z + 2)
+- .forEach(blockPos1 -> serverLevel.setBlockAndUpdate(blockPos1, Blocks.AIR.defaultBlockState()));
+- BlockPos.betweenClosed(x - 2, i, z - 2, x + 2, i, z + 2)
+- .forEach(blockPos1 -> serverLevel.setBlockAndUpdate(blockPos1, Blocks.OBSIDIAN.defaultBlockState()));
++ // CraftBukkit start
++ ServerLevel.makeObsidianPlatform(serverLevel, null);
+ }
+
++ 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((blockposition1) -> {
++ blockList.setBlock(blockposition1, Blocks.OBSIDIAN.defaultBlockState(), 3);
++ });
++ 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
+ protected LevelEntityGetter<Entity> getEntities() {
+ return this.entityManager.getEntityGetter();
+@@ -1693,6 +2005,8 @@
+ }
+
+ entity.updateDynamicGameEventListener(DynamicGameEventListener::add);
++ entity.inWorld = true; // CraftBukkit - Mark entity as in world
++ entity.valid = true; // CraftBukkit
+ }
+
+ @Override
+@@ -1721,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-vineflower-stripped/net/minecraft/server/level/ServerPlayer.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/level/ServerPlayer.java.patch
new file mode 100644
index 0000000000..73b5562e43
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/level/ServerPlayer.java.patch
@@ -0,0 +1,1312 @@
+--- a/net/minecraft/server/level/ServerPlayer.java
++++ b/net/minecraft/server/level/ServerPlayer.java
+@@ -152,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 {
+ private static final Logger LOGGER = LogUtils.getLogger();
+@@ -187,8 +220,8 @@
+ private Vec3 levitationStartPos;
+ private int levitationStartTime;
+ private boolean disconnected;
+- private int requestedViewDistance = 2;
+- private String language = "en_us";
++ private int requestedViewDistance;
++ public String language = "en_us"; // CraftBukkit - default
+ @Nullable
+ private Vec3 startingToFallPosition;
+ @Nullable
+@@ -212,8 +246,41 @@
+ ServerPlayer.this.connection
+ .send(new ClientboundContainerSetContentPacket(container.containerId, container.incrementStateId(), items, carriedItem));
+
+- for (int i = 0; i < initialData.length; i++) {
+- this.broadcastDataValue(container, i, initialData[i]);
++ // 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();
++ this.recipeBook = new ServerRecipeBook();
++ this.requestedViewDistance = 2;
++ this.language = "en_us";
++ this.lastSectionPos = SectionPos.of(0, 0, 0);
++ this.chunkTrackingView = ChunkTrackingView.EMPTY;
++ this.respawnDimension = Level.OVERWORLD;
++ this.wardenSpawnTracker = new WardenSpawnTracker(0, 0, 0);
++ this.containerSynchronizer = new ContainerSynchronizer() {
++ @Override
++ public void sendInitialData(AbstractContainerMenu container, NonNullList<ItemStack> items, ItemStack carriedItem, int[] initialData) {
++ ServerPlayer.this.connection.send(new ClientboundContainerSetContentPacket(container.containerId, container.incrementStateId(), items, carriedItem));
++
++ for (int i = 0; i < initialData.length; ++i) {
++ this.broadcastDataValue(container, i, initialData[i]);
++ }
++
+ }
+ }
+
+@@ -247,8 +315,57 @@
+ }
+ }
+
+- @Override
+- public void dataChanged(AbstractContainerMenu containerMenu, int dataSlotIndex, int value) {
++ @Override
++ public void dataChanged(AbstractContainerMenu containerMenu, int dataSlotIndex, int value) {}
++ };
++ this.textFilter = minecraftserver.createTextFilterForPlayer(this);
++ this.gameMode = minecraftserver.createGameModeForPlayer(this);
++ this.server = minecraftserver;
++ this.stats = minecraftserver.getPlayerList().getPlayerStats(this);
++ this.advancements = minecraftserver.getPlayerList().getPlayerAdvancements(this);
++ this.setMaxUpStep(1.0F);
++ this.fudgeSpawnLocation(worldserver);
++ this.updateOptions(clientinformation);
++
++ // CraftBukkit start
++ this.displayName = this.getScoreboardName();
++ this.bukkitPickUpLoot = true;
++ this.maxHealthCache = this.getMaxHealth();
++ }
++
++ // Yes, this doesn't match Vanilla, but it's the best we can do for now.
++ // If this is an issue, PRs are welcome
++ public final BlockPos getSpawnPoint(ServerLevel worldserver) {
++ BlockPos blockposition = worldserver.getSharedSpawnPos();
++
++ if (worldserver.dimensionType().hasSkyLight() && worldserver.serverLevelData.getGameType() != GameType.ADVENTURE) {
++ int i = Math.max(0, this.server.getSpawnRadius(worldserver));
++ int j = Mth.floor(worldserver.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(worldserver, blockposition.getX() + j2 - i, blockposition.getZ() + k2 - i);
++
++ if (blockposition1 != null) {
++ return blockposition1;
++ }
++ }
+ }
+ };
+ @Nullable
+@@ -267,14 +370,17 @@
+ this.fudgeSpawnLocation(level);
+ this.updateOptions(clientInformation);
+ }
++ // CraftBukkit end
+
+ private void fudgeSpawnLocation(ServerLevel level) {
+- BlockPos sharedSpawnPos = level.getSharedSpawnPos();
+- if (level.dimensionType().hasSkyLight() && level.getServer().getWorldData().getGameType() != GameType.ADVENTURE) {
+- int max = Math.max(0, this.server.getSpawnRadius(level));
+- int floor = Mth.floor(level.getWorldBorder().getDistanceToBorder((double)sharedSpawnPos.getX(), (double)sharedSpawnPos.getZ()));
+- if (floor < max) {
+- max = floor;
++ 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 (floor <= 1) {
+@@ -333,11 +443,20 @@
+ if (compound.contains("recipeBook", 10)) {
+ this.recipeBook.fromNbt(compound.getCompound("recipeBook"), this.server.getRecipeManager());
+ }
++ this.getBukkitEntity().readExtraData(compound); // CraftBukkit
+
+ if (this.isSleeping()) {
+ this.stopSleeping();
+ }
+
++ // 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");
+@@ -368,17 +493,32 @@
+ compound.put("enteredNetherPosition", compoundTag);
+ }
+
+- Entity rootVehicle = this.getRootVehicle();
+- Entity vehicle = this.getVehicle();
+- if (vehicle != null && rootVehicle != this && rootVehicle.hasExactlyOnePlayerPassenger()) {
+- CompoundTag compoundTag1 = new CompoundTag();
+- CompoundTag compoundTag2 = new CompoundTag();
+- rootVehicle.save(compoundTag2);
+- compoundTag1.putUUID("Attach", vehicle.getUUID());
+- compoundTag1.put("Entity", compoundTag2);
+- compound.put("RootVehicle", compoundTag1);
++ Entity entity = this.getRootVehicle();
++ Entity entity1 = this.getVehicle();
++
++ // 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;
++ }
++ }
+ }
+
++ 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);
++ }
++
+ compound.put("recipeBook", this.recipeBook.toNbt());
+ compound.putString("Dimension", this.level().dimension().location().toString());
+ if (this.respawnPosition != null) {
+@@ -392,8 +534,33 @@
+ .resultOrPartial(LOGGER::error)
+ .ifPresent(tag -> compound.put("SpawnDimension", tag));
+ }
++ this.getBukkitEntity().setExtraData(compound); // CraftBukkit
++
+ }
+
++ // 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;
+@@ -451,6 +619,11 @@
+
+ @Override
+ public void tick() {
++ // CraftBukkit start
++ if (this.joining) {
++ this.joining = false;
++ }
++ // CraftBukkit end
+ this.gameMode.tick();
+ this.wardenSpawnTracker.tick();
+ this.spawnInvulnerableTime--;
+@@ -503,10 +679,8 @@
+ }
+ }
+
+- 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()));
++ if (this.getHealth() != this.lastSentHealth || this.lastSentFood != this.foodData.getFoodLevel() || this.foodData.getSaturationLevel() == 0.0F != this.lastFoodSaturationZero) {
++ 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;
+@@ -537,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));
+@@ -550,11 +730,27 @@
+ if (this.tickCount % 20 == 0) {
+ CriteriaTriggers.LOCATION.trigger(this);
+ }
+- } catch (Throwable var4) {
+- CrashReport crashReport = CrashReport.forThrowable(var4, "Ticking player");
+- CrashReportCategory crashReportCategory = crashReport.addCategory("Player being ticked");
+- this.fillCrashReportCategory(crashReportCategory);
+- throw new ReportedException(crashReport);
++
++ // 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 crashreportsystemdetails = crashreport.addCategory("Player being ticked");
++
++ this.fillCrashReportCategory(crashreportsystemdetails);
++ throw new ReportedException(crashreport);
+ }
+ }
+
+@@ -589,38 +787,28 @@
+ }
+
+ private void updateScoreForCriteria(ObjectiveCriteria criteria, int points) {
+- this.getScoreboard().forAllObjectives(criteria, this, scoreAccess -> scoreAccess.set(points));
++ // CraftBukkit - Use our scores instead
++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(criteria, this, (scoreaccess) -> {
++ scoreaccess.set(points);
++ });
+ }
+
+ @Override
+ public void die(DamageSource cause) {
+ this.gameEvent(GameEvent.ENTITY_DIE);
+- boolean _boolean = this.level().getGameRules().getBoolean(GameRules.RULE_SHOWDEATHMESSAGES);
+- if (_boolean) {
+- Component deathMessage = this.getCombatTracker().getDeathMessage();
+- this.connection
+- .send(
+- new ClientboundPlayerCombatKillPacket(this.getId(), deathMessage),
+- PacketSendListener.exceptionallySend(
+- () -> {
+- int i = 256;
+- String string = deathMessage.getString(256);
+- Component component = Component.translatable(
+- "death.attack.message_too_long", Component.literal(string).withStyle(ChatFormatting.YELLOW)
+- );
+- Component component1 = Component.translatable("death.attack.even_more_magic", this.getDisplayName())
+- .withStyle(style -> style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, component)));
+- return new ClientboundPlayerCombatKillPacket(this.getId(), component1);
+- }
+- )
+- );
+- Team team = this.getTeam();
+- if (team == null || team.getDeathMessageVisibility() == Team.Visibility.ALWAYS) {
+- this.server.getPlayerList().broadcastSystemMessage(deathMessage, false);
+- } else if (team.getDeathMessageVisibility() == Team.Visibility.HIDE_FOR_OTHER_TEAMS) {
+- this.server.getPlayerList().broadcastSystemToTeam(this, deathMessage);
+- } else if (team.getDeathMessageVisibility() == Team.Visibility.HIDE_FOR_OWN_TEAM) {
+- this.server.getPlayerList().broadcastSystemToAllExceptTeam(this, deathMessage);
++ 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 (!keepInventory) {
++ for (ItemStack item : this.getInventory().getContents()) {
++ if (!item.isEmpty() && !EnchantmentHelper.hasVanishingCurse(item)) {
++ loot.add(CraftItemStack.asCraftMirror(item));
++ }
+ }
+ } else {
+ this.connection.send(new ClientboundPlayerCombatKillPacket(this.getId(), CommonComponents.EMPTY));
+@@ -635,12 +875,17 @@
+ this.dropAllDeathLoot(cause);
+ }
+
+- this.getScoreboard().forAllObjectives(ObjectiveCriteria.DEATH_COUNT, this, ScoreAccess::increment);
+- LivingEntity killCredit = this.getKillCredit();
+- if (killCredit != null) {
+- this.awardStat(Stats.ENTITY_KILLED_BY.get(killCredit.getType()));
+- killCredit.awardKillScore(this, this.deathScore, cause);
+- this.createWitherRose(killCredit);
++ this.setCamera(this); // Remove spectated target
++ // CraftBukkit end
++
++ // 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);
+@@ -668,10 +914,12 @@
+ if (killed != this) {
+ super.awardKillScore(killed, scoreValue, damageSource);
+ this.increaseScore(scoreValue);
+- this.getScoreboard().forAllObjectives(ObjectiveCriteria.KILL_COUNT_ALL, this, ScoreAccess::increment);
++ // 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);
+ }
+@@ -682,12 +930,15 @@
+ }
+ }
+
+- private void handleTeamKill(ScoreHolder scoreHolder, ScoreHolder scoreHolder1, ObjectiveCriteria[] objectiveCriterias) {
+- PlayerTeam playersTeam = this.getScoreboard().getPlayersTeam(scoreHolder1.getScoreboardName());
+- if (playersTeam != null) {
+- int id = playersTeam.getColor().getId();
+- if (id >= 0 && id < objectiveCriterias.length) {
+- this.getScoreboard().forAllObjectives(objectiveCriterias[id], scoreHolder, ScoreAccess::increment);
++ private void handleTeamKill(ScoreHolder scoreholder, ScoreHolder scoreholder1, ObjectiveCriteria[] aiscoreboardcriteria) {
++ PlayerTeam scoreboardteam = this.getScoreboard().getPlayersTeam(scoreholder1.getScoreboardName());
++
++ if (scoreboardteam != null) {
++ int i = scoreboardteam.getColor().getId();
++
++ if (i >= 0 && i < aiscoreboardcriteria.length) {
++ // CraftBukkit - Get our scores instead
++ this.level().getCraftServer().getScoreboardManager().forAllObjectives(aiscoreboardcriteria[i], scoreholder, ScoreAccess::increment);
+ }
+ }
+ }
+@@ -721,16 +988,20 @@
+ }
+
+ private boolean isPvpAllowed() {
+- return this.server.isPvpAllowed();
++ // CraftBukkit - this.server.isPvpAllowed() -> this.world.pvpMode
++ return this.level().pvpMode;
+ }
+
+ @Nullable
+ @Override
+ protected PortalInfo findDimensionEntryPoint(ServerLevel destination) {
+- PortalInfo portalInfo = super.findDimensionEntryPoint(destination);
+- if (portalInfo != null && this.level().dimension() == Level.OVERWORLD && destination.dimension() == Level.END) {
+- Vec3 vec3 = portalInfo.pos.add(0.0, -1.0, 0.0);
+- return new PortalInfo(vec3, Vec3.ZERO, 90.0F, 0.0F);
++ PortalInfo shapedetectorshape = super.findDimensionEntryPoint(destination);
++ destination = (shapedetectorshape == null) ? destination : shapedetectorshape.world; // CraftBukkit
++
++ 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(vec3d, Vec3.ZERO, 90.0F, 0.0F, destination, shapedetectorshape.portalEventInfo); // CraftBukkit
+ } else {
+ return portalInfo;
+ }
+@@ -739,10 +1010,20 @@
+ @Nullable
+ @Override
+ public Entity changeDimension(ServerLevel server) {
+- this.isChangingDimension = true;
+- ServerLevel serverLevel = this.serverLevel();
+- ResourceKey<Level> resourceKey = serverLevel.dimension();
+- if (resourceKey == Level.END && server.dimension() == Level.OVERWORLD) {
++ // CraftBukkit start
++ return changeDimension(server, TeleportCause.UNKNOWN);
++ }
++
++ @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) {
+@@ -753,26 +1034,61 @@
+
+ return this;
+ } else {
+- LevelData levelData = server.getLevelData();
+- this.connection.send(new ClientboundRespawnPacket(this.createCommonSpawnInfo(server), (byte)3));
+- this.connection.send(new ClientboundChangeDifficultyPacket(levelData.getDifficulty(), levelData.isDifficultyLocked()));
+- PlayerList playerList = this.server.getPlayerList();
+- playerList.sendPlayerPermissionLevel(this);
+- serverLevel.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION);
++ // CraftBukkit start
++ /*
++ WorldData worlddata = worldserver.getLevelData();
++
++ this.connection.send(new PacketPlayOutRespawn(this.createCommonSpawnInfo(worldserver), (byte) 3));
++ this.connection.send(new PacketPlayOutServerDifficulty(worlddata.getDifficulty(), worlddata.isDifficultyLocked()));
++ PlayerList playerlist = this.server.getPlayerList();
++
++ playerlist.sendPlayerPermissionLevel(this);
++ worldserver1.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION);
+ this.unsetRemoved();
+- PortalInfo portalInfo = this.findDimensionEntryPoint(server);
+- if (portalInfo != null) {
+- serverLevel.getProfiler().push("moving");
+- if (resourceKey == Level.OVERWORLD && server.dimension() == Level.NETHER) {
++ */
++ // CraftBukkit end
++ PortalInfo shapedetectorshape = this.findDimensionEntryPoint(worldserver);
++
++ 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 (server.dimension() == Level.END) {
+- this.createEndPlatform(server, 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
+
+- serverLevel.getProfiler().pop();
+- serverLevel.getProfiler().push("placing");
+- this.setServerLevel(server);
+- 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();
+ server.addDuringPortalTeleport(this);
+ serverLevel.getProfiler().pop();
+@@ -789,50 +1108,90 @@
+ 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;
+ }
+ }
+
++ // 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 mutableBlockPos = pos.mutable();
++ 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 i1 = -2; i1 <= 2; i1++) {
+- for (int i2 = -1; i2 < 3; i2++) {
+- BlockState blockState = i2 == -1 ? Blocks.OBSIDIAN.defaultBlockState() : Blocks.AIR.defaultBlockState();
+- level.setBlockAndUpdate(mutableBlockPos.set(pos).move(i1, i2, i), blockState);
++ for (int i = -2; i <= 2; ++i) {
++ for (int j = -2; j <= 2; ++j) {
++ for (int k = -1; k < 3; ++k) {
++ IBlockData iblockdata = k == -1 ? Blocks.OBSIDIAN.defaultBlockState() : Blocks.AIR.defaultBlockState();
++
++ 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
+- protected Optional<BlockUtil.FoundRectangle> getExitPortal(ServerLevel destination, BlockPos findFrom, boolean isToNether, WorldBorder worldBorder) {
+- Optional<BlockUtil.FoundRectangle> optional = super.getExitPortal(destination, findFrom, isToNether, worldBorder);
+- if (optional.isPresent()) {
++ 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() || !canCreatePortal) { // CraftBukkit
+ return optional;
+ } else {
+- Direction.Axis axis = this.level().getBlockState(this.portalEntrancePos).getOptionalValue(NetherPortalBlock.AXIS).orElse(Direction.Axis.X);
+- Optional<BlockUtil.FoundRectangle> optional1 = destination.getPortalForcer().createPortal(findFrom, 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()) {
+- 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;
+ }
+ }
+
+- private void triggerDimensionChangeTriggers(ServerLevel level) {
+- ResourceKey<Level> resourceKey = level.dimension();
+- ResourceKey<Level> resourceKey1 = this.level().dimension();
+- CriteriaTriggers.CHANGED_DIMENSION.trigger(this, resourceKey, resourceKey1);
+- if (resourceKey == Level.NETHER && resourceKey1 == Level.OVERWORLD && this.enteredNetherPosition != null) {
++ public void triggerDimensionChangeTriggers(ServerLevel level) {
++ ResourceKey<Level> resourcekey = level.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, 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;
+ }
+ }
+@@ -848,34 +1208,31 @@
+ this.containerMenu.broadcastChanges();
+ }
+
+- @Override
+- public Either<Player.BedSleepingProblem, Unit> startSleepInBed(BlockPos at) {
+- Direction direction = this.level().getBlockState(at).getValue(HorizontalDirectionalBlock.FACING);
+- if (this.isSleeping() || !this.isAlive()) {
+- return Either.left(Player.BedSleepingProblem.OTHER_PROBLEM);
+- } else if (!this.level().dimensionType().natural()) {
+- return Either.left(Player.BedSleepingProblem.NOT_POSSIBLE_HERE);
+- } else if (!this.bedInRange(at, direction)) {
+- return Either.left(Player.BedSleepingProblem.TOO_FAR_AWAY);
+- } else if (this.bedBlocked(at, direction)) {
+- return Either.left(Player.BedSleepingProblem.OBSTRUCTED);
+- } else {
+- this.setRespawnPosition(this.level().dimension(), at, this.getYRot(), false, true);
+- if (this.level().isDay()) {
+- return Either.left(Player.BedSleepingProblem.NOT_POSSIBLE_NOW);
++ // 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() || !this.level().dimensionType().bedWorks()) {
++ return Either.left(Player.BedSleepingProblem.NOT_POSSIBLE_HERE);
++ } else if (!this.bedInRange(blockposition, enumdirection)) {
++ return Either.left(Player.BedSleepingProblem.TOO_FAR_AWAY);
++ } else if (this.bedBlocked(blockposition, enumdirection)) {
++ return Either.left(Player.BedSleepingProblem.OBSTRUCTED);
+ } else {
+- if (!this.isCreative()) {
+- double d = 8.0;
+- double d1 = 5.0;
+- Vec3 vec3 = Vec3.atBottomCenterOf(at);
+- List<Monster> entitiesOfClass = this.level()
+- .getEntitiesOfClass(
+- Monster.class,
+- new AABB(vec3.x() - 8.0, vec3.y() - 5.0, vec3.z() - 8.0, vec3.x() + 8.0, vec3.y() + 5.0, vec3.z() + 8.0),
+- monster -> monster.isPreventingPlayerRest(this)
+- );
+- if (!entitiesOfClass.isEmpty()) {
+- return Either.left(Player.BedSleepingProblem.NOT_SAFE);
++ 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 {
++ if (!this.isCreative()) {
++ double d0 = 8.0D;
++ double d1 = 5.0D;
++ Vec3 vec3d = Vec3.atBottomCenterOf(blockposition);
++ List<Monster> list = this.level().getEntitiesOfClass(Monster.class, new AABB(vec3d.x() - 8.0D, vec3d.y() - 5.0D, vec3d.z() - 8.0D, vec3d.x() + 8.0D, vec3d.y() + 5.0D, vec3d.z() + 8.0D), (entitymonster) -> {
++ return entitymonster.isPreventingPlayerRest(this);
++ });
++
++ if (!list.isEmpty()) {
++ return Either.left(Player.BedSleepingProblem.NOT_SAFE);
++ }
+ }
+ }
+
+@@ -891,6 +1278,7 @@
+ return either;
+ }
+ }
++ // CraftBukkit end
+ }
+
+ @Override
+@@ -915,13 +1305,31 @@
+
+ @Override
+ 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(wakeImmediately, updateLevelForSleepingPlayers);
+ 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
+ }
+ }
+
+@@ -968,8 +1379,9 @@
+ this.connection.send(new ClientboundOpenSignEditorPacket(signEntity.getBlockPos(), isFrontText));
+ }
+
+- private void nextContainerCounter() {
++ public int nextContainerCounter() { // CraftBukkit - void -> int
+ this.containerCounter = this.containerCounter % 100 + 1;
++ return containerCounter; // CraftBukkit
+ }
+
+ @Override
+@@ -977,23 +1389,47 @@
+ if (menu == 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 = menu.createMenu(this.containerCounter, this.getInventory(), this);
+- if (abstractContainerMenu == null) {
++ AbstractContainerMenu container = menu.createMenu(this.containerCounter, this.getInventory(), this);
++
++ // 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(), menu.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);
+ }
+ }
+@@ -1006,13 +1442,24 @@
+
+ @Override
+ 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.nextContainerCounter(); // CraftBukkit - moved up
+ this.connection.send(new ClientboundHorseScreenOpenPacket(this.containerCounter, inventory.getContainerSize(), horse.getId()));
+- this.containerMenu = new HorseInventoryMenu(this.containerCounter, this.getInventory(), inventory, horse);
++ this.containerMenu = container; // CraftBukkit
+ this.initMenu(this.containerMenu);
+ }
+
+@@ -1034,6 +1482,7 @@
+
+ @Override
+ public void closeContainer() {
++ CraftEventFactory.handleInventoryCloseEvent(this); // CraftBukkit
+ this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId));
+ this.doCloseContainer();
+ }
+@@ -1056,6 +1505,16 @@
+ }
+
+ 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);
+ }
+ }
+@@ -1081,22 +1545,22 @@
+ public void checkMovementStatistics(double d, double d1, double d2) {
+ if (!this.isPassenger() && !didNotMove(d, d1, d2)) {
+ if (this.isSwimming()) {
+- int rounded = Math.round((float)Math.sqrt(d * d + d1 * d1 + d2 * d2) * 100.0F);
+- if (rounded > 0) {
+- this.awardStat(Stats.SWIM_ONE_CM, rounded);
+- this.causeFoodExhaustion(0.01F * (float)rounded * 0.01F);
++ 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, EntityExhaustionEvent.ExhaustionReason.SWIM); // CraftBukkit - EntityExhaustionEvent
+ }
+ } else if (this.isEyeInFluid(FluidTags.WATER)) {
+- int rounded = Math.round((float)Math.sqrt(d * d + d1 * d1 + d2 * d2) * 100.0F);
+- if (rounded > 0) {
+- this.awardStat(Stats.WALK_UNDER_WATER_ONE_CM, rounded);
+- this.causeFoodExhaustion(0.01F * (float)rounded * 0.01F);
++ 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, EntityExhaustionEvent.ExhaustionReason.WALK_UNDERWATER); // CraftBukkit - EntityExhaustionEvent
+ }
+ } else if (this.isInWater()) {
+- int rounded = Math.round((float)Math.sqrt(d * d + d2 * d2) * 100.0F);
+- if (rounded > 0) {
+- this.awardStat(Stats.WALK_ON_WATER_ONE_CM, rounded);
+- this.causeFoodExhaustion(0.01F * (float)rounded * 0.01F);
++ 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, EntityExhaustionEvent.ExhaustionReason.WALK_ON_WATER); // CraftBukkit - EntityExhaustionEvent
+ }
+ } else if (this.onClimbable()) {
+ if (d1 > 0.0) {
+@@ -1106,14 +1570,14 @@
+ int rounded = Math.round((float)Math.sqrt(d * d + d2 * d2) * 100.0F);
+ if (rounded > 0) {
+ if (this.isSprinting()) {
+- this.awardStat(Stats.SPRINT_ONE_CM, rounded);
+- this.causeFoodExhaustion(0.1F * (float)rounded * 0.01F);
++ this.awardStat(Stats.SPRINT_ONE_CM, i);
++ this.causeFoodExhaustion(0.1F * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.SPRINT); // CraftBukkit - EntityExhaustionEvent
+ } else if (this.isCrouching()) {
+- this.awardStat(Stats.CROUCH_ONE_CM, rounded);
+- this.causeFoodExhaustion(0.0F * (float)rounded * 0.01F);
++ this.awardStat(Stats.CROUCH_ONE_CM, i);
++ this.causeFoodExhaustion(0.0F * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.CROUCH); // CraftBukkit - EntityExhaustionEvent
+ } else {
+- this.awardStat(Stats.WALK_ONE_CM, rounded);
+- this.causeFoodExhaustion(0.0F * (float)rounded * 0.01F);
++ this.awardStat(Stats.WALK_ONE_CM, i);
++ this.causeFoodExhaustion(0.0F * (float) i * 0.01F, EntityExhaustionEvent.ExhaustionReason.WALK); // CraftBukkit - EntityExhaustionEvent
+ }
+ }
+ } else if (this.isFallFlying()) {
+@@ -1159,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
+@@ -1205,6 +1676,7 @@
+
+ public void resetSentInfo() {
+ this.lastSentHealth = -1.0E8F;
++ this.lastSentExp = -1; // CraftBukkit - Added to reset
+ }
+
+ @Override
+@@ -1260,7 +1734,7 @@
+ this.lastSentExp = -1;
+ this.lastSentHealth = -1.0F;
+ this.lastSentFood = -1;
+- this.recipeBook.copyOverData(that.recipeBook);
++ // this.recipeBook.copyOverData(entityplayer.recipeBook); // CraftBukkit
+ this.seenCredits = that.seenCredits;
+ this.enteredNetherPosition = that.enteredNetherPosition;
+ this.chunkTrackingView = that.chunkTrackingView;
+@@ -1310,18 +1784,25 @@
+ }
+
+ @Override
+- public boolean teleportTo(ServerLevel level, double x, double y, double z, Set<RelativeMovement> relativeMovements, float yRot, float xRot) {
+- ChunkPos chunkPos = new ChunkPos(BlockPos.containing(x, y, z));
+- level.getChunkSource().addRegionTicket(TicketType.POST_TELEPORT, chunkPos, 1, this.getId());
++ 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);
++ }
++
++ 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 (level == this.level()) {
+- this.connection.teleport(x, y, z, yRot, xRot, relativeMovements);
++ if (worldserver == this.level()) {
++ this.connection.teleport(d0, d1, d2, f, f1, set, cause); // CraftBukkit
+ } else {
+- this.teleportTo(level, x, y, z, yRot, xRot);
++ this.teleportTo(worldserver, d0, d1, d2, f, f1, cause); // CraftBukkit
+ }
+
+ this.setYHeadRot(yRot);
+@@ -1424,15 +1905,25 @@
+ : "<unknown>";
+ }
+
+- public void updateOptions(ClientInformation clientInformation) {
+- this.language = clientInformation.language();
+- this.requestedViewDistance = clientInformation.viewDistance();
+- this.chatVisibility = clientInformation.chatVisibility();
+- this.canChatColor = clientInformation.chatColors();
+- this.textFilteringEnabled = clientInformation.textFilteringEnabled();
+- this.allowsListing = clientInformation.allowsListing();
+- this.getEntityData().set(DATA_PLAYER_MODE_CUSTOMISATION, (byte)clientInformation.modelCustomisation());
+- this.getEntityData().set(DATA_PLAYER_MAIN_HAND, (byte)clientInformation.mainHand().getId());
++ 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();
++ this.canChatColor = clientinformation.chatColors();
++ this.textFilteringEnabled = clientinformation.textFilteringEnabled();
++ this.allowsListing = clientinformation.allowsListing();
++ this.getEntityData().set(ServerPlayer.DATA_PLAYER_MODE_CUSTOMISATION, (byte) clientinformation.modelCustomisation());
++ this.getEntityData().set(ServerPlayer.DATA_PLAYER_MAIN_HAND, (byte) clientinformation.mainHand().getId());
+ }
+
+ public ClientInformation clientInformation() {
+@@ -1504,11 +1990,16 @@
+ }
+
+ public void setCamera(@Nullable Entity entityToSpectate) {
+- Entity camera = this.getCamera();
+- this.camera = (Entity)(entityToSpectate == null ? this : entityToSpectate);
+- if (camera != this.camera) {
+- if (this.camera.level() instanceof ServerLevel serverLevel) {
+- this.teleportTo(serverLevel, this.camera.getX(), this.camera.getY(), this.camera.getZ(), Set.of(), this.getYRot(), this.getXRot());
++ Entity entity1 = this.getCamera();
++
++ this.camera = (Entity) (entityToSpectate == null ? this : entityToSpectate);
++ if (entity1 != this.camera) {
++ Level world = this.camera.level();
++
++ if (world instanceof ServerLevel) {
++ ServerLevel worldserver = (ServerLevel) world;
++
++ this.teleportTo(worldserver, this.camera.getX(), this.camera.getY(), this.camera.getZ(), Set.of(), this.getYRot(), this.getXRot(), TeleportCause.SPECTATE); // CraftBukkit
+ }
+
+ if (entityToSpectate != null) {
+@@ -1542,7 +2036,7 @@
+
+ @Nullable
+ public Component getTabListDisplayName() {
+- return null;
++ return listName; // CraftBukkit
+ }
+
+ @Override
+@@ -1563,11 +2057,18 @@
+ return this.advancements;
+ }
+
+- public void teleportTo(ServerLevel newLevel, double x, double y, double z, float yaw, float pitch) {
++ // 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 (newLevel == this.level()) {
+- this.connection.teleport(x, y, z, yaw, pitch);
++ /* CraftBukkit start - replace with bukkit handling for multi-world
++ if (worldserver == this.level()) {
++ this.connection.teleport(d0, d1, d2, f, f1);
+ } else {
+ ServerLevel serverLevel = this.serverLevel();
+ LevelData levelData = newLevel.getLevelData();
+@@ -1584,6 +2086,10 @@
+ this.server.getPlayerList().sendLevelInfo(this, newLevel);
+ this.server.getPlayerList().sendAllPlayerInfo(this);
+ }
++ */
++ this.getBukkitEntity().teleport(new Location(worldserver.getWorld(), d0, d1, d2, f, f1), cause);
++ // CraftBukkit end
++
+ }
+
+ @Nullable
+@@ -1604,9 +2110,36 @@
+ }
+
+ public void setRespawnPosition(ResourceKey<Level> dimension, @Nullable BlockPos position, float angle, boolean forced, boolean sendMessage) {
+- if (position != null) {
+- boolean flag = position.equals(this.respawnPosition) && dimension.equals(this.respawnDimension);
+- if (sendMessage && !flag) {
++ // 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"));
+ }
+
+@@ -1805,4 +2341,146 @@
+ 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-vineflower-stripped/net/minecraft/server/level/ServerPlayerGameMode.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/level/ServerPlayerGameMode.java.patch
new file mode 100644
index 0000000000..1a1bd0900e
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/level/ServerPlayerGameMode.java.patch
@@ -0,0 +1,327 @@
+--- 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 {
+ private static final Logger LOGGER = LogUtils.getLogger();
+@@ -51,12 +76,16 @@
+ if (gameModeForPlayer == this.gameModeForPlayer) {
+ return false;
+ } else {
++ // 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;
+ }
+@@ -86,7 +115,9 @@
+ }
+
+ public void tick() {
+- this.gameTicks++;
++ this.gameTicks = MinecraftServer.currentTick; // CraftBukkit;
++ IBlockData iblockdata;
++
+ if (this.hasDelayedDestroy) {
+ BlockState blockState = this.level.getBlockState(this.delayedDestroyPos);
+ if (blockState.isAir()) {
+@@ -134,11 +169,33 @@
+ } else {
+ if (action == ServerboundPlayerActionPacket.Action.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(pos, sequence, "creative destroy");
+ return;
+@@ -152,13 +209,46 @@
+
+ this.destroyProgressStart = this.gameTicks;
+ float f = 1.0F;
+- BlockState blockState = this.level.getBlockState(pos);
+- if (!blockState.isAir()) {
+- blockState.attack(this.level, pos, this.player);
+- f = blockState.getDestroyProgress(this.player, this.player.level(), pos);
++
++ 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) {
++ 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) {
+@@ -199,13 +292,15 @@
+ } else if (action == ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK) {
+ this.isDestroyingBlock = false;
+ if (!Objects.equals(this.destroyPos, pos)) {
+- LOGGER.warn("Mismatch in destroy block pos: {} {}", 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(pos, true, sequence, "aborted mismatched 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
+ }
+ }
+ }
+@@ -220,37 +317,112 @@
+ }
+
+ public boolean destroyBlock(BlockPos pos) {
+- BlockState blockState = this.level.getBlockState(pos);
+- if (!this.player.getMainHandItem().getItem().canAttackBlock(blockState, this.level, pos, this.player)) {
++ 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 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(pos);
+- 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(pos, blockState, blockState, 3);
+ return false;
+ } else if (this.player.blockActionRestricted(this.level, pos, this.gameModeForPlayer)) {
+ return false;
+ } else {
+- BlockState blockState1 = block.playerWillDestroy(this.level, pos, blockState, this.player);
++ // 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, pos, blockState1);
+ }
+
+ if (this.isCreative()) {
+- return true;
++ // return true; // CraftBukkit
+ } else {
+- ItemStack mainHandItem = this.player.getMainHandItem();
+- ItemStack itemStack = mainHandItem.copy();
+- boolean hasCorrectToolForDrops = this.player.hasCorrectToolForDrops(blockState1);
+- mainHandItem.mineBlock(this.level, blockState1, pos, this.player);
+- if (flag && hasCorrectToolForDrops) {
+- block.playerDestroy(this.level, this.player, pos, blockState1, blockEntity, itemStack);
++ ItemStack itemstack = this.player.getMainHandItem();
++ ItemStack itemstack1 = itemstack.copy();
++ boolean flag1 = this.player.hasCorrectToolForDrops(iblockdata1);
++
++ 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
+ }
+ }
+ }
+@@ -294,10 +468,19 @@
+ }
+ }
+
+- public InteractionResult useItemOn(ServerPlayer player, Level level, ItemStack stack, InteractionHand hand, BlockHitResult hitResult) {
+- BlockPos blockPos = hitResult.getBlockPos();
+- BlockState blockState = level.getBlockState(blockPos);
+- if (!blockState.getBlock().isEnabled(level.enabledFeatures())) {
++ // 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 (!iblockdata.getBlock().isEnabled(level.enabledFeatures())) {
+ return InteractionResult.FAIL;
+ } else if (this.gameModeForPlayer == GameType.SPECTATOR) {
+ MenuProvider menuProvider = blockState.getMenuProvider(level, blockPos);
+@@ -339,6 +557,8 @@
+ return InteractionResult.PASS;
+ }
+ }
++ return enuminteractionresult;
++ // CraftBukkit end
+ }
+
+ public void setLevel(ServerLevel serverLevel) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/level/TicketType.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/level/TicketType.java.patch
new file mode 100644
index 0000000000..91f6de154e
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -18,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 name, Comparator<T> comparator) {
+ return new TicketType<>(name, comparator, 0L);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/level/WorldGenRegion.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/level/WorldGenRegion.java.patch
new file mode 100644
index 0000000000..6b215d0f7c
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/level/WorldGenRegion.java.patch
@@ -0,0 +1,30 @@
+--- a/net/minecraft/server/level/WorldGenRegion.java
++++ b/net/minecraft/server/level/WorldGenRegion.java
+@@ -207,9 +208,10 @@
+ if (blockState.isAir()) {
+ return false;
+ } else {
+- if (dropBlock) {
+- BlockEntity blockEntity = blockState.hasBlockEntity() ? this.getBlockEntity(pos) : null;
+- Block.dropResources(blockState, this.level, pos, blockEntity, entity, ItemStack.EMPTY);
++ if (false) { // CraftBukkit - SPIGOT-6833: Do not drop during world generation
++ BlockEntity tileentity = iblockdata.hasBlockEntity() ? this.getBlockEntity(pos) : null;
++
++ Block.dropResources(iblockdata, this.level, pos, tileentity, entity, ItemStack.EMPTY);
+ }
+
+ return this.setBlock(pos, Blocks.AIR.defaultBlockState(), 3, recursionLeft);
+@@ -328,6 +327,13 @@
+
+ @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 i1 = SectionPos.blockToSectionCoord(entity.getBlockZ());
+ this.getChunk(i, i1).addEntity(entity);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/LegacyQueryHandler.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/LegacyQueryHandler.java.patch
new file mode 100644
index 0000000000..e985c493ff
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/LegacyQueryHandler.java.patch
@@ -0,0 +1,63 @@
+--- a/net/minecraft/server/network/LegacyQueryHandler.java
++++ b/net/minecraft/server/network/LegacyQueryHandler.java
+@@ -31,12 +32,15 @@
+ return;
+ }
+
+- SocketAddress socketAddress = context.channel().remoteAddress();
+- int i = byteBuf.readableBytes();
++ 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) {
+- LOGGER.debug("Ping: (<1.3.x) from {}", socketAddress);
+- String string = createVersion0Response(this.server);
+- sendFlushAndClose(context, createLegacyDisconnectPacket(context.alloc(), string));
++ LegacyQueryHandler.LOGGER.debug("Ping: (<1.3.x) from {}", socketaddress);
++ s = createVersion0Response(this.server, event); // CraftBukkit
++ sendFlushAndClose(channelhandlercontext, createLegacyDisconnectPacket(channelhandlercontext.alloc(), s));
+ } else {
+ if (byteBuf.readUnsignedByte() != 1) {
+ return;
+@@ -52,8 +56,8 @@
+ LOGGER.debug("Ping: (1.4-1.5.x) from {}", socketAddress);
+ }
+
+- String string = createVersion1Response(this.server);
+- sendFlushAndClose(context, createLegacyDisconnectPacket(context.alloc(), string));
++ s = createVersion1Response(this.server, event); // CraftBukkit
++ sendFlushAndClose(channelhandlercontext, createLegacyDisconnectPacket(channelhandlercontext.alloc(), s));
+ }
+
+ byteBuf.release();
+@@ -95,20 +107,16 @@
+ }
+ }
+
+- private static String createVersion0Response(ServerInfo serverInfo) {
+- return String.format(Locale.ROOT, "%s§%d§%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,
+- "§1\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 context) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch
new file mode 100644
index 0000000000..3575f2c830
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch
@@ -0,0 +1,187 @@
+--- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
++++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
+@@ -24,6 +30,17 @@
+ import net.minecraft.util.VisibleForDebug;
+ 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();
+ public static final int LATENCY_CHECK_INTERVAL = 15000;
+@@ -36,13 +54,21 @@
+ private int latency;
+ private volatile boolean suspendFlushingOnServerThread = false;
+
+- public ServerCommonPacketListenerImpl(MinecraftServer minecraftServer, Connection connection, CommonListenerCookie commonListenerCookie) {
+- this.server = minecraftServer;
+- this.connection = connection;
++ public ServerCommonPacketListenerImpl(MinecraftServer minecraftserver, Connection networkmanager, CommonListenerCookie commonlistenercookie, ServerPlayer player) { // CraftBukkit
++ this.server = minecraftserver;
++ this.connection = networkmanager;
+ this.keepAliveTime = Util.getMillis();
+- this.latency = commonListenerCookie.latency();
++ 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
+ public void onDisconnect(Component reason) {
+ if (this.isSingleplayerOwner()) {
+@@ -52,9 +82,11 @@
+ }
+
+ @Override
+- public void handleKeepAlive(ServerboundKeepAlivePacket serverboundKeepAlivePacket) {
+- if (this.keepAlivePending && serverboundKeepAlivePacket.getId() == this.keepAliveChallenge) {
+- int i = (int)(Util.getMillis() - this.keepAliveTime);
++ 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);
++
+ this.latency = (this.latency * 3 + i) / 4;
+ this.keepAlivePending = false;
+ } else if (!this.isSingleplayerOwner()) {
+@@ -66,10 +98,19 @@
+ 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
+ public void handleCustomPayload(ServerboundCustomPayloadPacket serverboundCustomPayloadPacket) {
+ }
+
++ public final boolean isDisconnected() {
++ return !this.player.joining && !this.connection.isConnected();
++ }
++ // CraftBukkit end
++
+ @Override
+ public void handleResourcePackResponse(ServerboundResourcePackPacket serverboundResourcePackPacket) {
+ PacketUtils.ensureRunningOnSameThread(serverboundResourcePackPacket, this, this.server);
+@@ -77,12 +156,15 @@
+ 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
++
+ }
+
+ protected void keepConnectionAlive() {
+ this.server.getProfiler().push("keepAlive");
+- long millis = Util.getMillis();
+- if (millis - this.keepAliveTime >= 15000L) {
++ long i = Util.getMillis();
++
++ if (i - this.keepAliveTime >= 25000L) { // CraftBukkit
+ if (this.keepAlivePending) {
+ this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE);
+ } else {
+@@ -109,7 +191,15 @@
+ this.send(packet, null);
+ }
+
+- public void send(Packet<?> packet, @Nullable PacketSendListener packetSendListener) {
++ 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 {
+@@ -122,10 +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();
+- this.server.executeBlocking(this.connection::handleDisconnection);
++ MinecraftServer minecraftserver = this.server;
++ Connection networkmanager = this.connection;
++
++ Objects.requireNonNull(this.connection);
++ // CraftBukkit - Don't wait
++ minecraftserver.wrapRunnable(networkmanager::handleDisconnection);
+ }
+
+ protected boolean isSingleplayerOwner() {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch
new file mode 100644
index 0000000000..f007de6d2c
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch
@@ -0,0 +1,39 @@
+--- a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
++++ b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
+@@ -42,10 +44,10 @@
+ private ConfigurationTask currentTask;
+ private ClientInformation clientInformation;
+
+- public ServerConfigurationPacketListenerImpl(MinecraftServer minecraftServer, Connection connection, CommonListenerCookie commonListenerCookie) {
+- super(minecraftServer, connection, commonListenerCookie);
+- this.gameProfile = commonListenerCookie.gameProfile();
+- this.clientInformation = commonListenerCookie.clientInformation();
++ 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();
+ }
+
+ @Override
+@@ -116,14 +117,16 @@
+ return;
+ }
+
+- Component component = playerList.canPlayerLogin(this.connection.getRemoteAddress(), this.gameProfile);
+- if (component != null) {
+- this.disconnect(component);
++ Component ichatbasecomponent = null; // CraftBukkit - login checks already completed
++
++ if (ichatbasecomponent != null) {
++ this.disconnect(ichatbasecomponent);
+ return;
+ }
+
+- ServerPlayer playerForLogin = playerList.getPlayerForLogin(this.gameProfile, this.clientInformation);
+- playerList.placeNewPlayer(this.connection, playerForLogin, this.createCookie(this.clientInformation));
++ ServerPlayer entityplayer = playerlist.getPlayerForLogin(this.gameProfile, this.clientInformation, this.player); // CraftBukkit
++
++ playerlist.placeNewPlayer(this.connection, entityplayer, this.createCookie(this.clientInformation));
+ this.connection.resumeInboundAfterProtocolChange();
+ } catch (Exception var5) {
+ LOGGER.error("Couldn't place player in world", (Throwable)var5);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerConnectionListener.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerConnectionListener.java.patch
new file mode 100644
index 0000000000..27a9942ae4
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerConnectionListener.java.patch
@@ -0,0 +1,79 @@
+--- a/net/minecraft/server/network/ServerConnectionListener.java
++++ b/net/minecraft/server/network/ServerConnectionListener.java
+@@ -78,45 +84,40 @@
+ LOGGER.info("Using default channel type");
+ }
+
+- this.channels
+- .add(
+- new ServerBootstrap()
+- .channel(clazz)
+- .childHandler(
+- new ChannelInitializer<Channel>() {
+- @Override
+- protected void initChannel(Channel channel) {
+- Connection.setInitialProtocolAttributes(channel);
+-
+- try {
+- channel.config().setOption(ChannelOption.TCP_NODELAY, true);
+- } catch (ChannelException var5) {
+- }
+-
+- ChannelPipeline channelPipeline = channel.pipeline()
+- .addLast("timeout", new ReadTimeoutHandler(30))
+- .addLast("legacy_query", new LegacyQueryHandler(ServerConnectionListener.this.getServer()));
+- Connection.configureSerialization(channelPipeline, PacketFlow.SERVERBOUND, null);
+- int rateLimitPacketsPerSecond = ServerConnectionListener.this.server.getRateLimitPacketsPerSecond();
+- Connection connection = (Connection)(rateLimitPacketsPerSecond > 0
+- ? new RateKickingConnection(rateLimitPacketsPerSecond)
+- : new Connection(PacketFlow.SERVERBOUND));
+- ServerConnectionListener.this.connections.add(connection);
+- connection.configurePacketHandler(channelPipeline);
+- connection.setListenerForServerboundHandshake(
+- new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, connection)
+- );
+- }
+- }
+- )
+- .group(eventLoopGroup)
+- .localAddress(address, port)
+- .bind()
+- .syncUninterruptibly()
+- );
++ this.channels.add(((ServerBootstrap) ((ServerBootstrap) (new ServerBootstrap()).channel(oclass)).childHandler(new ChannelInitializer<Channel>() {
++ protected void initChannel(Channel channel) {
++ Connection.setInitialProtocolAttributes(channel);
++
++ try {
++ channel.config().setOption(ChannelOption.TCP_NODELAY, true);
++ } catch (ChannelException channelexception) {
++ ;
++ }
++
++ ChannelPipeline channelpipeline = channel.pipeline().addLast("timeout", new ReadTimeoutHandler(30)).addLast("legacy_query", new LegacyQueryHandler(ServerConnectionListener.this.getServer()));
++
++ Connection.configureSerialization(channelpipeline, PacketFlow.SERVERBOUND, (BandwidthDebugMonitor) null);
++ int j = ServerConnectionListener.this.server.getRateLimitPacketsPerSecond();
++ Connection object = j > 0 ? new RateKickingConnection(j) : new Connection(PacketFlow.SERVERBOUND); // CraftBukkit - decompile error
++
++ ServerConnectionListener.this.connections.add(object);
++ ((Connection) object).configurePacketHandler(channelpipeline);
++ ((Connection) object).setListenerForServerboundHandshake(new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, (Connection) object));
++ }
++ }).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() {
+ ChannelFuture channelFuture;
+ synchronized (this.channels) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
new file mode 100644
index 0000000000..1ce9adc6bb
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
@@ -0,0 +1,1729 @@
+--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
++++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+@@ -179,11 +185,64 @@
+ import net.minecraft.world.phys.shapes.VoxelShape;
+ import org.slf4j.Logger;
+
+-public class ServerGamePacketListenerImpl
+- extends ServerCommonPacketListenerImpl
+- implements ServerGamePacketListener,
+- ServerPlayerConnection,
+- TickablePacketListener {
++// 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();
+ public static final double MAX_INTERACTION_DISTANCE = Mth.square(6.0);
+ private static final int NO_BLOCK_UPDATES_TO_ACK = -1;
+@@ -193,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;
+@@ -227,19 +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();
+- this.signedMessageDecoder = SignedMessageChain.Decoder.unsigned(serverPlayer.getUUID(), minecraftServer::enforceSecureProfile);
+- this.chatMessageChain = new FutureChain(minecraftServer);
++ 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.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
+ public void tick() {
+ if (this.ackBlockChangesUpTo > -1) {
+@@ -291,17 +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) {
++ 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"));
+ }
+ }
+@@ -385,16 +468,42 @@
+ double d2 = clampHorizontal(packet.getZ());
+ float f = Mth.wrapDegrees(packet.getYRot());
+ float f1 = Mth.wrapDegrees(packet.getXRot());
+- double d3 = d - this.vehicleFirstGoodX;
+- double d4 = d1 - this.vehicleFirstGoodY;
+- double d5 = d2 - this.vehicleFirstGoodZ;
+- double d6 = rootVehicle.getDeltaMovement().lengthSqr();
+- double d7 = d3 * d3 + d4 * d4 + d5 * d5;
+- if (d7 - d6 > 100.0 && !this.isSingleplayerOwner()) {
+- LOGGER.warn(
+- "{} (vehicle of {}) moved too quickly! {},{},{}", rootVehicle.getName().getString(), this.player.getName().getString(), d3, d4, d5
+- );
+- this.send(new ClientboundMoveVehiclePacket(rootVehicle));
++ double d6 = d3 - this.vehicleFirstGoodX;
++ double d7 = d4 - this.vehicleFirstGoodY;
++ double d8 = d5 - this.vehicleFirstGoodZ;
++ double d9 = entity.getDeltaMovement().lengthSqr();
++ double d10 = d6 * d6 + d7 * d7 + d8 * d8;
++
++
++ // 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;
+ }
+
+@@ -422,14 +540,73 @@
+ LOGGER.warn("{} (vehicle of {}) moved wrongly! {}", rootVehicle.getName().getString(), this.player.getName().getString(), Math.sqrt(d7));
+ }
+
+- rootVehicle.absMoveTo(d, d1, d2, f, f1);
+- boolean flag3 = serverLevel.noCollision(rootVehicle, rootVehicle.getBoundingBox().deflate(0.0625));
++ entity.absMoveTo(d3, d4, d5, f, f1);
++ 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)) {
+- rootVehicle.absMoveTo(x, y, z, f, f1);
+- this.send(new ClientboundMoveVehiclePacket(rootVehicle));
++ 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() - x, this.player.getY() - y, this.player.getZ() - z);
+ this.clientVehicleIsFloating = d4 >= -0.03125
+@@ -475,6 +640,7 @@
+ }
+
+ this.awaitingPositionFromClient = null;
++ this.player.serverLevel().getChunkSource().move(this.player); // CraftBukkit
+ }
+ }
+
+@@ -487,6 +658,7 @@
+ @Override
+ 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());
+ }
+
+@@ -505,17 +679,24 @@
+ @Override
+ public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket packet) {
+ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
+- StringReader stringReader = new StringReader(packet.getCommand());
+- if (stringReader.canRead() && stringReader.peek() == '/') {
+- stringReader.skip();
++ // 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());
+
+- ParseResults<CommandSourceStack> parseResults = this.server.getCommands().getDispatcher().parse(stringReader, this.player.createCommandSourceStack());
+- this.server
+- .getCommands()
+- .getDispatcher()
+- .getCompletionSuggestions(parseResults)
+- .thenAccept(suggestions -> this.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestions)));
++ if (stringreader.canRead() && stringreader.peek() == '/') {
++ stringreader.skip();
++ }
++
++ ParseResults<CommandSourceStack> parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack());
++
++ this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> {
++ if (suggestions.isEmpty()) return; // CraftBukkit - don't send through empty suggestions - prevents [<args>] from showing for plugins with nothing more to offer
++ this.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestions));
++ });
+ }
+
+ @Override
+@@ -721,12 +937,18 @@
+ @Override
+ public void handleSelectTrade(ServerboundSelectTradePacket packet) {
+ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
+- int item = packet.getItem();
+- if (this.player.containerMenu instanceof MerchantMenu merchantMenu) {
+- if (!merchantMenu.stillValid(this.player)) {
+- LOGGER.debug("Player {} interacted with invalid menu {}", this.player, merchantMenu);
++ int i = packet.getItem();
++ AbstractContainerMenu container = this.player.containerMenu;
++
++ 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
+
+ merchantMenu.setSelectionHint(item);
+ merchantMenu.tryMoveItems(item);
+@@ -735,8 +963,16 @@
+
+ @Override
+ public void handleEditBook(ServerboundEditBookPacket packet) {
+- int slot = packet.getSlot();
+- if (Inventory.isHotbarSlot(slot) || slot == 40) {
++ // 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();
+ Optional<String> title = packet.getTitle();
+ title.ifPresent(list::add);
+@@ -749,9 +993,10 @@
+ }
+
+ private void updateBookContents(List<FilteredText> pages, int index) {
+- ItemStack item = this.player.getInventory().getItem(index);
+- if (item.is(Items.WRITABLE_BOOK)) {
+- this.updateBookPages(pages, UnaryOperator.identity(), item);
++ ItemStack itemstack = this.player.getInventory().getItem(index);
++
++ if (itemstack.is(Items.WRITABLE_BOOK)) {
++ this.updateBookPages(pages, UnaryOperator.identity(), itemstack.copy(), index, itemstack); // CraftBukkit
+ }
+ }
+
+@@ -772,13 +1019,16 @@
+ itemStack.addTagElement("title", StringTag.valueOf(title.raw()));
+ }
+
+- this.updateBookPages(pages, string -> Component.Serializer.toJson(Component.literal(string)), itemStack);
+- this.player.getInventory().setItem(index, itemStack);
++ this.updateBookPages(pages, (s) -> {
++ return Component.Serializer.toJson(Component.literal(s));
++ }, itemstack1, index, itemstack); // CraftBukkit
++ this.player.getInventory().setItem(index, itemstack); // CraftBukkit - event factory updates the hand book
+ }
+ }
+
+- private void updateBookPages(List<FilteredText> pages, UnaryOperator<String> updater, ItemStack book) {
+- ListTag list = 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()) {
+ pages.stream().map(filteredText1 -> StringTag.valueOf(updater.apply(filteredText1.filteredOrEmpty()))).forEach(list::add);
+ } else {
+@@ -799,7 +1055,8 @@
+ }
+ }
+
+- book.addTagElement("pages", list);
++ itemstack.addTagElement("pages", nbttaglist);
++ CraftEventFactory.handleEditBookEvent(player, slot, handItem, itemstack); // CraftBukkit
+ }
+
+ @Override
+@@ -840,8 +1111,9 @@
+ if (containsInvalidValues(packet.getX(0.0), packet.getY(0.0), packet.getZ(0.0), packet.getYRot(0.0F), packet.getXRot(0.0F))) {
+ this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement"));
+ } else {
+- ServerLevel serverLevel = this.player.serverLevel();
+- if (!this.player.wonGame) {
++ ServerLevel worldserver = this.player.serverLevel();
++
++ if (!this.player.wonGame && !this.player.isImmobile()) { // CraftBukkit
+ if (this.tickCount == 0) {
+ this.resetPosition();
+ }
+@@ -857,6 +1123,7 @@
+ this.player.getXRot()
+ );
+ }
++ this.allowedPlayerTicks = 20; // CraftBukkit
+ } else {
+ this.awaitingTeleportTime = this.tickCount;
+ double d = clampHorizontal(packet.getX(this.player.getX()));
+@@ -867,15 +1135,24 @@
+ 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 {
+- double x = this.player.getX();
+- double y = this.player.getY();
+- double z = this.player.getZ();
+- double d3 = d - this.firstGoodX;
+- double d4 = d1 - this.firstGoodY;
+- double d5 = d2 - this.firstGoodZ;
+- double d6 = this.player.getDeltaMovement().lengthSqr();
+- double d7 = d3 * d3 + d4 * d4 + d5 * d5;
++ // 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();
++ double d6 = d0 - this.firstGoodX;
++ double d7 = d1 - this.firstGoodY;
++ double d8 = d2 - this.firstGoodZ;
++ double d9 = this.player.getDeltaMovement().lengthSqr();
++ double d10 = d6 * d6 + d7 * d7 + d8 * d8;
++
+ if (this.player.isSleeping()) {
+ if (d7 > 1.0) {
+ this.teleport(this.player.getX(), this.player.getY(), this.player.getZ(), f, f1);
+@@ -884,8 +1162,14 @@
+ if (serverLevel.tickRateManager().runsNormally()) {
+ this.receivedMovePacketCount++;
+ int i = this.receivedMovePacketCount - this.knownMovePacketCount;
+- if (i > 5) {
+- LOGGER.debug("{} is sending move packets too frequently ({} packets since last tick)", this.player.getName().getString(), i);
++
++ // 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;
+ }
+
+@@ -895,8 +1187,10 @@
+ || !this.player.isFallFlying()
+ )) {
+ float f2 = this.player.isFallFlying() ? 300.0F : 100.0F;
+- if (d7 - d6 > (double)(f2 * (float)i) && !this.isSingleplayerOwner()) {
+- LOGGER.warn("{} moved too quickly! {},{},{}", this.player.getName().getString(), d3, d4, d5);
++
++ 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;
+ }
+@@ -913,11 +1209,15 @@
+ }
+
+ boolean flag1 = this.player.verticalCollisionBelow;
+- this.player.move(MoverType.PLAYER, new Vec3(d3, d4, d5));
+- d3 = d - this.player.getX();
+- double var36 = d1 - this.player.getY();
+- if (var36 > -0.5 || var36 < 0.5) {
+- var36 = 0.0;
++
++ 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();
++ d7 = d1 - this.player.getY();
++ if (d7 > -0.5D || d7 < 0.5D) {
++ d7 = 0.0D;
+ }
+
+ d5 = d2 - this.player.getZ();
+@@ -932,20 +1229,73 @@
+ LOGGER.warn("{} moved wrongly!", this.player.getName().getString());
+ }
+
+- if (this.player.noPhysics
+- || this.player.isSleeping()
+- || (!flag2 || !serverLevel.noCollision(this.player, boundingBox))
+- && !this.isPlayerCollidingWithAnythingNew(serverLevel, boundingBox, d, d1, d2)) {
+- this.player.absMoveTo(d, d1, d2, f, f1);
+- this.clientIsFloating = d4 >= -0.03125
+- && !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);
++ 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);
+ this.player.doCheckFallDamage(this.player.getX() - x, this.player.getY() - y, this.player.getZ() - z, packet.isOnGround());
+ this.player
+@@ -980,26 +1327,94 @@
+ if (!Shapes.joinIsNotEmpty(voxelShape1, voxelShape, BooleanOp.AND)) {
+ return true;
+ }
++
++ voxelshape1 = (VoxelShape) iterator.next();
++ } while (Shapes.joinIsNotEmpty(voxelshape1, voxelshape, BooleanOp.AND));
++
++ return true;
++ }
++
++ // 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, 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
+ }
+
+- return false;
++ 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(double x, double y, double z, float yaw, float pitch) {
+ this.teleport(x, y, z, yaw, pitch, Collections.emptySet());
+ }
+
+- public void teleport(double x, double y, double z, float yaw, float pitch, Set<RelativeMovement> relativeSet) {
+- double d = relativeSet.contains(RelativeMovement.X) ? this.player.getX() : 0.0;
+- double d1 = relativeSet.contains(RelativeMovement.Y) ? this.player.getY() : 0.0;
+- double d2 = relativeSet.contains(RelativeMovement.Z) ? this.player.getZ() : 0.0;
+- float f = relativeSet.contains(RelativeMovement.Y_ROT) ? this.player.getYRot() : 0.0F;
+- float f1 = relativeSet.contains(RelativeMovement.X_ROT) ? this.player.getXRot() : 0.0F;
+- this.awaitingPositionFromClient = new Vec3(x, y, z);
++ 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;
++ float f2 = set.contains(RelativeMovement.Y_ROT) ? this.player.getYRot() : 0.0F;
++ float f3 = set.contains(RelativeMovement.X_ROT) ? this.player.getXRot() : 0.0F;
++
++ this.awaitingPositionFromClient = new Vec3(d0, d1, d2);
+ if (++this.awaitingTeleport == Integer.MAX_VALUE) {
+ 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(x, y, z, yaw, pitch);
+ this.player.connection.send(new ClientboundPlayerPositionPacket(x - d, y - d1, z - d2, yaw - f, pitch - f1, relativeSet, this.awaitingTeleport));
+@@ -1008,21 +1423,56 @@
+ @Override
+ public void handlePlayerAction(ServerboundPlayerActionPacket packet) {
+ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
+- BlockPos pos = packet.getPos();
++ if (this.player.isImmobile()) return; // CraftBukkit
++ BlockPos blockposition = packet.getPos();
++
+ this.player.resetLastActionTime();
+ ServerboundPlayerActionPacket.Action action = packet.getAction();
+ switch (action) {
+ case SWAP_ITEM_WITH_OFFHAND:
+ if (!this.player.isSpectator()) {
+- ItemStack itemInHand = this.player.getItemInHand(InteractionHand.OFF_HAND);
+- this.player.setItemInHand(InteractionHand.OFF_HAND, this.player.getItemInHand(InteractionHand.MAIN_HAND));
+- this.player.setItemInHand(InteractionHand.MAIN_HAND, itemInHand);
++ ItemStack itemstack = this.player.getItemInHand(EnumHand.OFF_HAND);
++
++ // 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);
+ }
+
+@@ -1059,6 +1511,7 @@
+ @Override
+ 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 serverLevel = this.player.serverLevel();
+ InteractionHand hand = packet.getHand();
+@@ -1074,20 +1531,19 @@
+ if (Math.abs(vec31.x()) < 1.0000001 && Math.abs(vec31.y()) < 1.0000001 && Math.abs(vec31.z()) < 1.0000001) {
+ Direction direction = hitResult.getDirection();
+ this.player.resetLastActionTime();
+- int maxBuildHeight = this.player.level().getMaxBuildHeight();
+- if (blockPos.getY() < maxBuildHeight) {
+- if (this.awaitingPositionFromClient == null
+- && this.player.distanceToSqr((double)blockPos.getX() + 0.5, (double)blockPos.getY() + 0.5, (double)blockPos.getZ() + 0.5) < 64.0
+- && serverLevel.mayInteract(this.player, blockPos)) {
+- InteractionResult interactionResult = this.player.gameMode.useItemOn(this.player, serverLevel, itemInHand, hand, hitResult);
+- if (direction == Direction.UP
+- && !interactionResult.consumesAction()
+- && blockPos.getY() >= maxBuildHeight - 1
+- && wasBlockPlacementAttempt(this.player, itemInHand)) {
+- Component component = Component.translatable("build.tooHigh", maxBuildHeight - 1).withStyle(ChatFormatting.RED);
+- this.player.sendSystemMessage(component, true);
+- } else if (interactionResult.shouldSwing()) {
+- this.player.swing(hand, true);
++ int i = this.player.level().getMaxBuildHeight();
++
++ 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 (enumdirection == Direction.UP && !enuminteractionresult.consumesAction() && blockposition.getY() >= i - 1 && wasBlockPlacementAttempt(this.player, itemstack)) {
++ MutableComponent ichatmutablecomponent = Component.translatable("build.tooHigh", i - 1).withStyle(ChatFormatting.RED);
++
++ this.player.sendSystemMessage(ichatmutablecomponent, true);
++ } else if (enuminteractionresult.shouldSwing()) {
++ this.player.swing(enumhand, true);
+ }
+ }
+ } else {
+@@ -1112,16 +1564,62 @@
+ @Override
+ public void handleUseItem(ServerboundUseItemPacket packet) {
+ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
++ if (this.player.isImmobile()) return; // CraftBukkit
+ this.ackBlockChangesUpTo(packet.getSequence());
+ ServerLevel serverLevel = this.player.serverLevel();
+ InteractionHand hand = packet.getHand();
+ ItemStack itemInHand = this.player.getItemInHand(hand);
+ this.player.resetLastActionTime();
+- if (!itemInHand.isEmpty() && itemInHand.isItemEnabled(serverLevel.enabledFeatures())) {
+- InteractionResult interactionResult = this.player.gameMode.useItem(this.player, serverLevel, itemInHand, hand);
+- if (interactionResult.shouldSwing()) {
+- this.player.swing(hand, true);
++ 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);
++
++ float f3 = Mth.cos(-f2 * 0.017453292F - 3.1415927F);
++ float f4 = Mth.sin(-f2 * 0.017453292F - 3.1415927F);
++ float f5 = -Mth.cos(-f1 * 0.017453292F);
++ float f6 = Mth.sin(-f1 * 0.017453292F);
++ float f7 = f4 * f5;
++ float f8 = f3 * f5;
++ double d3 = player.gameMode.getGameModeForPlayer()== GameType.CREATIVE ? 5.0D : 4.5D;
++ Vec3 vec3d1 = vec3d.add((double) f7 * d3, (double) f6 * d3, (double) f8 * d3);
++ HitResult movingobjectposition = this.player.level().clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, player));
++
++ boolean cancelled;
++ if (movingobjectposition == null || movingobjectposition.getType() != HitResult.EnumMovingObjectType.BLOCK) {
++ org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.RIGHT_CLICK_AIR, itemstack, enumhand);
++ cancelled = event.useItemInHand() == Event.Result.DENY;
++ } else {
++ BlockHitResult movingobjectpositionblock = (BlockHitResult) movingobjectposition;
++ if (player.gameMode.firedInteract && player.gameMode.interactPosition.equals(movingobjectpositionblock.getBlockPos()) && player.gameMode.interactHand == enumhand && ItemStack.isSameItemSameTags(player.gameMode.interactItemStack, itemstack)) {
++ cancelled = player.gameMode.interactResult;
++ } else {
++ org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(player, Action.RIGHT_CLICK_BLOCK, movingobjectpositionblock.getBlockPos(), movingobjectpositionblock.getDirection(), itemstack, true, enumhand, movingobjectpositionblock.getLocation());
++ cancelled = event.useItemInHand() == Event.Result.DENY;
++ }
++ player.gameMode.firedInteract = false;
+ }
++
++ 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);
++ }
++
+ }
+ }
+
+@@ -1132,7 +1635,7 @@
+ for (ServerLevel serverLevel : this.server.getAllLevels()) {
+ Entity entity = packet.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;
+ }
+ }
+@@ -1149,19 +1658,32 @@
+
+ @Override
+ public void onDisconnect(Component reason) {
+- LOGGER.info("{} lost connection: {}", this.player.getName().getString(), reason.getString());
++ // 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(reason);
+ }
+
+ 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();
+ }
+
+@@ -1176,27 +1698,44 @@
+ @Override
+ 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()) {
+- if (this.player.getInventory().selected != packet.getSlot() && this.player.getUsedItemHand() == InteractionHand.MAIN_HAND) {
++ 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();
+ }
+
+ this.player.getInventory().selected = packet.getSlot();
+ this.player.resetLastActionTime();
+ } else {
+- LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString());
++ ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString());
++ this.disconnect("Invalid hotbar selection (Hacking?)"); // CraftBukkit
+ }
+ }
+
+ @Override
+ 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(packet.lastSeenMessages());
+ if (optional.isPresent()) {
+- this.server.submit(() -> {
+- PlayerChatMessage signedMessage;
++ // this.server.submit(() -> { // CraftBukkit - async chat
++ PlayerChatMessage playerchatmessage;
++
+ try {
+ signedMessage = this.getSignedMessage(packet, optional.get());
+ } catch (SignedMessageChain.DecodeException var6) {
+@@ -1204,13 +1744,15 @@
+ return;
+ }
+
+- CompletableFuture<FilteredText> completableFuture = this.filterTextPacket(signedMessage.signedContent());
+- Component component = this.server.getChatDecorator().decorate(this.player, signedMessage.decoratedContent());
+- this.chatMessageChain.append(completableFuture, filteredText -> {
+- PlayerChatMessage playerChatMessage = signedMessage.withUnsignedContent(component).filter(filteredText.mask());
+- this.broadcastChatMessage(playerChatMessage);
++ 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(ichatbasecomponent).filter(filteredtext.mask());
++
++ this.broadcastChatMessage(playerchatmessage1);
+ });
+- });
++ // }); // CraftBukkit - async chat
+ }
+ }
+ }
+@@ -1223,7 +1767,13 @@
+ Optional<LastSeenMessages> optional = this.tryHandleChat(packet.lastSeenMessages());
+ if (optional.isPresent()) {
+ this.server.submit(() -> {
+- this.performChatCommand(packet, 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();
+ });
+ }
+@@ -1231,21 +1782,36 @@
+ }
+
+ private void performChatCommand(ServerboundChatCommandPacket packet, LastSeenMessages lastSeenMessages) {
+- ParseResults<CommandSourceStack> parseResults = this.parseCommand(packet.command());
++ // CraftBukkit start
++ String command = "/" + packet.command();
++ ServerGamePacketListenerImpl.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + command);
+
+- Map<String, PlayerChatMessage> map;
++ 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(packet, SignableCommand.of(parseResults), lastSeenMessages);
+- } catch (SignedMessageChain.DecodeException var6) {
+- this.handleMessageDecodeFailure(var6);
++ 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;
+ }
+
+- CommandSigningContext commandSigningContext = new CommandSigningContext.SignedArguments(map);
+- parseResults = Commands.mapSource(
+- parseResults, commandSourceStack -> commandSourceStack.withSigningContext(commandSigningContext, this.chatMessageChain)
+- );
+- this.server.getCommands().performCommand(parseResults, packet.command());
++ CommandSigningContext.a commandsigningcontext_a = new CommandSigningContext.a(map);
++
++ parseresults = Commands.<CommandSourceStack>mapSource(parseresults, (commandlistenerwrapper) -> { // CraftBukkit - decompile error
++ return commandlistenerwrapper.withSigningContext(commandsigningcontext_a, this.chatMessageChain);
++ });
++ this.server.getCommands().performCommand(parseresults, command); // CraftBukkit
+ }
+
+ private void handleMessageDecodeFailure(SignedMessageChain.DecodeException exception) {
+@@ -1276,9 +1845,10 @@
+ return dispatcher.parse(command, this.player.createCommandSourceStack());
+ }
+
+- private Optional<LastSeenMessages> tryHandleChat(LastSeenMessages.Update update) {
+- Optional<LastSeenMessages> optional = this.unpackAndApplyLastSeen(update);
+- if (this.player.getChatVisibility() == ChatVisiblity.HIDDEN) {
++ private Optional<LastSeenMessages> tryHandleChat(LastSeenMessages.Update lastseenmessages_b) {
++ Optional<LastSeenMessages> optional = this.unpackAndApplyLastSeen(lastseenmessages_b);
++
++ 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 {
+@@ -1309,19 +1882,149 @@
+ return false;
+ }
+
++ // 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);
++
++ if (!async && s.startsWith("/")) {
++ this.handleCommand(s);
++ } else if (this.player.getChatVisibility() == ChatVisiblity.SYSTEM) {
++ // Do nothing, this is coming from a plugin
++ } else {
++ Player player = this.getCraftPlayer();
++ AsyncPlayerChatEvent event = new AsyncPlayerChatEvent(async, player, s, new LazyPlayerSet(server));
++ String originalFormat = event.getFormat(), originalMessage = event.getMessage();
++ this.cserver.getPluginManager().callEvent(event);
++
++ if (PlayerChatEvent.getHandlerList().getRegisteredListeners().length != 0) {
++ // Evil plugins still listening to deprecated event
++ final PlayerChatEvent queueEvent = new PlayerChatEvent(player, event.getMessage(), event.getFormat(), event.getRecipients());
++ queueEvent.setCancelled(event.isCancelled());
++ Waitable waitable = new Waitable() {
++ @Override
++ protected Object evaluate() {
++ org.bukkit.Bukkit.getPluginManager().callEvent(queueEvent);
++
++ if (queueEvent.isCancelled()) {
++ return null;
++ }
++
++ String message = String.format(queueEvent.getFormat(), queueEvent.getPlayer().getDisplayName(), queueEvent.getMessage());
++ if (((LazyPlayerSet) queueEvent.getRecipients()).isLazy()) {
++ if (originalFormat.equals(queueEvent.getFormat()) && originalMessage.equals(queueEvent.getMessage()) && queueEvent.getPlayer().getName().equalsIgnoreCase(queueEvent.getPlayer().getDisplayName())) {
++ ServerGamePacketListenerImpl.this.server.getPlayerList().broadcastChatMessage(original, ServerGamePacketListenerImpl.this.player, ChatType.bind(ChatType.CHAT, (Entity) ServerGamePacketListenerImpl.this.player));
++ return null;
++ }
++
++ for (ServerPlayer recipient : server.getPlayerList().players) {
++ recipient.getBukkitEntity().sendMessage(ServerGamePacketListenerImpl.this.player.getUUID(), message);
++ }
++ } else {
++ for (Player player : queueEvent.getRecipients()) {
++ player.sendMessage(ServerGamePacketListenerImpl.this.player.getUUID(), message);
++ }
++ }
++ ServerGamePacketListenerImpl.this.server.console.sendMessage(message);
++
++ return null;
++ }};
++ if (async) {
++ server.processQueue.add(waitable);
++ } else {
++ waitable.run();
++ }
++ try {
++ waitable.get();
++ } catch (InterruptedException e) {
++ Thread.currentThread().interrupt(); // This is proper habit for java. If we aren't handling it, pass it on!
++ } catch (ExecutionException e) {
++ throw new RuntimeException("Exception processing chat event", e.getCause());
++ }
++ } else {
++ if (event.isCancelled()) {
++ return;
++ }
++
++ s = String.format(event.getFormat(), event.getPlayer().getDisplayName(), event.getMessage());
++ if (((LazyPlayerSet) event.getRecipients()).isLazy()) {
++ if (originalFormat.equals(event.getFormat()) && originalMessage.equals(event.getMessage()) && event.getPlayer().getName().equalsIgnoreCase(event.getPlayer().getDisplayName())) {
++ ServerGamePacketListenerImpl.this.server.getPlayerList().broadcastChatMessage(original, ServerGamePacketListenerImpl.this.player, ChatType.bind(ChatType.CHAT, (Entity) ServerGamePacketListenerImpl.this.player));
++ return;
++ }
++
++ for (ServerPlayer recipient : server.getPlayerList().players) {
++ recipient.getBukkitEntity().sendMessage(ServerGamePacketListenerImpl.this.player.getUUID(), s);
++ }
++ } else {
++ for (Player recipient : event.getRecipients()) {
++ recipient.sendMessage(ServerGamePacketListenerImpl.this.player.getUUID(), s);
++ }
++ }
++ server.console.sendMessage(s);
++ }
++ }
++ }
++
++ 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) {
+- this.server.getPlayerList().broadcastChatMessage(message, this.player, ChatType.bind(ChatType.CHAT, this.player));
++ // 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"));
+ }
+ }
+@@ -1339,13 +2047,62 @@
+ @Override
+ public void handleAnimate(ServerboundSwingPacket packet) {
+ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
++ if (this.player.isImmobile()) return; // CraftBukkit
+ this.player.resetLastActionTime();
++ // 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
+ 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();
+ switch (packet.getAction()) {
+ case PRESS_SHIFT_KEY:
+@@ -1411,17 +2184,13 @@
+ }
+
+ public void sendPlayerChatMessage(PlayerChatMessage chatMessage, ChatType.Bound boundType) {
+- 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())
+- )
+- );
++ // 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);
+ }
+
+@@ -1447,8 +2216,10 @@
+ @Override
+ public void handleInteract(ServerboundInteractPacket packet) {
+ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
+- final ServerLevel serverLevel = this.player.serverLevel();
+- final Entity target = packet.getTarget(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(packet.isUsingSecondaryAction());
+ if (target != null) {
+@@ -1456,48 +2227,90 @@
+ return;
+ }
+
+- AABB boundingBox = target.getBoundingBox();
+- if (boundingBox.distanceToSqr(this.player.getEyePosition()) < MAX_INTERACTION_DISTANCE) {
+- packet.dispatch(
+- new ServerboundInteractPacket.Handler() {
+- private void performInteraction(InteractionHand hand, ServerGamePacketListenerImpl.EntityInteraction entityInteraction) {
+- ItemStack itemInHand = ServerGamePacketListenerImpl.this.player.getItemInHand(hand);
+- if (itemInHand.isItemEnabled(serverLevel.enabledFeatures())) {
+- ItemStack itemStack = itemInHand.copy();
+- InteractionResult interactionResult = entityInteraction.run(ServerGamePacketListenerImpl.this.player, target, hand);
+- if (interactionResult.consumesAction()) {
+- CriteriaTriggers.PLAYER_INTERACTED_WITH_ENTITY.trigger(ServerGamePacketListenerImpl.this.player, itemStack, target);
+- if (interactionResult.shouldSwing()) {
+- ServerGamePacketListenerImpl.this.player.swing(hand, true);
+- }
++ AABB axisalignedbb = entity.getBoundingBox();
++
++ 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(worldserver.enabledFeatures())) {
++ ItemStack itemstack1 = itemstack.copy();
++ // 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();
++
++ 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 (enuminteractionresult.shouldSwing()) {
++ ServerGamePacketListenerImpl.this.player.swing(enumhand, true);
++ }
++ }
++
+ }
+-
+- @Override
+- public void onInteraction(InteractionHand hand) {
+- this.performInteraction(hand, Player::interactOn);
+- }
+-
+- @Override
+- public void onInteraction(InteractionHand hand, Vec3 interactionLocation) {
+- this.performInteraction(hand, (player, entity, hand1) -> entity.interactAt(player, interactionLocation, hand1));
+- }
+-
+- @Override
+- public void onAttack() {
+- if (!(target instanceof ItemEntity)
+- && !(target instanceof ExperienceOrb)
+- && !(target instanceof AbstractArrow)
+- && target != ServerGamePacketListenerImpl.this.player) {
+- ItemStack itemInHand = ServerGamePacketListenerImpl.this.player.getItemInHand(InteractionHand.MAIN_HAND);
+- if (itemInHand.isItemEnabled(serverLevel.enabledFeatures())) {
+- ServerGamePacketListenerImpl.this.player.attack(target);
++ }
++
++ @Override
++ 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
++ 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
++ public void onAttack() {
++ // 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(worldserver.enabledFeatures())) {
++ ServerGamePacketListenerImpl.this.player.attack(entity);
++ // CraftBukkit start
++ if (!itemstack.isEmpty() && itemstack.getCount() <= -1) {
++ player.containerMenu.sendAllDataToRemote();
+ }
+- } else {
+- ServerGamePacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.invalid_entity_attacked"));
+- ServerGamePacketListenerImpl.LOGGER
+- .warn("Player {} tried to attack an invalid entity", ServerGamePacketListenerImpl.this.player.getName().getString());
++ // CraftBukkit end
+ }
+ }
+ }
+@@ -1537,15 +2356,21 @@
+ @Override
+ 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
+ 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 == packet.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)) {
+ LOGGER.debug("Player {} interacted with invalid menu {}", this.player, this.player.containerMenu);
+@@ -1558,12 +2383,122 @@
+ } else {
+ boolean flag = packet.getStateId() != this.player.containerMenu.getStateId();
+ this.player.containerMenu.suppressRemoteUpdates();
+- this.player.containerMenu.clicked(slotNum, packet.getButtonNum(), packet.getClickType(), this.player);
++ // CraftBukkit start - Call InventoryClickEvent
++ if (packet.getSlotNum() < -1 && packet.getSlotNum() != -999) {
++ return;
++ }
+
+ for (Entry<ItemStack> entry : Int2ObjectMaps.fastIterable(packet.getChangedSlots())) {
+ this.player.containerMenu.setRemoteSlotNoCopy(entry.getIntKey(), entry.getValue());
+ }
+
++ 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();
++
++ this.player.containerMenu.setRemoteSlotNoCopy(entry.getIntKey(), (ItemStack) entry.getValue());
++ }
++
+ this.player.containerMenu.setRemoteCarried(packet.getCarriedItem());
+ this.player.containerMenu.resumeRemoteUpdates();
+ if (flag) {
+@@ -1586,13 +2690,18 @@
+ if (!this.player.containerMenu.stillValid(this.player)) {
+ LOGGER.debug("Player {} interacted with invalid menu {}", this.player, this.player.containerMenu);
+ } else {
+- this.server
+- .getRecipeManager()
+- .byKey(packet.getRecipe())
+- .ifPresent(
+- recipeHolder -> ((RecipeBookMenu)this.player.containerMenu)
+- .handlePlacement(packet.isShiftDown(), (RecipeHolder<?>)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
+ }
+ }
+ }
+@@ -1600,6 +2709,7 @@
+ @Override
+ 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 == packet.getContainerId() && !this.player.isSpectator()) {
+ if (!this.player.containerMenu.stillValid(this.player)) {
+@@ -1635,7 +2751,45 @@
+ }
+
+ boolean flag1 = packet.getSlotNum() >= 1 && packet.getSlotNum() <= 45;
+- boolean flag2 = item.isEmpty() || item.getDamageValue() >= 0 && item.getCount() <= 64 && !item.isEmpty();
++ 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(packet.getSlotNum()).setByPlayer(item);
+ this.player.inventoryMenu.broadcastChanges();
+@@ -1653,6 +2811,7 @@
+ }
+
+ private void updateSignText(ServerboundSignUpdatePacket packet, List<FilteredText> filteredText) {
++ if (this.player.isImmobile()) return; // CraftBukkit
+ this.player.resetLastActionTime();
+ ServerLevel serverLevel = this.player.serverLevel();
+ BlockPos pos = packet.getPos();
+@@ -1668,7 +2833,17 @@
+ @Override
+ public void handlePlayerAbilities(ServerboundPlayerAbilitiesPacket packet) {
+ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
+- this.player.getAbilities().flying = packet.isFlying() && this.player.getAbilities().mayfly;
++ // 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
+@@ -1724,8 +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-vineflower-stripped/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch
new file mode 100644
index 0000000000..b4887ad7c6
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch
@@ -0,0 +1,65 @@
+--- a/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java
++++ b/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java
+@@ -10,7 +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;
+@@ -22,9 +33,44 @@
+
+ @Override
+ 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);
++ // CraftBukkit start - Connection throttle
++ try {
++ long currentTime = System.currentTimeMillis();
++ long connectionThrottle = this.server.server.getConnectionThrottle();
++ InetAddress address = ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getAddress();
++
++ 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()) {
+ Component component;
+ if (packet.protocolVersion() < 754) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch
new file mode 100644
index 0000000000..4f1171a127
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch
@@ -0,0 +1,161 @@
+--- 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 {
+ private static final AtomicInteger UNIQUE_THREAD_ID = new AtomicInteger(0);
+@@ -54,7 +60,8 @@
+ String requestedUsername;
+ @Nullable
+ private GameProfile authenticatedProfile;
+- private final String serverId = "";
++ private final String serverId;
++ private ServerPlayer player; // CraftBukkit
+
+ public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection) {
+ this.server = server;
+@@ -78,6 +87,13 @@
+ }
+ }
+
++ // CraftBukkit start
++ @Deprecated
++ public void disconnect(String s) {
++ disconnect(Component.literal(s));
++ }
++ // CraftBukkit end
++
+ @Override
+ public boolean isAcceptingMessages() {
+ return this.connection.isConnected();
+@@ -130,11 +150,14 @@
+ this.state = ServerLoginPacketListenerImpl.State.VERIFYING;
+ }
+
+- private void verifyLoginAndFinishConnectionSetup(GameProfile gameProfile) {
+- PlayerList playerList = this.server.getPlayerList();
+- Component component = playerList.canPlayerLogin(this.connection.getRemoteAddress(), gameProfile);
+- if (component != null) {
+- this.disconnect(component);
++ private void verifyLoginAndFinishConnectionSetup(GameProfile gameprofile) {
++ PlayerList playerlist = this.server.getPlayerList();
++ // CraftBukkit start - fire PlayerLoginEvent
++ this.player = playerlist.canPlayerLogin(this, gameprofile); // CraftBukkit
++
++ if (this.player == null) {
++ // this.disconnect(ichatbasecomponent);
++ // CraftBukkit end
+ } else {
+ if (this.server.getCompressionThreshold() >= 0 && !this.connection.isMemoryConnection()) {
+ this.connection
+@@ -144,7 +165,8 @@
+ );
+ }
+
+- 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;
+ } else {
+@@ -185,13 +210,50 @@
+ String string1 = Objects.requireNonNull(ServerLoginPacketListenerImpl.this.requestedUsername, "Player name not initialized");
+
+ try {
+- ProfileResult profileResult = ServerLoginPacketListenerImpl.this.server
+- .getSessionService()
+- .hasJoinedServer(string1, string, this.getAddress());
+- if (profileResult != null) {
+- GameProfile gameProfile = profileResult.profile();
+- ServerLoginPacketListenerImpl.LOGGER.info("UUID of player {} is {}", gameProfile.getName(), gameProfile.getId());
+- ServerLoginPacketListenerImpl.this.startClientVerification(gameProfile);
++ ProfileResult profileresult = ServerLoginPacketListenerImpl.this.server.getSessionService().hasJoinedServer(s1, s, this.getAddress());
++
++ 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()) {
+ ServerLoginPacketListenerImpl.LOGGER.warn("Failed to verify username but will let them in anyway!");
+ ServerLoginPacketListenerImpl.this.startClientVerification(UUIDUtil.createOfflineProfile(string1));
+@@ -207,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
+ }
+ }
+
+@@ -228,15 +296,14 @@
+ }
+
+ @Override
+- public void handleLoginAcknowledgement(ServerboundLoginAcknowledgedPacket serverboundLoginAcknowledgedPacket) {
+- Validate.validState(this.state == ServerLoginPacketListenerImpl.State.PROTOCOL_SWITCHING, "Unexpected login acknowledgement packet");
+- CommonListenerCookie commonListenerCookie = CommonListenerCookie.createInitial(Objects.requireNonNull(this.authenticatedProfile));
+- ServerConfigurationPacketListenerImpl serverConfigurationPacketListenerImpl = new ServerConfigurationPacketListenerImpl(
+- this.server, this.connection, commonListenerCookie
+- );
+- this.connection.setListener(serverConfigurationPacketListenerImpl);
+- serverConfigurationPacketListenerImpl.startConfiguration();
+- this.state = ServerLoginPacketListenerImpl.State.ACCEPTED;
++ public void handleLoginAcknowledgement(ServerboundLoginAcknowledgedPacket serverboundloginacknowledgedpacket) {
++ Validate.validState(this.state == ServerLoginPacketListenerImpl.EnumProtocolState.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, this.player); // CraftBukkit
++
++ this.connection.setListener(serverconfigurationpacketlistenerimpl);
++ serverconfigurationpacketlistenerimpl.startConfiguration();
++ this.state = ServerLoginPacketListenerImpl.EnumProtocolState.ACCEPTED;
+ }
+
+ @Override
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch
new file mode 100644
index 0000000000..706bb740d5
--- /dev/null
+++ b/patch-remap/mache-vineflower-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 {
+ private static final Component DISCONNECT_REASON = Component.translatable("multiplayer.status.request_handled");
+@@ -35,7 +48,101 @@
+ this.connection.disconnect(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-vineflower-stripped/net/minecraft/server/players/BanListEntry.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/players/BanListEntry.java.patch
new file mode 100644
index 0000000000..ef1bd2e41f
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/players/BanListEntry.java.patch
@@ -0,0 +1,34 @@
+--- a/net/minecraft/server/players/BanListEntry.java
++++ b/net/minecraft/server/players/BanListEntry.java
+@@ -26,7 +27,7 @@
+ }
+
+ protected BanListEntry(@Nullable T user, JsonObject entryData) {
+- super(user);
++ super(checkExpiry(user, entryData)); // CraftBukkit
+
+ Date date;
+ try {
+@@ -80,4 +83,22 @@
+ data.addProperty("expires", this.expires == null ? "forever" : DATE_FORMAT.format(this.expires));
+ data.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-vineflower-stripped/net/minecraft/server/players/GameProfileCache.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/players/GameProfileCache.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/players/GameProfileCache.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/players/OldUsersConverter.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/players/OldUsersConverter.java.patch
new file mode 100644
index 0000000000..e69f8d4e5e
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/players/OldUsersConverter.java.patch
@@ -0,0 +1,96 @@
+--- a/net/minecraft/server/players/OldUsersConverter.java
++++ b/net/minecraft/server/players/OldUsersConverter.java
+@@ -63,9 +86,9 @@
+ if (OLD_USERBANLIST.exists() && OLD_USERBANLIST.isFile()) {
+ if (userBanList.getFile().exists()) {
+ try {
+- userBanList.load();
+- } catch (IOException var6) {
+- LOGGER.warn("Could not load existing file {}", userBanList.getFile().getName(), var6);
++ gameprofilebanlist.load();
++ } catch (IOException ioexception) {
++ OldUsersConverter.LOGGER.warn("Could not load existing file {}", gameprofilebanlist.getFile().getName()); // CraftBukkit - don't print stacktrace
+ }
+ }
+
+@@ -118,9 +144,9 @@
+ if (OLD_IPBANLIST.exists() && OLD_IPBANLIST.isFile()) {
+ if (ipBanList.getFile().exists()) {
+ try {
+- ipBanList.load();
+- } catch (IOException var11) {
+- LOGGER.warn("Could not load existing file {}", ipBanList.getFile().getName(), var11);
++ ipbanlist.load();
++ } catch (IOException ioexception) {
++ OldUsersConverter.LOGGER.warn("Could not load existing file {}", ipbanlist.getFile().getName()); // CraftBukkit - don't print stacktrace
+ }
+ }
+
+@@ -154,9 +185,9 @@
+ if (OLD_OPLIST.exists() && OLD_OPLIST.isFile()) {
+ if (serverOpList.getFile().exists()) {
+ try {
+- serverOpList.load();
+- } catch (IOException var6) {
+- LOGGER.warn("Could not load existing file {}", serverOpList.getFile().getName(), var6);
++ oplist.load();
++ } catch (IOException ioexception) {
++ OldUsersConverter.LOGGER.warn("Could not load existing file {}", oplist.getFile().getName()); // CraftBukkit - don't print stacktrace
+ }
+ }
+
+@@ -198,9 +229,9 @@
+ if (OLD_WHITELIST.exists() && OLD_WHITELIST.isFile()) {
+ if (userWhiteList.getFile().exists()) {
+ try {
+- userWhiteList.load();
+- } catch (IOException var6) {
+- LOGGER.warn("Could not load existing file {}", userWhiteList.getFile().getName(), var6);
++ whitelist.load();
++ } catch (IOException ioexception) {
++ OldUsersConverter.LOGGER.warn("Could not load existing file {}", whitelist.getFile().getName()); // CraftBukkit - don't print stacktrace
+ }
+ }
+
+@@ -310,11 +346,36 @@
+ }
+ }
+
+- private void movePlayerFile(File file3, String oldFileName, String newFileName) {
+- File file4 = new File(worldPlayersDirectory, oldFileName + ".dat");
+- File file5 = new File(file3, newFileName + ".dat");
+- OldUsersConverter.ensureDirectoryExists(file3);
+- if (!file4.renameTo(file5)) {
++ private void movePlayerFile(File file, String oldFileName, String newFileName) {
++ File file5 = new File(file, oldFileName + ".dat");
++ File file6 = new File(file, newFileName + ".dat");
++
++ // 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 " + oldFileName);
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/players/PlayerList.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/players/PlayerList.java.patch
new file mode 100644
index 0000000000..8555b90bc5
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/players/PlayerList.java.patch
@@ -0,0 +1,968 @@
+--- a/net/minecraft/server/players/PlayerList.java
++++ b/net/minecraft/server/players/PlayerList.java
+@@ -98,6 +101,25 @@
+ import net.minecraft.world.scores.Team;
+ 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");
+ public static final File IPBANLIST_FILE = new File("banned-ips.json");
+@@ -109,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 = new UserBanList(USERBANLIST_FILE);
+- private final IpBanList ipBans = new IpBanList(IPBANLIST_FILE);
+- private final ServerOpList ops = new ServerOpList(OPLIST_FILE);
+- private final UserWhiteList whitelist = new UserWhiteList(WHITELIST_FILE);
+- private final Map<UUID, ServerStatsCounter> stats = Maps.newHashMap();
+- private final Map<UUID, PlayerAdvancements> advancements = Maps.newHashMap();
+- private final PlayerDataStorage playerIo;
++ private final UserBanList bans;
++ private final IpBanList ipBans;
++ private final ServerOpList ops;
++ private final UserWhiteList whitelist;
++ // 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;
+@@ -127,7 +152,23 @@
+ private static final boolean ALLOW_LOGOUTIVATOR = false;
+ private int sendAllPlayerInfoIn;
+
++ // 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);
++ // CraftBukkit start
++ // this.stats = Maps.newHashMap();
++ // this.advancements = Maps.newHashMap();
++ // CraftBukkit end
+ this.server = server;
+ this.registries = registries;
+ this.maxPlayers = maxPlayers;
+@@ -146,61 +189,60 @@
+ string = gameProfile.getName();
+ }
+
+- CompoundTag compoundTag = this.load(serverPlayer);
+- ResourceKey<Level> resourceKey = compoundTag != null
+- ? DimensionType.parseLegacy(new Dynamic<>(NbtOps.INSTANCE, compoundTag.get("Dimension"))).resultOrPartial(LOGGER::error).orElse(Level.OVERWORLD)
+- : Level.OVERWORLD;
+- ServerLevel level = this.server.getLevel(resourceKey);
+- ServerLevel serverLevel;
+- if (level == null) {
+- LOGGER.warn("Unknown respawn dimension {}, defaulting to overworld", resourceKey);
+- serverLevel = this.server.overworld();
++ CompoundTag nbttagcompound = this.load(entityplayer);
++ 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 (nbttagcompound != null) {
++ DataResult<ResourceKey<Level>> dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, nbttagcompound.get("Dimension"))); // CraftBukkit - decompile error
++ Logger logger = PlayerList.LOGGER;
++
++ Objects.requireNonNull(logger);
++ 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 {
+- serverLevel = level;
++ 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
+ }
+
+- serverPlayer.setServerLevel(serverLevel);
+- String loggableAddress = connection.getLoggableAddress(this.server.logIPs());
+- LOGGER.info(
+- "{}[{}] logged in with entity id {} at ({}, {}, {})",
+- serverPlayer.getName().getString(),
+- loggableAddress,
+- serverPlayer.getId(),
+- serverPlayer.getX(),
+- serverPlayer.getY(),
+- serverPlayer.getZ()
+- );
+- LevelData levelData = serverLevel.getLevelData();
+- serverPlayer.loadGameTypes(compoundTag);
+- ServerGamePacketListenerImpl serverGamePacketListenerImpl = new ServerGamePacketListenerImpl(
+- this.server, connection, serverPlayer, commonListenerCookie
+- );
+- GameRules gameRules = serverLevel.getGameRules();
+- boolean _boolean = gameRules.getBoolean(GameRules.RULE_DO_IMMEDIATE_RESPAWN);
+- boolean _boolean1 = gameRules.getBoolean(GameRules.RULE_REDUCEDDEBUGINFO);
+- boolean _boolean2 = gameRules.getBoolean(GameRules.RULE_LIMITED_CRAFTING);
+- serverGamePacketListenerImpl.send(
+- new ClientboundLoginPacket(
+- serverPlayer.getId(),
+- levelData.isHardcore(),
+- this.server.levelKeys(),
+- this.getMaxPlayers(),
+- this.viewDistance,
+- this.simulationDistance,
+- _boolean1,
+- !_boolean,
+- _boolean2,
+- serverPlayer.createCommonSpawnInfo(serverLevel)
+- )
+- );
+- 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(serverLevel.getScoreboard(), serverPlayer);
++ ResourceKey<Level> resourcekey1 = resourcekey;
++ ServerLevel worldserver = this.server.getLevel(resourcekey1);
++ ServerLevel worldserver1;
++
++ if (worldserver == null) {
++ PlayerList.LOGGER.warn("Unknown respawn dimension {}, defaulting to overworld", resourcekey1);
++ worldserver1 = this.server.overworld();
++ } else {
++ worldserver1 = worldserver;
++ }
++
++ entityplayer.setServerLevel(worldserver1);
++ String s1 = networkmanager.getLoggableAddress(this.server.logIPs());
++
++ // 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();
++
++ entityplayer.loadGameTypes(nbttagcompound);
++ ServerGamePacketListenerImpl playerconnection = new ServerGamePacketListenerImpl(this.server, networkmanager, entityplayer, commonlistenercookie);
++ GameRules gamerules = worldserver1.getGameRules();
++ boolean flag = gamerules.getBoolean(GameRules.RULE_DO_IMMEDIATE_RESPAWN);
++ boolean flag1 = gamerules.getBoolean(GameRules.RULE_REDUCEDDEBUGINFO);
++ boolean flag2 = gamerules.getBoolean(GameRules.RULE_LIMITED_CRAFTING);
++
++ 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;
+ if (serverPlayer.getGameProfile().getName().equalsIgnoreCase(string)) {
+@@ -208,6 +251,9 @@
+ } else {
+ mutableComponent = Component.translatable("multiplayer.player.joined.renamed", serverPlayer.getDisplayName(), string);
+ }
++ // 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());
+@@ -216,23 +262,81 @@
+ serverPlayer.sendServerStatus(status);
+ }
+
+- 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, serverLevel);
+- serverLevel.addNewPlayer(serverPlayer);
+- this.server.getCustomBossEvents().onPlayerConnect(serverPlayer);
++ // 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
+
+- for (MobEffectInstance mobEffectInstance : serverPlayer.getActiveEffects()) {
+- serverGamePacketListenerImpl.send(new ClientboundUpdateMobEffectPacket(serverPlayer.getId(), mobEffectInstance));
++ // 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;
+ }
+
+- if (compoundTag != null && compoundTag.contains("RootVehicle", 10)) {
+- CompoundTag compound = compoundTag.getCompound("RootVehicle");
+- Entity entity = EntityType.loadEntityRecursive(
+- compound.getCompound("Entity"), serverLevel, entity2 -> !serverLevel.addWithUUID(entity2) ? null : entity2
+- );
++ 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 mobeffect = (MobEffectInstance) iterator.next();
++
++ playerconnection.send(new ClientboundUpdateMobEffectPacket(entityplayer.getId(), mobeffect));
++ }
++
++ 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) {
+ UUID uUID;
+ if (compound.hasUUID("Attach")) {
+@@ -263,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 scoreboard, ServerPlayer player) {
+@@ -286,30 +415,31 @@
+ }
+
+ public void addWorldborderListener(ServerLevel level) {
++ if (playerIo != null) return; // CraftBukkit
+ level.getWorldBorder().addListener(new BorderChangeListener() {
+ @Override
+ public void onBorderSizeSet(WorldBorder border, double size) {
+- PlayerList.this.broadcastAll(new ClientboundSetBorderSizePacket(border));
++ PlayerList.this.broadcastAll(new ClientboundSetBorderSizePacket(border), border.world); // CraftBukkit
+ }
+
+ @Override
+- public void onBorderSizeLerping(WorldBorder border, double oldSize, double newSize, long time) {
+- PlayerList.this.broadcastAll(new ClientboundSetBorderLerpSizePacket(border));
++ public void onBorderSizeLerping(WorldBorder border, double oldSize, double d1, long newSize) {
++ PlayerList.this.broadcastAll(new ClientboundSetBorderLerpSizePacket(border), border.world); // CraftBukkit
+ }
+
+ @Override
+- public void onBorderCenterSet(WorldBorder border, double x, double z) {
+- PlayerList.this.broadcastAll(new ClientboundSetBorderCenterPacket(border));
++ public void onBorderCenterSet(WorldBorder border, double x, double d1) {
++ PlayerList.this.broadcastAll(new ClientboundSetBorderCenterPacket(border), border.world); // CraftBukkit
+ }
+
+ @Override
+ public void onBorderSetWarningTime(WorldBorder border, int warningTime) {
+- PlayerList.this.broadcastAll(new ClientboundSetBorderWarningDelayPacket(border));
++ PlayerList.this.broadcastAll(new ClientboundSetBorderWarningDelayPacket(border), border.world); // CraftBukkit
+ }
+
+ @Override
+ public void onBorderSetWarningBlocks(WorldBorder border, int warningBlocks) {
+- PlayerList.this.broadcastAll(new ClientboundSetBorderWarningDistancePacket(border));
++ PlayerList.this.broadcastAll(new ClientboundSetBorderWarningDistancePacket(border), border.world); // CraftBukkit
+ }
+
+ @Override
+@@ -338,68 +467,126 @@
+ }
+
+ protected void save(ServerPlayer player) {
++ if (!player.getBukkitEntity().isPersistent()) return; // CraftBukkit
+ this.playerIo.save(player);
+- ServerStatsCounter serverStatsCounter = this.stats.get(player.getUUID());
+- if (serverStatsCounter != null) {
+- serverStatsCounter.save();
++ ServerStatsCounter serverstatisticmanager = (ServerStatsCounter) player.getStats(); // CraftBukkit
++
++ if (serverstatisticmanager != null) {
++ serverstatisticmanager.save();
+ }
+
+- PlayerAdvancements playerAdvancements = this.advancements.get(player.getUUID());
+- if (playerAdvancements != null) {
+- playerAdvancements.save();
++ PlayerAdvancements advancementdataplayer = (PlayerAdvancements) player.getAdvancements(); // CraftBukkit
++
++ if (advancementdataplayer != null) {
++ advancementdataplayer.save();
+ }
+ }
+
+- public void remove(ServerPlayer player) {
+- ServerLevel serverLevel = player.serverLevel();
+- player.awardStat(Stats.LEAVE_GAME);
+- this.save(player);
+- if (player.isPassenger()) {
+- Entity rootVehicle = player.getRootVehicle();
+- if (rootVehicle.hasExactlyOnePlayerPassenger()) {
+- LOGGER.debug("Removing player mount");
+- player.stopRiding();
+- rootVehicle.getPassengersAndSelf().forEach(entity -> entity.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER));
++ public String remove(ServerPlayer entityplayer) { // CraftBukkit - return string
++ ServerLevel worldserver = entityplayer.serverLevel();
++
++ entityplayer.awardStat(Stats.LEAVE_GAME);
++
++ // 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");
++ entityplayer.stopRiding();
++ entity.getPassengersAndSelf().forEach((entity1) -> {
++ entity1.setRemoved(Entity.RemovalReason.UNLOADED_WITH_PLAYER);
++ });
+ }
+ }
+
+- player.unRide();
+- serverLevel.removePlayerImmediately(player, Entity.RemovalReason.UNLOADED_WITH_PLAYER);
+- player.getAdvancements().stopListening();
+- this.players.remove(player);
+- this.server.getCustomBossEvents().onPlayerDisconnect(player);
+- UUID uUID = player.getUUID();
+- ServerPlayer serverPlayer = this.playersByUUID.get(uUID);
+- if (serverPlayer == player) {
+- this.playersByUUID.remove(uUID);
+- this.stats.remove(uUID);
+- this.advancements.remove(uUID);
++ entityplayer.unRide();
++ worldserver.removePlayerImmediately(entityplayer, Entity.RemovalReason.UNLOADED_WITH_PLAYER);
++ entityplayer.getAdvancements().stopListening();
++ this.players.remove(entityplayer);
++ this.server.getCustomBossEvents().onPlayerDisconnect(entityplayer);
++ UUID uuid = entityplayer.getUUID();
++ ServerPlayer entityplayer1 = (ServerPlayer) this.playersByUUID.get(uuid);
++
++ if (entityplayer1 == entityplayer) {
++ this.playersByUUID.remove(uuid);
++ // CraftBukkit start
++ // this.stats.remove(uuid);
++ // this.advancements.remove(uuid);
++ // CraftBukkit end
+ }
+
+- this.broadcastAll(new ClientboundPlayerInfoRemovePacket(List.of(player.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) {
+- if (this.bans.isBanned(gameProfile)) {
+- UserBanListEntry userBanListEntry = this.bans.get(gameProfile);
+- MutableComponent mutableComponent = Component.translatable("multiplayer.disconnect.banned.reason", userBanListEntry.getReason());
+- if (userBanListEntry.getExpires() != null) {
+- mutableComponent.append(
+- Component.translatable("multiplayer.disconnect.banned.expiration", BAN_DATE_FORMAT.format(userBanListEntry.getExpires()))
+- );
++ // CraftBukkit start - Whole method, SocketAddress to LoginListener, added hostname to signature, return EntityPlayer
++ public ServerPlayer canPlayerLogin(ServerLoginPacketListenerImpl loginlistener, GameProfile gameprofile) {
++ MutableComponent ichatmutablecomponent;
++
++ // Moved from processLogin
++ UUID uuid = gameprofile.getId();
++ List<ServerPlayer> list = Lists.newArrayList();
++
++ ServerPlayer entityplayer;
++
++ for (int i = 0; i < this.players.size(); ++i) {
++ entityplayer = (ServerPlayer) this.players.get(i);
++ if (entityplayer.getUUID().equals(uuid)) {
++ list.add(entityplayer);
+ }
+
+- return mutableComponent;
+- } else if (!this.isWhiteListed(gameProfile)) {
+- return Component.translatable("multiplayer.disconnect.not_whitelisted");
+- } else if (this.ipBans.isBanned(socketAddress)) {
+- IpBanListEntry ipBanListEntry = this.ipBans.get(socketAddress);
+- MutableComponent mutableComponent = Component.translatable("multiplayer.disconnect.banned_ip.reason", ipBanListEntry.getReason());
+- if (ipBanListEntry.getExpires() != null) {
+- mutableComponent.append(
+- Component.translatable("multiplayer.disconnect.banned_ip.expiration", BAN_DATE_FORMAT.format(ipBanListEntry.getExpires()))
+- );
++ 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 mutableComponent;
+@@ -410,13 +621,18 @@
+ }
+ }
+
+- 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) {
+- UUID id = gameProfile.getId();
+- Set<ServerPlayer> set = Sets.newIdentityHashSet();
++ public boolean disconnectAllPlayersWithProfile(GameProfile gameprofile, ServerPlayer player) { // CraftBukkit - added EntityPlayer
++ /* CraftBukkit startMoved up
++ UUID uuid = gameprofile.getId();
++ Set<EntityPlayer> set = Sets.newIdentityHashSet();
++ Iterator iterator = this.players.iterator();
+
+ for (ServerPlayer serverPlayer : this.players) {
+ if (serverPlayer.getUUID().equals(id)) {
+@@ -434,44 +657,82 @@
+ }
+
+ return !set.isEmpty();
++ */
++ return player == null;
++ // CraftBukkit end
+ }
+
+- public ServerPlayer respawn(ServerPlayer player, boolean keepEverything) {
+- this.players.remove(player);
+- player.serverLevel().removePlayerImmediately(player, Entity.RemovalReason.DISCARDED);
+- BlockPos respawnPosition = player.getRespawnPosition();
+- float respawnAngle = player.getRespawnAngle();
+- boolean isRespawnForced = player.isRespawnForced();
+- ServerLevel level = this.server.getLevel(player.getRespawnDimension());
+- Optional<Vec3> optional;
+- if (level != null && respawnPosition != null) {
+- optional = Player.findRespawnPositionAndUseSpawnBlock(level, respawnPosition, respawnAngle, isRespawnForced, keepEverything);
++ // 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 (worldserver != null && blockposition != null) {
++ optional = EntityHuman.findRespawnPositionAndUseSpawnBlock(worldserver, blockposition, f, flag1, flag);
+ } else {
+ optional = Optional.empty();
+ }
+
+- ServerLevel serverLevel = level != null && optional.isPresent() ? level : this.server.overworld();
+- ServerPlayer serverPlayer = new ServerPlayer(this.server, serverLevel, player.getGameProfile(), player.clientInformation());
+- serverPlayer.connection = player.connection;
+- serverPlayer.restoreFrom(player, keepEverything);
+- serverPlayer.setId(player.getId());
+- serverPlayer.setMainArm(player.getMainArm());
++ 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
+
+ for (String string : player.getTags()) {
+ serverPlayer.addTag(string);
+ }
+
+- boolean flag = false;
+- if (optional.isPresent()) {
+- BlockState blockState = serverLevel.getBlockState(respawnPosition);
+- boolean isRespawnAnchor = blockState.is(Blocks.RESPAWN_ANCHOR);
+- Vec3 vec3 = optional.get();
+- float f;
+- if (!blockState.is(BlockTags.BEDS) && !isRespawnAnchor) {
+- f = respawnAngle;
+- } else {
+- Vec3 vec31 = Vec3.atBottomCenterOf(respawnPosition).subtract(vec3).normalize();
+- f = (float)Mth.wrapDegrees(Mth.atan2(vec31.z, vec31.x) * 180.0F / (float)Math.PI - 90.0);
++ boolean flag2 = false;
++
++ // CraftBukkit start - fire PlayerRespawnEvent
++ if (location == null) {
++ boolean isBedSpawn = false;
++ ServerLevel worldserver1 = this.server.getLevel(entityplayer.getRespawnDimension());
++ if (worldserver1 != null) {
++ Optional optional;
++
++ if (blockposition != null) {
++ optional = net.minecraft.world.entity.player.Player.findRespawnPositionAndUseSpawnBlock(worldserver1, blockposition, f, flag1, flag);
++ } else {
++ optional = Optional.empty();
++ }
++
++ 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
++ }
+ }
+
+ serverPlayer.moveTo(vec3.x, vec3.y, vec3.z, f, 0.0F);
+@@ -481,43 +764,43 @@
+ serverPlayer.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.NO_RESPAWN_BLOCK_AVAILABLE, 0.0F));
+ }
+
+- while (!serverLevel.noCollision(serverPlayer) && serverPlayer.getY() < (double)serverLevel.getMaxBuildHeight()) {
+- serverPlayer.setPos(serverPlayer.getX(), serverPlayer.getY() + 1.0, serverPlayer.getZ());
++ while (avoidSuffocation && !worldserver1.noCollision((Entity) entityplayer1) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) {
++ // CraftBukkit end
++ entityplayer1.setPos(entityplayer1.getX(), entityplayer1.getY() + 1.0D, entityplayer1.getZ());
+ }
+
+- byte b = (byte)(keepEverything ? 1 : 0);
+- ServerLevel serverLevel1 = serverPlayer.serverLevel();
+- LevelData levelData = serverLevel1.getLevelData();
+- serverPlayer.connection.send(new ClientboundRespawnPacket(serverPlayer.createCommonSpawnInfo(serverLevel1), b));
+- serverPlayer.connection.teleport(serverPlayer.getX(), serverPlayer.getY(), serverPlayer.getZ(), serverPlayer.getYRot(), serverPlayer.getXRot());
+- serverPlayer.connection.send(new ClientboundSetDefaultSpawnPositionPacket(serverLevel.getSharedSpawnPos(), serverLevel.getSharedSpawnAngle()));
+- serverPlayer.connection.send(new ClientboundChangeDifficultyPacket(levelData.getDifficulty(), levelData.isDifficultyLocked()));
+- serverPlayer.connection
+- .send(new ClientboundSetExperiencePacket(serverPlayer.experienceProgress, serverPlayer.totalExperience, serverPlayer.experienceLevel));
+- this.sendLevelInfo(serverPlayer, serverLevel);
+- this.sendPlayerPermissionLevel(serverPlayer);
+- serverLevel.addRespawnedPlayer(serverPlayer);
+- this.players.add(serverPlayer);
+- this.playersByUUID.put(serverPlayer.getUUID(), serverPlayer);
+- serverPlayer.initInventoryMenu();
+- serverPlayer.setHealth(serverPlayer.getHealth());
+- if (flag) {
+- serverPlayer.connection
+- .send(
+- new ClientboundSoundPacket(
+- SoundEvents.RESPAWN_ANCHOR_DEPLETE,
+- SoundSource.BLOCKS,
+- (double)respawnPosition.getX(),
+- (double)respawnPosition.getY(),
+- (double)respawnPosition.getZ(),
+- 1.0F,
+- 1.0F,
+- serverLevel.getRandom().nextLong()
+- )
+- );
++ int i = flag ? 1 : 0;
++ ServerLevel worldserver2 = entityplayer1.serverLevel();
++ LevelData worlddata = worldserver2.getLevelData();
++
++ 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);
+ }
+
+- return serverPlayer;
++ // 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 player) {
+@@ -528,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;
+ }
+ }
+@@ -539,6 +851,25 @@
+ }
+ }
+
++ // 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) {
+ for (ServerPlayer serverPlayer : this.players) {
+ if (serverPlayer.level().dimension() == dimension) {
+@@ -621,6 +972,7 @@
+ player.connection.send(new ClientboundEntityEventPacket(player, b));
+ }
+
++ player.getBukkitEntity().recalculatePermissions(); // CraftBukkit
+ this.server.getCommands().sendCommands(player);
+ }
+
+@@ -648,15 +999,23 @@
+ return null;
+ }
+
+- public void broadcast(@Nullable Player except, double x, double y, double z, double radius, ResourceKey<Level> dimension, Packet<?> packet) {
+- for (int i = 0; i < this.players.size(); i++) {
+- ServerPlayer serverPlayer = this.players.get(i);
+- if (serverPlayer != except && serverPlayer.level().dimension() == dimension) {
+- double d = x - serverPlayer.getX();
+- double d1 = y - serverPlayer.getY();
+- double d2 = z - serverPlayer.getZ();
+- if (d * d + d1 * d1 + d2 * d2 < radius * radius) {
+- serverPlayer.connection.send(packet);
++ public void broadcast(@Nullable net.minecraft.world.entity.player.Player except, double x, double d1, double y, double d3, ResourceKey<Level> z, Packet<?> packet) {
++ for (int i = 0; i < this.players.size(); ++i) {
++ ServerPlayer entityplayer = (ServerPlayer) this.players.get(i);
++
++ // 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 (entityplayer != except && entityplayer.level().dimension() == z) {
++ double d4 = x - entityplayer.getX();
++ double d5 = d1 - entityplayer.getY();
++ double d6 = y - entityplayer.getZ();
++
++ if (d4 * d4 + d5 * d5 + d6 * d6 < d3 * d3) {
++ entityplayer.connection.send(packet);
+ }
+ }
+ }
+@@ -688,14 +1048,19 @@
+ }
+
+ public void sendLevelInfo(ServerPlayer player, ServerLevel level) {
+- WorldBorder worldBorder = this.server.overworld().getWorldBorder();
+- player.connection.send(new ClientboundInitializeBorderPacket(worldBorder));
++ WorldBorder worldborder = player.level().getWorldBorder(); // CraftBukkit
++
++ 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()) {
+- player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0.0F));
+- player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, level.getRainLevel(1.0F)));
+- player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, level.getThunderLevel(1.0F)));
++ // 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
+ }
+
+ player.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.LEVEL_CHUNKS_LOAD_START, 0.0F));
+@@ -704,8 +1069,16 @@
+
+ public void sendAllPlayerInfo(ServerPlayer player) {
+ player.inventoryMenu.sendAllDataToRemote();
+- player.resetSentInfo();
++ // 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() {
+@@ -758,11 +1134,22 @@
+ }
+
+ public void removeAll() {
+- for (int i = 0; i < this.players.size(); i++) {
+- 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
++
+ }
+
++ // CraftBukkit start
++ public void broadcastMessage(Component[] iChatBaseComponents) {
++ for (Component component : iChatBaseComponents) {
++ broadcastSystemMessage(component, false);
++ }
++ }
++ // CraftBukkit end
++
+ public void broadcastSystemMessage(Component message, boolean bypassHiddenChat) {
+ this.broadcastSystemMessage(message, serverPlayer -> message, bypassHiddenChat);
+ }
+@@ -809,34 +1207,44 @@
+ return message.hasSignature() && !message.hasExpiredServer(Instant.now());
+ }
+
+- public ServerStatsCounter getPlayerStats(Player player) {
+- UUID uUID = player.getUUID();
+- ServerStatsCounter serverStatsCounter = this.stats.get(uUID);
+- if (serverStatsCounter == null) {
++ // CraftBukkit start
++ public ServerStatsCounter getPlayerStats(ServerPlayer entityhuman) {
++ ServerStatsCounter serverstatisticmanager = entityhuman.getStats();
++ return serverstatisticmanager == null ? getPlayerStats(entityhuman.getUUID(), entityhuman.getDisplayName().getString()) : serverstatisticmanager;
++ }
++
++ 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()) {
+ file2.renameTo(file1);
+ }
+ }
+
+- 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 player) {
+- UUID uUID = player.getUUID();
+- PlayerAdvancements playerAdvancements = this.advancements.get(uUID);
+- 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, player);
+- this.advancements.put(uUID, playerAdvancements);
++ UUID uuid = player.getUUID();
++ PlayerAdvancements advancementdataplayer = (PlayerAdvancements) player.getAdvancements(); // CraftBukkit
++
++ if (advancementdataplayer == null) {
++ Path path = this.server.getWorldPath(LevelResource.PLAYER_ADVANCEMENTS_DIR).resolve(uuid + ".json");
++
++ advancementdataplayer = new PlayerAdvancements(this.server.getFixerUpper(), this, this.server.getAdvancements(), path, player);
++ // this.advancements.put(uuid, advancementdataplayer); // CraftBukkit
+ }
+
+ playerAdvancements.setPlayer(player);
+@@ -879,9 +1297,20 @@
+ }
+
+ public void reloadResources() {
+- for (PlayerAdvancements playerAdvancements : this.advancements.values()) {
+- playerAdvancements.reload(this.server.getAdvancements());
++ // CraftBukkit start
++ /*Iterator iterator = this.advancements.values().iterator();
++
++ while (iterator.hasNext()) {
++ AdvancementDataPlayer advancementdataplayer = (AdvancementDataPlayer) iterator.next();
++
++ 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-vineflower-stripped/net/minecraft/server/players/SleepStatus.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/players/SleepStatus.java.patch
new file mode 100644
index 0000000000..ff7a4138c0
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/players/SleepStatus.java.patch
@@ -0,0 +1,49 @@
+--- a/net/minecraft/server/players/SleepStatus.java
++++ b/net/minecraft/server/players/SleepStatus.java
+@@ -14,8 +18,12 @@
+ }
+
+ public boolean areEnoughDeepSleeping(int requiredSleepPercentage, List<ServerPlayer> sleepingPlayers) {
+- int i = (int)sleepingPlayers.stream().filter(Player::isSleepingLongEnough).count();
+- return i >= this.sleepersNeeded(requiredSleepPercentage);
++ // CraftBukkit start
++ int j = (int) sleepingPlayers.stream().filter((eh) -> { return eh.isSleepingLongEnough() || eh.fauxSleeping; }).count();
++ boolean anyDeepSleep = sleepingPlayers.stream().anyMatch(Player::isSleepingLongEnough);
++
++ return anyDeepSleep && j >= this.sleepersNeeded(requiredSleepPercentage);
++ // CraftBukkit end
+ }
+
+ public int sleepersNeeded(int requiredSleepPercentage) {
+@@ -35,16 +44,25 @@
+ int i1 = this.sleepingPlayers;
+ this.activePlayers = 0;
+ this.sleepingPlayers = 0;
++ Iterator iterator = players.iterator();
++ boolean anySleep = false; // CraftBukkit
+
+- for (ServerPlayer serverPlayer : players) {
+- if (!serverPlayer.isSpectator()) {
+- this.activePlayers++;
+- if (serverPlayer.isSleeping()) {
+- this.sleepingPlayers++;
++ while (iterator.hasNext()) {
++ ServerPlayer entityplayer = (ServerPlayer) iterator.next();
++
++ if (!entityplayer.isSpectator()) {
++ ++this.activePlayers;
++ if (entityplayer.isSleeping() || entityplayer.fauxSleeping) { // CraftBukkit
++ ++this.sleepingPlayers;
+ }
++ // CraftBukkit start
++ if (entityplayer.isSleeping()) {
++ anySleep = true;
++ }
++ // CraftBukkit end
+ }
+ }
+
+- return (i1 > 0 || this.sleepingPlayers > 0) && (i != this.activePlayers || i1 != this.sleepingPlayers);
++ return anySleep && (j > 0 || this.sleepingPlayers > 0) && (i != this.activePlayers || j != this.sleepingPlayers); // CraftBukkit
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/players/StoredUserList.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/players/StoredUserList.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/players/StoredUserList.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/players/UserBanListEntry.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/players/UserBanListEntry.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/players/UserBanListEntry.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/rcon/RconConsoleSource.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/rcon/RconConsoleSource.java.patch
new file mode 100644
index 0000000000..90dbb187e4
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/rcon/RconConsoleSource.java.patch
@@ -0,0 +1,48 @@
+--- a/net/minecraft/server/rcon/RconConsoleSource.java
++++ b/net/minecraft/server/rcon/RconConsoleSource.java
+@@ -7,15 +8,23 @@
+ import net.minecraft.server.level.ServerLevel;
+ 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 server) {
+- this.server = server;
++ public RconConsoleSource(MinecraftServer minecraftserver, SocketAddress socketAddress) {
++ this.socketAddress = socketAddress;
++ // CraftBukkit end
++ this.server = minecraftserver;
+ }
+
+ public void prepareForCommand() {
+@@ -33,7 +42,18 @@
+ );
+ }
+
++ // 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-vineflower-stripped/net/minecraft/server/rcon/thread/RconClient.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/rcon/thread/RconClient.java.patch
new file mode 100644
index 0000000000..61ad71d139
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/rcon/thread/RconClient.java.patch
@@ -0,0 +1,83 @@
+--- 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;
+
+@@ -23,11 +27,14 @@
+ 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 rconPassword, Socket client) {
+ super("RCON Client " + client.getInetAddress());
+- this.serverInterface = serverInterface;
++ this.serverInterface = (DedicatedServer) serverInterface; // CraftBukkit
+ this.client = client;
+
+ try {
+@@ -37,18 +44,17 @@
+ }
+
+ this.rconPassword = rconPassword;
++ this.rconConsoleSource = new net.minecraft.server.rcon.RconConsoleSource(this.serverInterface, client.getRemoteSocketAddress()); // CraftBukkit
+ }
+
+ @Override
+ public void run() {
+ try {
+- try {
+- while (this.running) {
+- BufferedInputStream bufferedInputStream = new BufferedInputStream(this.client.getInputStream());
+- int i = bufferedInputStream.read(this.buf, 0, 1460);
+- if (10 > i) {
+- return;
+- }
++ while (true) {
++ // CraftBukkit end
++ if (!this.running) {
++ return;
++ }
+
+ int i1 = 0;
+ int i2 = PktUtils.intFromByteArray(this.buf, 0, i);
+@@ -67,9 +80,9 @@
+ String string1 = PktUtils.stringFromByteArray(this.buf, var21, i);
+
+ try {
+- this.sendCmdResponse(i3, this.serverInterface.runCommand(string1));
+- } catch (Exception var15) {
+- this.sendCmdResponse(i3, "Error executing: " + string1 + " (" + var15.getMessage() + ")");
++ this.sendCmdResponse(l, this.serverInterface.runCommand(this.rconConsoleSource, s)); // CraftBukkit
++ } catch (Exception exception) {
++ this.sendCmdResponse(l, "Error executing: " + s + " (" + exception.getMessage() + ")");
+ }
+ break;
+ }
+@@ -98,11 +118,11 @@
+ } catch (Exception var17) {
+ LOGGER.error("Exception whilst parsing RCON input", (Throwable)var17);
+ }
+- } finally {
+- this.closeSocket();
+- LOGGER.info("Thread {} shutting down", this.name);
+- this.running = false;
+- }
++
++ // CraftBukkit start - decompile error: switch try / while statement
++ // return;
++ // }
++ // CraftBukkit end
+ }
+
+ private void send(int id, int i, String message) throws IOException {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/stats/ServerRecipeBook.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/stats/ServerRecipeBook.java.patch
new file mode 100644
index 0000000000..9be367b6e9
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/stats/ServerRecipeBook.java.patch
@@ -0,0 +1,36 @@
+--- a/net/minecraft/stats/ServerRecipeBook.java
++++ b/net/minecraft/stats/ServerRecipeBook.java
+@@ -19,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";
+ private static final Logger LOGGER = LogUtils.getLogger();
+@@ -27,14 +34,16 @@
+ List<ResourceLocation> list = Lists.newArrayList();
+ int i = 0;
+
+- for (RecipeHolder<?> recipeHolder : recipes) {
+- 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(player, recipeHolder);
+- i++;
++ while (iterator.hasNext()) {
++ RecipeHolder<?> recipeholder = (RecipeHolder) iterator.next();
++ ResourceLocation minecraftkey = recipeholder.id();
++
++ 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-vineflower-stripped/net/minecraft/stats/ServerStatsCounter.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/stats/ServerStatsCounter.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/stats/ServerStatsCounter.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/stats/StatsCounter.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/stats/StatsCounter.java.patch
new file mode 100644
index 0000000000..b35df579f8
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/stats/StatsCounter.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/stats/StatsCounter.java
++++ b/net/minecraft/stats/StatsCounter.java
+@@ -13,8 +14,15 @@
+ }
+
+ public void increment(Player player, Stat<?> stat, int amount) {
+- int i = (int)Math.min((long)this.getValue(stat) + (long)amount, 2147483647L);
+- this.setValue(player, stat, i);
++ int j = (int) Math.min((long) this.getValue(stat) + (long) amount, 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);
+ }
+
+ public void setValue(Player player, Stat<?> stat, int value) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/util/SpawnUtil.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/util/SpawnUtil.java.patch
new file mode 100644
index 0000000000..1dd16178a2
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/util/SpawnUtil.java.patch
@@ -0,0 +1,42 @@
+--- a/net/minecraft/util/SpawnUtil.java
++++ b/net/minecraft/util/SpawnUtil.java
+@@ -20,16 +18,29 @@
+ ) {
+ BlockPos.MutableBlockPos mutableBlockPos = pos.mutable();
+
+- for (int i1 = 0; i1 < attempts; i1++) {
+- int i2 = Mth.randomBetweenInclusive(level.random, -i, i);
+- int i3 = Mth.randomBetweenInclusive(level.random, -i, i);
+- mutableBlockPos.setWithOffset(pos, i2, yOffset, i3);
+- if (level.getWorldBorder().isWithinBounds(mutableBlockPos) && moveToPossibleSpawnPosition(level, yOffset, mutableBlockPos, strategy)) {
+- T mob = (T)entityType.create(level, null, null, mutableBlockPos, spawnType, false, false);
+- if (mob != null) {
+- if (mob.checkSpawnRules(level, spawnType) && mob.checkSpawnObstruction(level)) {
+- level.addFreshEntityWithPassengers(mob);
+- return Optional.of(mob);
++ public SpawnUtil() {}
++
++ 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(worldserver.random, -j, j);
++ int j1 = Mth.randomBetweenInclusive(worldserver.random, -j, j);
++
++ blockposition_mutableblockposition.setWithOffset(blockposition, i1, k, j1);
++ if (worldserver.getWorldBorder().isWithinBounds((BlockPos) blockposition_mutableblockposition) && moveToPossibleSpawnPosition(worldserver, k, blockposition_mutableblockposition, spawnutil_a)) {
++ T t0 = entitytypes.create(worldserver, (CompoundTag) null, null, blockposition_mutableblockposition, enummobspawn, false, false); // CraftBukkit - decompile error
++
++ if (t0 != null) {
++ if (t0.checkSpawnRules(worldserver, enummobspawn) && t0.checkSpawnObstruction(worldserver)) {
++ worldserver.addFreshEntityWithPassengers(t0, reason); // CraftBukkit
++ return Optional.of(t0);
+ }
+
+ mob.discard();
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/util/datafix/DataFixers.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/util/datafix/DataFixers.java.patch
new file mode 100644
index 0000000000..c9f3a2ae67
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/util/datafix/DataFixers.java.patch
@@ -0,0 +1,24 @@
+--- a/net/minecraft/util/datafix/DataFixers.java
++++ b/net/minecraft/util/datafix/DataFixers.java
+@@ -442,7 +277,20 @@
+ builder.addFixer(new VillagerTradeFix(schema43, false));
+ Schema schema44 = builder.addSchema(1456, SAME_NAMESPACED);
+ builder.addFixer(new EntityItemFrameDirectionFix(schema44, false));
+- Schema schema45 = builder.addSchema(1458, SAME_NAMESPACED);
++ Schema schema45 = builder.addSchema(1458, DataFixers.SAME_NAMESPACED);
++
++ // 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));
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java.patch
new file mode 100644
index 0000000000..f2a44f3590
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java.patch
@@ -0,0 +1,34 @@
+--- a/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java
++++ b/net/minecraft/util/datafix/fixes/ItemStackMapIdFix.java
+@@ -20,17 +20,20 @@
+
+ @Override
+ public TypeRewriteRule makeRule() {
+- Type<?> type = this.getInputSchema().getType(References.ITEM_STACK);
+- OpticFinder<Pair<String, String>> opticFinder = DSL.fieldFinder("id", DSL.named(References.ITEM_NAME.typeName(), NamespacedSchema.namespacedString()));
+- OpticFinder<?> opticFinder1 = type.findField("tag");
+- return this.fixTypeEverywhereTyped("ItemInstanceMapIdFix", type, typed -> {
+- Optional<Pair<String, String>> optional = typed.getOptional(opticFinder);
+- if (optional.isPresent() && Objects.equals(optional.get().getSecond(), "minecraft:filled_map")) {
+- Dynamic<?> dynamic = typed.get(DSL.remainderFinder());
+- Typed<?> typed1 = typed.getOrCreateTyped(opticFinder1);
+- Dynamic<?> dynamic1 = typed1.get(DSL.remainderFinder());
+- dynamic1 = dynamic1.set("map", dynamic1.createInt(dynamic.get("Damage").asInt(0)));
+- return typed.set(opticFinder1, typed1.set(DSL.remainderFinder(), dynamic1));
++ Type<?> type = this.getInputSchema().getType(DataConverterTypes.ITEM_STACK);
++ OpticFinder<Pair<String, String>> opticfinder = DSL.fieldFinder("id", DSL.named(DataConverterTypes.ITEM_NAME.typeName(), NamespacedSchema.namespacedString()));
++ OpticFinder<?> opticfinder1 = type.findField("tag");
++
++ return this.fixTypeEverywhereTyped("ItemInstanceMapIdFix", type, (typed) -> {
++ Optional<Pair<String, String>> optional = typed.getOptional(opticfinder);
++
++ if (optional.isPresent() && Objects.equals(((Pair) optional.get()).getSecond(), "minecraft:filled_map")) {
++ Dynamic<?> dynamic = (Dynamic) typed.get(DSL.remainderFinder());
++ Typed<?> typed1 = typed.getOrCreateTyped(opticfinder1);
++ Dynamic<?> dynamic1 = (Dynamic) typed1.get(DSL.remainderFinder());
++
++ 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-vineflower-stripped/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java.patch
new file mode 100644
index 0000000000..cb554a827d
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java
++++ b/net/minecraft/util/datafix/fixes/ItemStackTheFlatteningFix.java
+@@ -421,11 +372,12 @@
+ typed1 = typed.set(opticFinder, Pair.of(References.ITEM_NAME.typeName(), string));
+ }
+
+- if (DAMAGE_IDS.contains(optional.get().getSecond())) {
+- Typed<?> typed2 = typed.getOrCreateTyped(opticFinder1);
+- Dynamic<?> dynamic1 = typed2.get(DSL.remainderFinder());
+- dynamic1 = dynamic1.set("Damage", dynamic1.createInt(_int));
+- typed1 = typed1.set(opticFinder1, typed2.set(DSL.remainderFinder(), dynamic1));
++ if (ItemStackTheFlatteningFix.DAMAGE_IDS.contains(((Pair) optional.get()).getSecond())) {
++ Typed<?> typed2 = typed.getOrCreateTyped(opticfinder1);
++ Dynamic<?> dynamic1 = (Dynamic) typed2.get(DSL.remainderFinder());
++
++ if (i != 0) dynamic1 = dynamic1.set("Damage", dynamic1.createInt(i)); // CraftBukkit
++ typed1 = typed1.set(opticfinder1, typed2.set(DSL.remainderFinder(), dynamic1));
+ }
+
+ return typed1.set(DSL.remainderFinder(), dynamic.remove("Damage"));
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/util/worldupdate/WorldUpgrader.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/util/worldupdate/WorldUpgrader.java.patch
new file mode 100644
index 0000000000..1717a9783f
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/util/worldupdate/WorldUpgrader.java.patch
@@ -0,0 +1,40 @@
+--- a/net/minecraft/util/worldupdate/WorldUpgrader.java
++++ b/net/minecraft/util/worldupdate/WorldUpgrader.java
+@@ -62,7 +66,7 @@
+
+ public WorldUpgrader(LevelStorageSource.LevelStorageAccess levelStoarge, DataFixer dataFixer, Registry<LevelStem> dimensions, boolean eraseCache) {
+ this.dimensions = dimensions;
+- this.levels = dimensions.registryKeySet().stream().map(Registries::levelStemToLevel).collect(Collectors.toUnmodifiableSet());
++ 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;
+@@ -123,16 +140,18 @@
+ boolean flag1 = false;
+
+ try {
+- CompoundTag compoundTag = chunkStorage.read(chunkPos).join().orElse(null);
+- if (compoundTag != null) {
+- int version = ChunkStorage.getVersion(compoundTag);
+- ChunkGenerator chunkGenerator = this.dimensions.getOrThrow(Registries.levelToLevelStem(resourceKey2)).generator();
+- CompoundTag compoundTag1 = chunkStorage.upgradeChunkTag(
+- resourceKey2, () -> this.overworldDataStorage, compoundTag, chunkGenerator.getTypeNameForDataFixer()
+- );
+- ChunkPos chunkPos1 = new ChunkPos(compoundTag1.getInt("xPos"), compoundTag1.getInt("zPos"));
+- if (!chunkPos1.equals(chunkPos)) {
+- LOGGER.warn("Chunk {} has invalid position {}", chunkPos, chunkPos1);
++ CompoundTag nbttagcompound = (CompoundTag) ((Optional) ichunkloader.read(chunkcoordintpair).join()).orElse((Object) null);
++
++ if (nbttagcompound != null) {
++ int j = ChunkStorage.getVersion(nbttagcompound);
++ ChunkGenerator chunkgenerator = ((LevelStem) this.dimensions.getOrThrow(Registries.levelToLevelStem(resourcekey2))).generator();
++ CompoundTag nbttagcompound1 = ichunkloader.upgradeChunkTag(Registries.levelToLevelStem(resourcekey2), () -> { // CraftBukkit
++ return this.overworldDataStorage;
++ }, nbttagcompound, chunkgenerator.getTypeNameForDataFixer(), chunkcoordintpair, null); // CraftBukkit
++ ChunkPos chunkcoordintpair1 = new ChunkPos(nbttagcompound1.getInt("xPos"), nbttagcompound1.getInt("zPos"));
++
++ if (!chunkcoordintpair1.equals(chunkcoordintpair)) {
++ WorldUpgrader.LOGGER.warn("Chunk {} has invalid position {}", chunkcoordintpair, chunkcoordintpair1);
+ }
+
+ boolean flag2 = version < SharedConstants.getCurrentVersion().getDataVersion().getVersion();
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/CompoundContainer.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/CompoundContainer.java.patch
new file mode 100644
index 0000000000..e378d5fcef
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/CompoundContainer.java.patch
@@ -0,0 +1,76 @@
+--- a/net/minecraft/world/CompoundContainer.java
++++ b/net/minecraft/world/CompoundContainer.java
+@@ -3,10 +3,64 @@
+ 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 final Container container1;
++ public final Container container2;
++
++ // 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;
++ }
++
++ public void onOpen(CraftHumanEntity who) {
++ this.container1.onOpen(who);
++ this.container2.onOpen(who);
++ transaction.add(who);
++ }
++
++ public void onClose(CraftHumanEntity who) {
++ this.container1.onClose(who);
++ this.container2.onClose(who);
++ transaction.remove(who);
++ }
++
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ public org.bukkit.inventory.InventoryHolder getOwner() {
++ return null; // This method won't be called since CraftInventoryDoubleChest doesn't defer to here
++ }
++
++ public void setMaxStackSize(int size) {
++ this.container1.setMaxStackSize(size);
++ this.container2.setMaxStackSize(size);
++ }
++
++ @Override
++ public Location getLocation() {
++ return container1.getLocation(); // TODO: right?
++ }
++ // CraftBukkit end
++
+ public CompoundContainer(Container container1, Container container2) {
+ this.container1 = container1;
+ this.container2 = container2;
+@@ -58,7 +105,7 @@
+
+ @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-vineflower-stripped/net/minecraft/world/Container.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/Container.java.patch
new file mode 100644
index 0000000000..032b8dcb9b
--- /dev/null
+++ b/patch-remap/mache-vineflower-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 {
+ int LARGE_MAX_STACK_SIZE = 64;
+@@ -25,9 +30,7 @@
+
+ void setItem(int slot, ItemStack stack);
+
+- default int getMaxStackSize() {
+- return 64;
+- }
++ int getMaxStackSize(); // CraftBukkit
+
+ void setChanged();
+
+@@ -87,4 +90,29 @@
+ && player.distanceToSqr((double)blockPos.getX() + 0.5, (double)blockPos.getY() + 0.5, (double)blockPos.getZ() + 0.5)
+ <= (double)(maxDistance * maxDistance);
+ }
++
++ // 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-vineflower-stripped/net/minecraft/world/LockCode.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/LockCode.java.patch
new file mode 100644
index 0000000000..a5241b88c9
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/LockCode.java.patch
@@ -0,0 +1,35 @@
+--- 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 {
+ public static final LockCode NO_LOCK = new LockCode("");
+@@ -15,7 +21,19 @@
+ }
+
+ public boolean unlocksWith(ItemStack stack) {
+- return this.key.isEmpty() || !stack.isEmpty() && stack.hasCustomHoverName() && this.key.equals(stack.getHoverName().getString());
++ // 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 nbt) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/SimpleContainer.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/SimpleContainer.java.patch
new file mode 100644
index 0000000000..ad5df6ac40
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/SimpleContainer.java.patch
@@ -0,0 +1,79 @@
+--- a/net/minecraft/world/SimpleContainer.java
++++ b/net/minecraft/world/SimpleContainer.java
+@@ -13,17 +14,76 @@
+ 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;
+ private final NonNullList<ItemStack> items;
+ @Nullable
+ private List<ContainerListener> listeners;
+
++ // 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 = size;
+ this.items = NonNullList.withSize(size, ItemStack.EMPTY);
+ }
+
++ public SimpleContainer(int i, org.bukkit.inventory.InventoryHolder owner) {
++ this.bukkitOwner = owner;
++ // CraftBukkit end
++ this.size = i;
++ this.items = NonNullList.withSize(i, ItemStack.EMPTY);
++ }
++
+ public SimpleContainer(ItemStack... items) {
+ this.size = items.length;
+ this.items = NonNullList.of(ItemStack.EMPTY, items);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/damagesource/DamageSource.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/damagesource/DamageSource.java.patch
new file mode 100644
index 0000000000..471b9ae2dd
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -19,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-vineflower-stripped/net/minecraft/world/damagesource/DamageSources.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/damagesource/DamageSources.java.patch
new file mode 100644
index 0000000000..437fb8bf51
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/damagesource/DamageSources.java.patch
@@ -0,0 +1,18 @@
+--- a/net/minecraft/world/damagesource/DamageSources.java
++++ b/net/minecraft/world/damagesource/DamageSources.java
+@@ -40,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 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-vineflower-stripped/net/minecraft/world/effect/HealOrHarmMobEffect.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/effect/HealOrHarmMobEffect.java.patch
new file mode 100644
index 0000000000..5cb5a9ac43
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/effect/HealOrHarmMobEffect.java.patch
@@ -0,0 +1,22 @@
+--- a/net/minecraft/world/effect/HealOrHarmMobEffect.java
++++ b/net/minecraft/world/effect/HealOrHarmMobEffect.java
+@@ -16,7 +17,7 @@
+ public void applyEffectTick(LivingEntity livingEntity, int amplifier) {
+ super.applyEffectTick(livingEntity, amplifier);
+ if (this.isHarm == livingEntity.isInvertedHealAndHarm()) {
+- livingEntity.heal((float)Math.max(4 << amplifier, 0));
++ livingEntity.heal((float) Math.max(4 << amplifier, 0), org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC); // CraftBukkit
+ } else {
+ livingEntity.hurt(livingEntity.damageSources().magic(), (float)(6 << amplifier));
+ }
+@@ -25,8 +29,8 @@
+ @Override
+ public void applyInstantenousEffect(@Nullable Entity source, @Nullable Entity indirectSource, LivingEntity livingEntity, int amplifier, double health) {
+ if (this.isHarm == livingEntity.isInvertedHealAndHarm()) {
+- int i = (int)(health * (double)(4 << amplifier) + 0.5);
+- livingEntity.heal((float)i);
++ j = (int) (health * (double) (4 << amplifier) + 0.5D);
++ livingEntity.heal((float) j, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC); // CraftBukkit
+ } else {
+ int i = (int)(health * (double)(6 << amplifier) + 0.5);
+ if (source == null) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/effect/HungerMobEffect.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/effect/HungerMobEffect.java.patch
new file mode 100644
index 0000000000..139a72e2bd
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/effect/HungerMobEffect.java.patch
@@ -0,0 +1,15 @@
+--- a/net/minecraft/world/effect/HungerMobEffect.java
++++ b/net/minecraft/world/effect/HungerMobEffect.java
+@@ -11,8 +12,10 @@
+ @Override
+ public void applyEffectTick(LivingEntity livingEntity, int amplifier) {
+ super.applyEffectTick(livingEntity, amplifier);
+- if (livingEntity instanceof Player player) {
+- player.causeFoodExhaustion(0.005F * (float)(amplifier + 1));
++ if (livingEntity instanceof Player) {
++ Player entityhuman = (Player) livingEntity;
++
++ entityhuman.causeFoodExhaustion(0.005F * (float) (amplifier + 1), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.HUNGER_EFFECT); // CraftBukkit - EntityExhaustionEvent
+ }
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/effect/MobEffectUtil.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/effect/MobEffectUtil.java.patch
new file mode 100644
index 0000000000..34b576545d
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/effect/MobEffectUtil.java.patch
@@ -0,0 +1,40 @@
+--- a/net/minecraft/world/effect/MobEffectUtil.java
++++ b/net/minecraft/world/effect/MobEffectUtil.java
+@@ -43,21 +48,21 @@
+ return entity.hasEffect(MobEffects.WATER_BREATHING) || entity.hasEffect(MobEffects.CONDUIT_POWER);
+ }
+
+- public static List<ServerPlayer> addEffectToPlayersAround(
+- ServerLevel level, @Nullable Entity source, Vec3 pos, double radius, MobEffectInstance effect, int duration
+- ) {
+- MobEffect effect1 = effect.getEffect();
+- List<ServerPlayer> players = level.getPlayers(
+- serverPlayer -> serverPlayer.gameMode.isSurvival()
+- && (source == null || !source.isAlliedTo(serverPlayer))
+- && pos.closerThan(serverPlayer.position(), radius)
+- && (
+- !serverPlayer.hasEffect(effect1)
+- || serverPlayer.getEffect(effect1).getAmplifier() < effect.getAmplifier()
+- || serverPlayer.getEffect(effect1).endsWithin(duration - 1)
+- )
+- );
+- players.forEach(serverPlayer -> serverPlayer.addEffect(new MobEffectInstance(effect), source));
+- return players;
++ 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((entityplayer) -> {
++ entityplayer.addEffect(new MobEffectInstance(mobeffect), entity, cause); // CraftBukkit
++ });
++ return list;
++ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/effect/PoisonMobEffect.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/effect/PoisonMobEffect.java.patch
new file mode 100644
index 0000000000..692eb9e956
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/effect/PoisonMobEffect.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/effect/PoisonMobEffect.java
++++ b/net/minecraft/world/effect/PoisonMobEffect.java
+@@ -11,7 +12,7 @@
+ public void applyEffectTick(LivingEntity livingEntity, int amplifier) {
+ super.applyEffectTick(livingEntity, amplifier);
+ if (livingEntity.getHealth() > 1.0F) {
+- livingEntity.hurt(livingEntity.damageSources().magic(), 1.0F);
++ livingEntity.hurt(livingEntity.damageSources().poison, 1.0F); // CraftBukkit - DamageSource.MAGIC -> CraftEventFactory.POISON
+ }
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/effect/RegenerationMobEffect.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/effect/RegenerationMobEffect.java.patch
new file mode 100644
index 0000000000..0bdab8d665
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/effect/RegenerationMobEffect.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/effect/RegenerationMobEffect.java
++++ b/net/minecraft/world/effect/RegenerationMobEffect.java
+@@ -11,7 +12,7 @@
+ public void applyEffectTick(LivingEntity livingEntity, int amplifier) {
+ super.applyEffectTick(livingEntity, amplifier);
+ if (livingEntity.getHealth() < livingEntity.getMaxHealth()) {
+- livingEntity.heal(1.0F);
++ livingEntity.heal(1.0F, org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason.MAGIC_REGEN); // CraftBukkit
+ }
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/effect/SaturationMobEffect.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/effect/SaturationMobEffect.java.patch
new file mode 100644
index 0000000000..d3290f2029
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/effect/SaturationMobEffect.java.patch
@@ -0,0 +1,34 @@
+--- 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 {
+ protected SaturationMobEffect(MobEffectCategory category, int color) {
+@@ -11,8 +16,18 @@
+ @Override
+ public void applyEffectTick(LivingEntity livingEntity, int amplifier) {
+ super.applyEffectTick(livingEntity, amplifier);
+- if (!livingEntity.level().isClientSide && livingEntity instanceof Player player) {
+- player.getFoodData().eat(amplifier + 1, 1.0F);
++ if (!livingEntity.level().isClientSide && livingEntity instanceof Player) {
++ Player entityhuman = (Player) livingEntity;
++
++ // 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-vineflower-stripped/net/minecraft/world/entity/AgeableMob.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/AgeableMob.java.patch
new file mode 100644
index 0000000000..2b255ec379
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/AgeableMob.java.patch
@@ -0,0 +1,35 @@
+--- a/net/minecraft/world/entity/AgeableMob.java
++++ b/net/minecraft/world/entity/AgeableMob.java
+@@ -19,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);
+@@ -103,6 +104,7 @@
+ super.addAdditionalSaveData(compound);
+ compound.putInt("Age", this.getAge());
+ compound.putInt("ForcedAge", this.forcedAge);
++ compound.putBoolean("AgeLocked", this.ageLocked); // CraftBukkit
+ }
+
+ @Override
+@@ -110,6 +112,7 @@
+ super.readAdditionalSaveData(compound);
+ this.setAge(compound.getInt("Age"));
+ this.forcedAge = compound.getInt("ForcedAge");
++ this.ageLocked = compound.getBoolean("AgeLocked"); // CraftBukkit
+ }
+
+ @Override
+@@ -124,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.0), this.getRandomY() + 0.5, this.getRandomZ(1.0), 0.0, 0.0, 0.0);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/AreaEffectCloud.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/AreaEffectCloud.java.patch
new file mode 100644
index 0000000000..64d33de08e
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/AreaEffectCloud.java.patch
@@ -0,0 +1,58 @@
+--- a/net/minecraft/world/entity/AreaEffectCloud.java
++++ b/net/minecraft/world/entity/AreaEffectCloud.java
+@@ -28,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 {
+ private static final Logger LOGGER = LogUtils.getLogger();
+@@ -251,12 +264,40 @@
+ if (d7 <= (double)(radius * radius)) {
+ this.victims.put(livingEntity, this.tickCount + this.reapplicationDelay);
+
+- for (MobEffectInstance mobEffectInstance1 : list) {
+- if (mobEffectInstance1.getEffect().isInstantenous()) {
+- mobEffectInstance1.getEffect()
+- .applyInstantenousEffect(this, this.getOwner(), livingEntity, mobEffectInstance1.getAmplifier(), 0.5);
++ if (!list1.isEmpty()) {
++ Iterator iterator1 = list1.iterator();
++
++ List<LivingEntity> entities = new java.util.ArrayList<LivingEntity>(); // CraftBukkit
++ while (iterator1.hasNext()) {
++ net.minecraft.world.entity.LivingEntity entityliving = (net.minecraft.world.entity.LivingEntity) iterator1.next();
++
++ if (!this.victims.containsKey(entityliving) && entityliving.isAffectedByPotions()) {
++ double d6 = entityliving.getX() - this.getX();
++ double d7 = entityliving.getZ() - this.getZ();
++ double d8 = d6 * d6 + d7 * d7;
++
++ if (d8 <= (double) (f * f)) {
++ // 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()) {
++ MobEffectInstance mobeffect1 = (MobEffectInstance) iterator2.next();
++
++ if (mobeffect1.getEffect().isInstantenous()) {
++ mobeffect1.getEffect().applyInstantenousEffect(this, this.getOwner(), entityliving, mobeffect1.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-vineflower-stripped/net/minecraft/world/entity/Entity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/Entity.java.patch
new file mode 100644
index 0000000000..108c062a94
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/Entity.java.patch
@@ -0,0 +1,1065 @@
+--- a/net/minecraft/world/entity/Entity.java
++++ b/net/minecraft/world/entity/Entity.java
+@@ -123,8 +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 {
++public abstract class Entity implements INamableTileEntity, 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";
+@@ -240,8 +296,31 @@
+ private int lastCrystalSoundPlayTick;
+ private boolean hasVisualFire;
+ @Nullable
+- private BlockState feetBlockState = null;
++ 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 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.type = entityType;
+ this.level = level;
+@@ -353,8 +452,14 @@
+ public void onClientRemoval() {
+ }
+
+- public void setPose(Pose pose) {
+- this.entityData.set(DATA_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);
+ }
+
+ public Pose getPose() {
+@@ -377,6 +483,33 @@
+ }
+
+ 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);
+ }
+@@ -416,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;
+@@ -430,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();
+ }
+@@ -465,6 +609,10 @@
+ if (this.isInLava()) {
+ this.lavaHurt();
+ this.fallDistance *= 0.5F;
++ // CraftBukkit start
++ } else {
++ this.lastLavaContact = null;
++ // CraftBukkit end
+ }
+
+ this.checkBelowWorld();
+@@ -514,18 +664,49 @@
+
+ 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 seconds) {
+- int i = seconds * 20;
+- if (this instanceof LivingEntity) {
+- i = ProtectionEnchantment.getFireAfterDampener((LivingEntity)this, i);
++ // 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.remainingFireTicks < i) {
+ this.setRemainingFireTicks(i);
+@@ -662,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(), onPosLegacy, blockState, this);
+ }
+@@ -976,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 boundingBox = this.getBoundingBox();
+ BlockPos blockPos = BlockPos.containing(boundingBox.minX + 1.0E-7, boundingBox.minY + 1.0E-7, boundingBox.minZ + 1.0E-7);
+@@ -1357,13 +1625,15 @@
+ this.xRotO = this.getXRot();
+ }
+
+- public void absMoveTo(double x, double y, double z) {
+- double d = Mth.clamp(x, -3.0E7, 3.0E7);
+- double d1 = Mth.clamp(z, -3.0E7, 3.0E7);
+- this.xo = d;
+- this.yo = y;
+- this.zo = d1;
+- this.setPos(d, y, d1);
++ public void absMoveTo(double x, double d1, double y) {
++ double d3 = Mth.clamp(x, -3.0E7D, 3.0E7D);
++ double d4 = Mth.clamp(y, -3.0E7D, 3.0E7D);
++
++ this.xo = d3;
++ 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 vec) {
+@@ -1549,6 +1828,12 @@
+ return false;
+ }
+
++ // 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);
+@@ -1574,15 +1862,22 @@
+ }
+
+ 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 encodeId = this.getEncodeId();
+- if (encodeId == null) {
++ String s = this.getEncodeId();
++
++ if (!this.persist || s == null) { // CraftBukkit - persist flag
+ return false;
+ } else {
+- compound.putString("id", encodeId);
+- this.saveWithoutId(compound);
++ nbttagcompound.putString("id", s);
++ this.saveWithoutId(nbttagcompound, includeAll); // CraftBukkit - pass on includeAll
+ return true;
+ }
+ }
+@@ -1593,28 +1888,73 @@
+ }
+
+ 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) {
+- compound.put("Pos", this.newDoubleList(this.vehicle.getX(), this.getY(), this.vehicle.getZ()));
+- } else {
+- compound.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 deltaMovement = this.getDeltaMovement();
+- compound.put("Motion", this.newDoubleList(deltaMovement.x, deltaMovement.y, deltaMovement.z));
+- compound.put("Rotation", this.newFloatList(this.getYRot(), this.getXRot()));
+- compound.putFloat("FallDistance", this.fallDistance);
+- compound.putShort("Fire", (short)this.remainingFireTicks);
+- compound.putShort("Air", (short)this.getAirSupply());
+- compound.putBoolean("OnGround", this.onGround());
+- compound.putBoolean("Invulnerable", this.invulnerable);
+- compound.putInt("PortalCooldown", this.portalCooldown);
+- compound.putUUID("UUID", this.getUUID());
+- Component customName = this.getCustomName();
+- if (customName != null) {
+- compound.putString("CustomName", Component.Serializer.toJson(customName));
++ Vec3 vec3d = this.getDeltaMovement();
++
++ nbttagcompound.put("Motion", this.newDoubleList(vec3d.x, vec3d.y, vec3d.z));
++
++ // 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()) {
+ compound.putBoolean("CustomNameVisible", this.isCustomNameVisible());
+ }
+@@ -1650,14 +1997,16 @@
+ compound.put("Tags", list);
+ }
+
+- this.addAdditionalSaveData(compound);
++ this.addAdditionalSaveData(nbttagcompound, includeAll); // CraftBukkit - pass on includeAll
+ if (this.isVehicle()) {
+ ListTag list = new ListTag();
+
+- for (Entity entity : this.getPassengers()) {
+- CompoundTag compoundTag = new CompoundTag();
+- if (entity.saveAsPassenger(compoundTag)) {
+- list.add(compoundTag);
++ while (iterator.hasNext()) {
++ Entity entity = (Entity) iterator.next();
++ CompoundTag nbttagcompound1 = new CompoundTag();
++
++ if (entity.saveAsPassenger(nbttagcompound1, includeAll)) { // CraftBukkit - pass on includeAll
++ nbttaglist.add(nbttagcompound1);
+ }
+ }
+
+@@ -1666,12 +2016,18 @@
+ }
+ }
+
+- return compound;
+- } catch (Throwable var9) {
+- CrashReport crashReport = CrashReport.forThrowable(var9, "Saving entity NBT");
+- CrashReportCategory crashReportCategory = crashReport.addCategory("Entity being saved");
+- this.fillCrashReportCategory(crashReportCategory);
+- throw new ReportedException(crashReport);
++ // 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 crashreportsystemdetails = crashreport.addCategory("Entity being saved");
++
++ this.fillCrashReportCategory(crashreportsystemdetails);
++ throw new ReportedException(crashreport);
+ }
+ }
+
+@@ -1749,11 +2104,51 @@
+ } else {
+ throw new IllegalStateException("Entity has invalid rotation");
+ }
+- } catch (Throwable var17) {
+- CrashReport crashReport = CrashReport.forThrowable(var17, "Loading entity NBT");
+- CrashReportCategory crashReportCategory = crashReport.addCategory("Entity being loaded");
+- this.fillCrashReportCategory(crashReportCategory);
+- throw new ReportedException(crashReport);
++
++ // 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 crashreportsystemdetails = crashreport.addCategory("Entity being loaded");
++
++ this.fillCrashReportCategory(crashreportsystemdetails);
++ throw new ReportedException(crashreport);
+ }
+ }
+
+@@ -1768,6 +2164,12 @@
+ return type.canSerialize() && key != null ? key.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);
+@@ -1814,10 +2224,24 @@
+ } else if (this.level().isClientSide) {
+ return null;
+ } else {
+- ItemEntity itemEntity = new ItemEntity(this.level(), this.getX(), this.getY() + (double)offsetY, this.getZ(), stack);
+- itemEntity.setDefaultPickUpDelay();
+- this.level().addFreshEntity(itemEntity);
+- return itemEntity;
++ // 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);
++
++ 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;
+ }
+ }
+
+@@ -1918,7 +2334,21 @@
+ }
+ }
+
+- if (force || this.canRide(vehicle) && vehicle.canAddPassenger(this)) {
++ if (!force && (!this.canRide(vehicle) || !vehicle.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();
+ }
+@@ -1950,7 +2382,7 @@
+ if (this.vehicle != null) {
+ Entity entity = this.vehicle;
+ this.vehicle = null;
+- entity.removePassenger(this);
++ if (!entity.removePassenger(this)) this.vehicle = entity; // CraftBukkit
+ }
+ }
+
+@@ -1979,11 +2413,30 @@
+ }
+ }
+
+- protected void removePassenger(Entity passenger) {
+- if (passenger.getVehicle() == this) {
++ protected boolean removePassenger(Entity entity) { // CraftBukkit
++ if (entity.getVehicle() == this) {
+ throw new IllegalStateException("Use x.stopRiding(y), not y.removePassenger(x)");
+ } else {
+- if (this.passengers.size() == 1 && this.passengers.get(0) == passenger) {
++ // 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 {
+ this.passengers = this.passengers.stream().filter(entity -> entity != passenger).collect(ImmutableList.toImmutableList());
+@@ -1992,6 +2447,7 @@
+ passenger.boardingCooldown = 60;
+ this.gameEvent(GameEvent.ENTITY_DISMOUNT, passenger);
+ }
++ return true; // CraftBukkit
+ }
+
+ protected boolean canAddPassenger(Entity passenger) {
+@@ -2074,14 +2533,21 @@
+ int portalWaitTime = this.getPortalWaitTime();
+ ServerLevel serverLevel = (ServerLevel)this.level();
+ if (this.isInsidePortal) {
+- MinecraftServer server = serverLevel.getServer();
+- ResourceKey<Level> resourceKey = this.level().dimension() == Level.NETHER ? Level.OVERWORLD : Level.NETHER;
+- ServerLevel level = server.getLevel(resourceKey);
+- if (level != null && server.isNetherEnabled() && !this.isPassenger() && this.portalTime++ >= portalWaitTime) {
++ MinecraftServer minecraftserver = worldserver.getServer();
++ ResourceKey<Level> resourcekey = this.level().getTypeKey() == LevelStem.NETHER ? Level.OVERWORLD : Level.NETHER; // CraftBukkit
++ ServerLevel worldserver1 = minecraftserver.getLevel(resourcekey);
++
++ if (true && !this.isPassenger() && this.portalTime++ >= i) { // CraftBukkit
+ this.level().getProfiler().push("portal");
+ this.portalTime = portalWaitTime;
+ this.setPortalCooldown();
+- this.changeDimension(level);
++ // CraftBukkit start
++ if (this instanceof ServerPlayer) {
++ ((ServerPlayer) this).changeDimension(worldserver1, PlayerTeleportEvent.TeleportCause.NETHER_PORTAL);
++ } else {
++ this.changeDimension(worldserver1);
++ }
++ // CraftBukkit end
+ this.level().getProfiler().pop();
+ }
+
+@@ -2206,6 +2671,13 @@
+ }
+
+ 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);
+ }
+
+@@ -2255,8 +2727,12 @@
+ return this.getTeam() != null && this.getTeam().isAlliedTo(team);
+ }
+
++ // CraftBukkit - start
+ public void setInvisible(boolean invisible) {
+- this.setSharedFlag(5, invisible);
++ if (!this.persistentInvisibility) { // Prevent Minecraft from removing our invisibility flag
++ this.setSharedFlag(5, invisible);
++ }
++ // CraftBukkit - end
+ }
+
+ protected boolean getSharedFlag(int flag) {
+@@ -2273,7 +2751,7 @@
+ }
+
+ public int getMaxAirSupply() {
+- return 300;
++ return maxAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
+ }
+
+ public int getAirSupply() {
+@@ -2281,7 +2759,18 @@
+ }
+
+ public void setAirSupply(int air) {
+- this.entityData.set(DATA_AIR_SUPPLY_ID, 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() {
+@@ -2307,11 +2797,41 @@
+
+ public void thunderHit(ServerLevel level, LightningBolt lightning) {
+ 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 downwards) {
+@@ -2489,24 +2996,60 @@
+
+ @Nullable
+ 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(destination);
+- if (portalInfo == null) {
++ 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 (shapedetectorshape == 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(destination);
+ 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);
+- destination.addDuringTeleport(entity);
+- if (destination.dimension() == Level.END) {
+- ServerLevel.makeObsidianPlatform(destination);
++ 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();
+@@ -2527,39 +3071,52 @@
+
+ @Nullable
+ protected PortalInfo findDimensionEntryPoint(ServerLevel destination) {
+- boolean flag = this.level().dimension() == Level.END && destination.dimension() == Level.OVERWORLD;
+- boolean flag1 = destination.dimension() == Level.END;
++ // 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 = destination.dimension() == Level.NETHER;
+- if (this.level().dimension() != Level.NETHER && !flag2) {
++ boolean flag2 = destination.getTypeKey() == LevelStem.NETHER; // CraftBukkit
++
++ if (this.level().getTypeKey() != LevelStem.NETHER && !flag2) { // CraftBukkit
+ return null;
+ } else {
+- WorldBorder worldBorder = destination.getWorldBorder();
+- double teleportationScale = DimensionType.getTeleportationScale(this.level().dimensionType(), destination.dimensionType());
+- BlockPos blockPos1 = worldBorder.clampToBounds(this.getX() * teleportationScale, this.getY(), this.getZ() * teleportationScale);
+- return this.getExitPortal(destination, blockPos1, flag2, worldBorder)
+- .map(
+- foundRectangle -> {
+- BlockState blockState = this.level().getBlockState(this.portalEntrancePos);
+- Direction.Axis axis;
+- Vec3 relativePortalPosition;
+- if (blockState.hasProperty(BlockStateProperties.HORIZONTAL_AXIS)) {
+- axis = blockState.getValue(BlockStateProperties.HORIZONTAL_AXIS);
+- BlockUtil.FoundRectangle largestRectangleAround = BlockUtil.getLargestRectangleAround(
+- this.portalEntrancePos, axis, 21, Direction.Axis.Y, 21, blockPos2 -> this.level().getBlockState(blockPos2) == blockState
+- );
+- relativePortalPosition = this.getRelativePortalPosition(axis, largestRectangleAround);
+- } else {
+- axis = Direction.Axis.X;
+- relativePortalPosition = new Vec3(0.5, 0.0, 0.0);
+- }
+-
+- return PortalShape.createPortalInfo(
+- destination, foundRectangle, axis, relativePortalPosition, this, this.getDeltaMovement(), this.getYRot(), this.getXRot()
+- );
+- }
+- )
+- .orElse(null);
++ 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(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 (iblockdata.hasProperty(BlockStateProperties.HORIZONTAL_AXIS)) {
++ enumdirection_enumaxis = (Direction.Axis) iblockdata.getValue(BlockStateProperties.HORIZONTAL_AXIS);
++ BlockUtil.FoundRectangle blockutil_rectangle1 = BlockUtil.getLargestRectangleAround(this.portalEntrancePos, enumdirection_enumaxis, 21, Direction.Axis.Y, 21, (blockposition1) -> {
++ return this.level().getBlockState(blockposition1) == iblockdata;
++ });
++
++ vec3d = this.getRelativePortalPosition(enumdirection_enumaxis, blockutil_rectangle1);
++ } else {
++ enumdirection_enumaxis = Direction.Axis.X;
++ vec3d = new Vec3(0.5D, 0.0D, 0.0D);
++ }
++
++ 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 blockPos;
+@@ -2568,13 +3126,14 @@
+ } else {
+ blockPos = destination.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, destination.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)blockPos.getX() + 0.5, (double)blockPos.getY(), (double)blockPos.getZ() + 0.5),
+- 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
+ }
+ }
+
+@@ -2582,10 +3141,25 @@
+ return PortalShape.getRelativePosition(portal, axis, this.position(), this.getDimensions(this.getPose()));
+ }
+
+- protected Optional<BlockUtil.FoundRectangle> getExitPortal(ServerLevel destination, BlockPos findFrom, boolean isToNether, WorldBorder worldBorder) {
+- return destination.getPortalForcer().findPortalAround(findFrom, isToNether, 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();
+ }
+@@ -2695,8 +3278,15 @@
+ }
+ }
+
+- public boolean teleportTo(ServerLevel level, double x, double y, double z, Set<RelativeMovement> relativeMovements, float yRot, float xRot) {
+- float f = Mth.clamp(xRot, -90.0F, 90.0F);
++ // 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 (level == this.level()) {
+ this.moveTo(x, y, z, yRot, f);
+ this.teleportPassengers();
+@@ -2712,7 +3303,11 @@
+ entity.moveTo(x, y, z, yRot, f);
+ entity.setYHeadRot(yRot);
+ this.setRemoved(Entity.RemovalReason.CHANGED_DIMENSION);
+- level.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;
+@@ -2812,7 +3413,26 @@
+ }
+
+ public final void setBoundingBox(AABB bb) {
+- this.bb = 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 dimensions) {
+@@ -3114,6 +3747,11 @@
+ vec3 = vec3.add(flow);
+ i++;
+ }
++ // CraftBukkit start - store last lava contact location
++ if (fluidTag == FluidTags.LAVA) {
++ this.lastLavaContact = blockposition_mutableblockposition.immutable();
++ }
++ // CraftBukkit end
+ }
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/EntitySelector.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/EntitySelector.java.patch
new file mode 100644
index 0000000000..e03a629440
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/EntitySelector.java.patch
@@ -0,0 +1,48 @@
+--- a/net/minecraft/world/entity/EntitySelector.java
++++ b/net/minecraft/world/entity/EntitySelector.java
+@@ -27,27 +40,24 @@
+ }
+
+ public static Predicate<Entity> pushableBy(Entity entity) {
+- Team team = entity.getTeam();
+- Team.CollisionRule collisionRule = team == null ? Team.CollisionRule.ALWAYS : team.getCollisionRule();
+- return (Predicate<Entity>)(collisionRule == Team.CollisionRule.NEVER
+- ? Predicates.alwaysFalse()
+- : NO_SPECTATORS.and(
+- pushedEntity -> {
+- if (!pushedEntity.isPushable()) {
+- return false;
+- } else if (!entity.level().isClientSide || pushedEntity instanceof Player && ((Player)pushedEntity).isLocalPlayer()) {
+- Team team1 = pushedEntity.getTeam();
+- Team.CollisionRule collisionRule1 = team1 == null ? Team.CollisionRule.ALWAYS : team1.getCollisionRule();
+- if (collisionRule1 == Team.CollisionRule.NEVER) {
+- return false;
+- } else {
+- boolean flag = team != null && team.isAlliedTo(team1);
+- return (collisionRule != Team.CollisionRule.PUSH_OWN_TEAM && collisionRule1 != Team.CollisionRule.PUSH_OWN_TEAM || !flag)
+- && (collisionRule != Team.CollisionRule.PUSH_OTHER_TEAMS && collisionRule1 != Team.CollisionRule.PUSH_OTHER_TEAMS || flag);
+- }
+- } else {
+- return false;
+- }
++ PlayerTeam scoreboardteam = entity.getTeam();
++ Team.CollisionRule scoreboardteambase_enumteampush = scoreboardteam == null ? Team.CollisionRule.ALWAYS : scoreboardteam.getCollisionRule();
++
++ return (Predicate) (scoreboardteambase_enumteampush == Team.CollisionRule.NEVER ? Predicates.alwaysFalse() : EntitySelector.NO_SPECTATORS.and((entity1) -> {
++ if (!entity1.canCollideWithBukkit(entity) || !entity.canCollideWithBukkit(entity1)) { // CraftBukkit - collidable API
++ return false;
++ } else if (entity.level().isClientSide && (!(entity1 instanceof Player) || !((Player) entity1).isLocalPlayer())) {
++ return false;
++ } else {
++ PlayerTeam scoreboardteam1 = entity1.getTeam();
++ Team.CollisionRule scoreboardteambase_enumteampush1 = scoreboardteam1 == null ? Team.CollisionRule.ALWAYS : scoreboardteam1.getCollisionRule();
++
++ if (scoreboardteambase_enumteampush1 == Team.CollisionRule.NEVER) {
++ return false;
++ } else {
++ boolean flag = scoreboardteam != null && scoreboardteam.isAlliedTo(scoreboardteam1);
++
++ return (scoreboardteambase_enumteampush == Team.CollisionRule.PUSH_OWN_TEAM || scoreboardteambase_enumteampush1 == Team.CollisionRule.PUSH_OWN_TEAM) && flag ? false : scoreboardteambase_enumteampush != Team.CollisionRule.PUSH_OTHER_TEAMS && scoreboardteambase_enumteampush1 != Team.CollisionRule.PUSH_OTHER_TEAMS || flag;
+ }
+ ));
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/EntityType.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/EntityType.java.patch
new file mode 100644
index 0000000000..21f87b5994
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/EntityType.java.patch
@@ -0,0 +1,138 @@
+--- a/net/minecraft/world/entity/EntityType.java
++++ b/net/minecraft/world/entity/EntityType.java
+@@ -168,26 +170,11 @@
+ private final Holder.Reference<EntityType<?>> builtInRegistryHolder = BuiltInRegistries.ENTITY_TYPE.createIntrusiveHolder(this);
+ 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.<AreaEffectCloud>of(AreaEffectCloud::new, MobCategory.MISC)
+- .fireImmune()
+- .sized(6.0F, 0.5F)
+- .clientTrackingRange(10)
+- .updateInterval(Integer.MAX_VALUE)
+- );
+- public static final EntityType<ArmorStand> ARMOR_STAND = register(
+- "armor_stand", EntityType.Builder.<ArmorStand>of(ArmorStand::new, MobCategory.MISC).sized(0.5F, 1.975F).clientTrackingRange(10)
+- );
+- public static final EntityType<Arrow> ARROW = register(
+- "arrow", EntityType.Builder.<Arrow>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)
+- );
++ 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(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));
+ public static final EntityType<Bat> BAT = register("bat", EntityType.Builder.of(Bat::new, MobCategory.AMBIENT).sized(0.5F, 0.9F).clientTrackingRange(5));
+ public static final EntityType<Bee> BEE = register("bee", EntityType.Builder.of(Bee::new, MobCategory.CREATURE).sized(0.7F, 0.6F).clientTrackingRange(8));
+ public static final EntityType<Blaze> BLAZE = register(
+@@ -627,27 +342,27 @@
+ }
+
+ @Nullable
+- public T spawn(
+- ServerLevel serverLevel,
+- @Nullable ItemStack stack,
+- @Nullable Player player,
+- BlockPos pos,
+- MobSpawnType spawnType,
+- boolean shouldOffsetY,
+- boolean shouldOffsetYMore
+- ) {
+- Consumer<T> consumer;
+- CompoundTag tag;
+- if (stack != null) {
+- tag = stack.getTag();
+- consumer = createDefaultStackConfig(serverLevel, stack, player);
++ 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) {
++ nbttagcompound = itemstack.getTag();
++ consumer = createDefaultStackConfig(worldserver, itemstack, entityhuman);
+ } else {
+ consumer = entity -> {
+ };
+ tag = null;
+ }
+
+- return this.spawn(serverLevel, tag, consumer, pos, spawnType, shouldOffsetY, shouldOffsetYMore);
++ return this.spawn(worldserver, nbttagcompound, consumer, blockposition, enummobspawn, flag, flag1, spawnReason); // CraftBukkit
+ }
+
+ public static <T extends Entity> Consumer<T> createDefaultStackConfig(ServerLevel serverLevel, ItemStack stack, @Nullable Player player) {
+@@ -665,31 +380,41 @@
+ return stack.hasCustomHoverName() ? consumer.andThen(entity -> entity.setCustomName(stack.getHoverName())) : consumer;
+ }
+
+- public static <T extends Entity> Consumer<T> appendCustomEntityStackConfig(
+- Consumer<T> consumer, ServerLevel level, ItemStack stack, @Nullable Player player
+- ) {
+- CompoundTag tag = stack.getTag();
+- return tag != null ? consumer.andThen(spawnedEntity -> updateCustomEntityTag(level, player, spawnedEntity, tag)) : consumer;
++ public static <T extends Entity> Consumer<T> appendCustomEntityStackConfig(Consumer<T> consumer, ServerLevel level, ItemStack stack, @Nullable Player player) {
++ CompoundTag nbttagcompound = stack.getTag();
++
++ 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 level, BlockPos pos, MobSpawnType spawnType) {
+- return this.spawn(level, (CompoundTag)null, null, pos, spawnType, 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 level,
+- @Nullable CompoundTag compound,
+- @Nullable Consumer<T> consumer,
+- BlockPos pos,
+- MobSpawnType spawnType,
+- boolean shouldOffsetY,
+- boolean shouldOffsetYMore
+- ) {
+- T entity = this.create(level, compound, consumer, pos, spawnType, shouldOffsetY, shouldOffsetYMore);
+- if (entity != null) {
+- level.addFreshEntityWithPassengers(entity);
++ 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) {
++ worldserver.addFreshEntityWithPassengers(t0, spawnReason);
++ return !t0.isRemoved() ? t0 : null; // Don't return an entity when CreatureSpawnEvent is canceled
++ // CraftBukkit end
+ }
+
+ return entity;
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ExperienceOrb.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ExperienceOrb.java.patch
new file mode 100644
index 0000000000..c1c9baa94c
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ExperienceOrb.java.patch
@@ -0,0 +1,161 @@
+--- a/net/minecraft/world/entity/ExperienceOrb.java
++++ b/net/minecraft/world/entity/ExperienceOrb.java
+@@ -19,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 {
+ private static final int LIFETIME = 6000;
+@@ -58,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,20 +91,33 @@
+ this.followingPlayer = null;
+ }
+
+- if (this.followingPlayer != null) {
+- Vec3 vec3 = new Vec3(
+- this.followingPlayer.getX() - this.getX(),
+- this.followingPlayer.getY() + (double)this.followingPlayer.getEyeHeight() / 2.0 - this.getY(),
+- this.followingPlayer.getZ() - this.getZ()
+- );
+- double d = vec3.lengthSqr();
+- if (d < 64.0) {
+- double d1 = 1.0 - Math.sqrt(d) / 8.0;
+- this.setDeltaMovement(this.getDeltaMovement().add(vec3.normalize().scale(d1 * d1 * 0.1)));
++ // 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;
+ }
+ }
+
+- this.move(MoverType.SELF, this.getDeltaMovement());
++ 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;
++
++ this.setDeltaMovement(this.getDeltaMovement().add(vec3d.normalize().scale(d1 * d1 * 0.1D)));
++ }
++ }
++
++ this.move(EnumMoveType.SELF, this.getDeltaMovement());
+ float f = 0.98F;
+ if (this.onGround()) {
+ f = this.level().getBlockState(this.getBlockPosBelowThatAffectsMyMovement()).getBlock().getFriction() * 0.98F;
+@@ -219,11 +249,11 @@
+ public void playerTouch(Player entity) {
+ if (!this.level().isClientSide) {
+ if (entity.takeXpDelay == 0) {
+- entity.takeXpDelay = 2;
++ 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) {
+- entity.giveExperiencePoints(i);
++ entity.giveExperiencePoints(CraftEventFactory.callPlayerExpChangeEvent(entity, i).getAmount()); // CraftBukkit - this.value -> event.getAmount()
+ }
+
+ this.count--;
+@@ -235,13 +267,24 @@
+ }
+
+ private int repairPlayerItems(Player player, int repairAmount) {
+- Entry<EquipmentSlot, ItemStack> randomItemWith = EnchantmentHelper.getRandomItemWith(Enchantments.MENDING, player, ItemStack::isDamaged);
+- if (randomItemWith != null) {
+- ItemStack itemStack = randomItemWith.getValue();
+- int min = Math.min(this.xpToDurability(repairAmount), itemStack.getDamageValue());
+- itemStack.setDamageValue(itemStack.getDamageValue() - min);
+- int i = repairAmount - this.durabilityToXp(min);
+- return i > 0 ? this.repairPlayerItems(player, i) : 0;
++ Entry<EquipmentSlot, ItemStack> entry = EnchantmentHelper.getRandomItemWith(Enchantments.MENDING, player, ItemStack::isDamaged);
++
++ if (entry != null) {
++ ItemStack itemstack = (ItemStack) entry.getValue();
++ 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 = 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 {
+ return repairAmount;
+ }
+@@ -284,27 +307,25 @@
+ }
+
+ public static int getExperienceValue(int expValue) {
+- if (expValue >= 2477) {
+- return 2477;
+- } else if (expValue >= 1237) {
+- return 1237;
+- } else if (expValue >= 617) {
+- return 617;
+- } else if (expValue >= 307) {
+- return 307;
+- } else if (expValue >= 149) {
+- return 149;
+- } else if (expValue >= 73) {
+- return 73;
+- } else if (expValue >= 37) {
+- return 37;
+- } else if (expValue >= 17) {
+- return 17;
+- } else if (expValue >= 7) {
+- return 7;
+- } else {
+- return expValue >= 3 ? 3 : 1;
+- }
++ // 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-vineflower-stripped/net/minecraft/world/entity/Interaction.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/Interaction.java.patch
new file mode 100644
index 0000000000..c03fcd8095
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/Interaction.java.patch
@@ -0,0 +1,44 @@
+--- a/net/minecraft/world/entity/Interaction.java
++++ b/net/minecraft/world/entity/Interaction.java
+@@ -25,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();
+ private static final EntityDataAccessor<Float> DATA_WIDTH_ID = SynchedEntityData.defineId(Interaction.class, EntityDataSerializers.FLOAT);
+@@ -129,12 +147,23 @@
+
+ @Override
+ public boolean skipAttackInteraction(Entity entity) {
+- if (entity instanceof Player player) {
+- this.attack = new Interaction.PlayerAction(player.getUUID(), this.level().getGameTime());
+- if (player instanceof ServerPlayer serverPlayer) {
+- CriteriaTriggers.PLAYER_HURT_ENTITY.trigger(serverPlayer, this, player.damageSources().generic(), 1.0F, 1.0F, false);
++ if (entity instanceof Player) {
++ 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(entityhuman.getUUID(), this.level().getGameTime());
++ if (entityhuman instanceof ServerPlayer) {
++ ServerPlayer entityplayer = (ServerPlayer) entityhuman;
++
++ CriteriaTriggers.PLAYER_HURT_ENTITY.trigger(entityplayer, this, source, (float) event.getFinalDamage(), 1.0F, false); // CraftBukkit
++ }
++
+ return !this.getResponse();
+ } else {
+ return false;
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ItemBasedSteering.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ItemBasedSteering.java.patch
new file mode 100644
index 0000000000..85f6fe1e3c
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ItemBasedSteering.java.patch
@@ -0,0 +1,17 @@
+--- a/net/minecraft/world/entity/ItemBasedSteering.java
++++ b/net/minecraft/world/entity/ItemBasedSteering.java
+@@ -51,6 +53,14 @@
+ return this.entityData.get(this.boostTimeAccessor);
+ }
+
++ // 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 addAdditionalSaveData(CompoundTag nbt) {
+ nbt.putBoolean("Saddle", this.hasSaddle());
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/LightningBolt.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/LightningBolt.java.patch
new file mode 100644
index 0000000000..6c5a520ade
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/LightningBolt.java.patch
@@ -0,0 +1,62 @@
+--- a/net/minecraft/world/entity/LightningBolt.java
++++ b/net/minecraft/world/entity/LightningBolt.java
+@@ -24,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 {
+ private static final int START_LIFE = 2;
+@@ -138,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) {
+@@ -168,19 +162,29 @@
+
+ private void spawnFire(int extraIgnitions) {
+ if (!this.visualOnly && !this.level().isClientSide && this.level().getGameRules().getBoolean(GameRules.RULE_DOFIRETICK)) {
+- BlockPos blockPos = this.blockPosition();
+- BlockState state = BaseFireBlock.getState(this.level(), blockPos);
+- if (this.level().getBlockState(blockPos).isAir() && state.canSurvive(this.level(), blockPos)) {
+- this.level().setBlockAndUpdate(blockPos, state);
+- this.blocksSetOnFire++;
++ BlockPos blockposition = this.blockPosition();
++ IBlockData iblockdata = BaseFireBlock.getState(this.level(), blockposition);
++
++ 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 i = 0; i < extraIgnitions; i++) {
+- BlockPos blockPos1 = blockPos.offset(this.random.nextInt(3) - 1, this.random.nextInt(3) - 1, this.random.nextInt(3) - 1);
+- state = BaseFireBlock.getState(this.level(), blockPos1);
+- if (this.level().getBlockState(blockPos1).isAir() && state.canSurvive(this.level(), blockPos1)) {
+- this.level().setBlockAndUpdate(blockPos1, state);
+- this.blocksSetOnFire++;
++ for (int j = 0; j < extraIgnitions; ++j) {
++ BlockPos blockposition1 = blockposition.offset(this.random.nextInt(3) - 1, this.random.nextInt(3) - 1, this.random.nextInt(3) - 1);
++
++ 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-vineflower-stripped/net/minecraft/world/entity/LivingEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/LivingEntity.java.patch
new file mode 100644
index 0000000000..f09edaaa57
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/LivingEntity.java.patch
@@ -0,0 +1,1095 @@
+--- 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();
+ private static final String TAG_ACTIVE_EFFECTS = "active_effects";
+@@ -229,11 +247,27 @@
+ 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;
+
++ @Override
++ public float getBukkitYaw() {
++ return getYHeadRot();
++ }
++ // CraftBukkit end
++
+ protected LivingEntity(EntityType<? extends LivingEntity> entityType, Level level) {
+ super(entityType, level);
+ this.attributes = new AttributeMap(DefaultAttributes.getSupplier(entityType));
+- this.setHealth(this.getMaxHealth());
++ 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.0) * 0.01F);
+ this.reapplyPosition();
+@@ -311,10 +350,17 @@
+ z = (double)pos.getZ() + 0.5 + d1 / max * 0.5;
+ }
+
+- float f = (float)Mth.ceil(this.fallDistance - 3.0F);
+- double min = Math.min((double)(0.2F + f / 15.0F), 2.5);
+- int i = (int)(150.0 * min);
+- ((ServerLevel)this.level()).sendParticles(new BlockParticleOption(ParticleTypes.BLOCK, state), x, y1, z, i, 0.0, 0.0, 0.0, 0.15F);
++ float f = (float) Mth.ceil(this.fallDistance - 3.0F);
++ double d7 = Math.min((double) (0.2F + f / 15.0F), 2.5D);
++ int i = (int) (150.0D * d7);
++
++ // 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(y, onGround, state, pos);
+@@ -678,12 +715,20 @@
+ }
+
+ public void onEquipItem(EquipmentSlot slot, ItemStack oldItem, ItemStack newItem) {
+- boolean flag = newItem.isEmpty() && oldItem.isEmpty();
+- if (!flag && !ItemStack.isSameItemSameTags(oldItem, newItem) && !this.firstTick) {
+- Equipable equipable = Equipable.get(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() == slot) {
+- this.level().playSound(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(slot)) {
+@@ -746,6 +801,17 @@
+ }
+ }
+
++ // 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
++
+ if (compound.contains("Health", 99)) {
+ this.setHealth(compound.getFloat("Health"));
+ }
+@@ -780,15 +849,44 @@
+ }
+ }
+
++ // 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<MobEffect> iterator = this.activeEffects.keySet().iterator();
+
++ isTickingEffects = true; // CraftBukkit
+ try {
+ while (iterator.hasNext()) {
+ MobEffect mobEffect = iterator.next();
+ MobEffectInstance mobEffectInstance = this.activeEffects.get(mobEffect);
+ if (!mobEffectInstance.tick(this, () -> this.onEffectUpdated(mobEffectInstance, true, 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);
+ }
+@@ -798,6 +900,17 @@
+ }
+ } catch (ConcurrentModificationException var11) {
+ }
++ // 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) {
+@@ -920,7 +1037,13 @@
+ this.entityData.set(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 {
+@@ -928,7 +1052,14 @@
+
+ boolean flag;
+ for (flag = false; iterator.hasNext(); flag = true) {
+- this.onEffectRemoved(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();
+ }
+
+@@ -957,18 +1088,49 @@
+ return this.addEffect(effectInstance, null);
+ }
+
++ // 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) {
+- if (!this.canBeAffected(effectInstance)) {
++ 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 mobEffectInstance = this.activeEffects.get(effectInstance.getEffect());
+ boolean flag = false;
+- if (mobEffectInstance == null) {
+- this.activeEffects.put(effectInstance.getEffect(), effectInstance);
+- this.onEffectAdded(effectInstance, 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 (mobEffectInstance.update(effectInstance)) {
+- this.onEffectUpdated(mobEffectInstance, true, entity);
++ // CraftBukkit start
++ } else if (event.isOverride()) {
++ mobeffect1.update(mobeffect);
++ this.onEffectUpdated(mobeffect1, true, entity);
++ // CraftBukkit end
+ flag = true;
+ }
+
+@@ -1003,15 +1168,22 @@
+ return this.getMobType() == MobType.UNDEAD;
+ }
+
++ // CraftBukkit start
+ @Nullable
+ public MobEffectInstance removeEffectNoUpdate(@Nullable MobEffect effect) {
+ return this.activeEffects.remove(effect);
+ }
+
+ public boolean removeEffect(MobEffect effect) {
+- MobEffectInstance mobEffectInstance = this.removeEffectNoUpdate(effect);
+- if (mobEffectInstance != null) {
+- this.onEffectRemoved(mobEffectInstance);
++ 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;
+@@ -1082,19 +1298,55 @@
+ }
+ }
+
++ // CraftBukkit start - Delegate so we can handle providing a reason for health being regained
+ public void heal(float healAmount) {
+- float health = this.getHealth();
+- if (health > 0.0F) {
+- this.setHealth(health + healAmount);
++ heal(healAmount, EntityRegainHealthEvent.RegainReason.CUSTOM);
++ }
++
++ public void heal(float f, EntityRegainHealthEvent.RegainReason regainReason) {
++ float f1 = this.getHealth();
++
++ if (f1 > 0.0F) {
++ 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() {
+- return this.entityData.get(DATA_HEALTH_ID);
++ // 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 health) {
+- this.entityData.set(DATA_HEALTH_ID, Mth.clamp(health, 0.0F, this.getMaxHealth()));
++ // 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() {
+@@ -1107,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 (source.is(DamageTypeTags.IS_FIRE) && this.hasEffect(MobEffects.FIRE_RESISTANCE)) {
+ return false;
+@@ -1117,10 +1370,12 @@
+ }
+
+ this.noActionTime = 0;
+- float f = amount;
+- boolean flag = false;
+- float f1 = 0.0F;
+- if (amount > 0.0F && this.isDamageSourceBlocked(source)) {
++ float f1 = amount;
++ boolean flag = amount > 0.0F && this.isDamageSourceBlocked(source); // Copied from below
++ float f2 = 0.0F;
++
++ // CraftBukkit - Moved into damageEntity0(DamageSource, float)
++ if (false && amount > 0.0F && this.isDamageSourceBlocked(source)) {
+ this.hurtCurrentlyUsedShield(amount);
+ f1 = amount;
+ amount = 0.0F;
+@@ -1137,23 +1398,34 @@
+
+ this.walkAnimation.setSpeed(1.5F);
+ boolean flag1 = true;
+- if ((float)this.invulnerableTime > 10.0F && !source.is(DamageTypeTags.BYPASSES_COOLDOWN)) {
++
++ 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(source, amount - this.lastHurt);
++ // CraftBukkit start
++ if (!this.damageEntity0(source, amount - this.lastHurt)) {
++ return false;
++ }
++ // CraftBukkit end
+ this.lastHurt = amount;
+ flag1 = false;
+ } else {
++ // CraftBukkit start
++ if (!this.damageEntity0(source, amount)) {
++ return false;
++ }
+ this.lastHurt = amount;
+- this.invulnerableTime = 20;
+- this.actuallyHurt(source, amount);
++ this.invulnerableTime = this.invulnerableDuration; // CraftBukkit - restore use of maxNoDamageTicks
++ // this.damageEntity0(damagesource, f);
++ // CraftBukkit end
+ this.hurtDuration = 10;
+ this.hurtTime = this.hurtDuration;
+ }
+
+- if (source.is(DamageTypeTags.DAMAGES_HELMET) && !this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
++ // 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;
+ }
+@@ -1251,28 +1543,47 @@
+ } else {
+ ItemStack itemStack = null;
+
+- for (InteractionHand interactionHand : InteractionHand.values()) {
+- ItemStack itemInHand = this.getItemInHand(interactionHand);
+- if (itemInHand.is(Items.TOTEM_OF_UNDYING)) {
+- itemStack = itemInHand.copy();
+- itemInHand.shrink(1);
++ // CraftBukkit start
++ EnumHand hand = null;
++ ItemStack itemstack1 = ItemStack.EMPTY;
++ for (int j = 0; j < i; ++j) {
++ EnumHand enumhand = aenumhand[j];
++ itemstack1 = this.getItemInHand(enumhand);
++
++ if (itemstack1.is(Items.TOTEM_OF_UNDYING)) {
++ hand = enumhand; // CraftBukkit
++ itemstack = itemstack1.copy();
++ // itemstack1.subtract(1); // CraftBukkit
+ break;
+ }
+ }
+
+- if (itemStack != null) {
+- if (this instanceof ServerPlayer serverPlayer) {
+- serverPlayer.awardStat(Stats.ITEM_USED.get(Items.TOTEM_OF_UNDYING));
+- CriteriaTriggers.USED_TOTEM.trigger(serverPlayer, itemStack);
++ org.bukkit.inventory.EquipmentSlot handSlot = (hand != null) ? org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand) : null;
++ EntityResurrectEvent event = new EntityResurrectEvent((org.bukkit.entity.LivingEntity) this.getBukkitEntity(), handSlot);
++ event.setCancelled(itemstack == null);
++ this.level().getCraftServer().getPluginManager().callEvent(event);
++
++ 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));
+- this.level().broadcastEntityEvent(this, (byte)35);
++ // 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);
+ }
+
+ return itemStack != null;
+@@ -1372,17 +1689,27 @@
+ boolean flag = false;
+ if (entitySource instanceof WitherBoss) {
+ if (this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+- 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;
++ BlockPos blockposition = this.blockPosition();
++ IBlockData iblockdata = Blocks.WITHER_ROSE.defaultBlockState();
++
++ 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);
++ ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), new ItemStack(Items.WITHER_ROSE));
++
++ // 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);
+ }
+ }
+ }
+@@ -1398,28 +1727,41 @@
+ }
+
+ 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, mobLooting, 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 dropEquipment() {}
++
++ // 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))) {
++ int i = this.getExperienceReward();
++ return i;
++ } else {
++ return 0;
++ }
+ }
++ // CraftBukkit end
+
+ protected void dropExperience() {
+- 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 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 looting, boolean hitByPlayer) {
+ }
+@@ -1513,6 +1853,28 @@
+ return stack.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;
+ }
+@@ -1556,9 +1921,14 @@
+ boolean flag = super.causeFallDamage(fallDistance, multiplier, source);
+ int i = this.calculateFallDamage(fallDistance, multiplier);
+ 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(source, (float)i);
++ // this.damageEntity(damagesource, (float) i); // CraftBukkit - moved up
+ return true;
+ } else {
+ return flag;
+@@ -1609,10 +1980,8 @@
+
+ protected float getDamageAfterArmorAbsorb(DamageSource damageSource, float damageAmount) {
+ if (!damageSource.is(DamageTypeTags.BYPASSES_ARMOR)) {
+- this.hurtArmor(damageSource, damageAmount);
+- damageAmount = CombatRules.getDamageAfterAbsorb(
+- damageAmount, (float)this.getArmorValue(), (float)this.getAttributeValue(Attributes.ARMOR_TOUGHNESS)
+- );
++ // this.hurtArmor(damagesource, f); // CraftBukkit - Moved into damageEntity0(DamageSource, float)
++ damageAmount = CombatRules.getDamageAfterAbsorb(damageAmount, (float) this.getArmorValue(), (float) this.getAttributeValue(Attributes.ARMOR_TOUGHNESS));
+ }
+
+ return damageAmount;
+@@ -1622,14 +1991,19 @@
+ if (damageSource.is(DamageTypeTags.BYPASSES_EFFECTS)) {
+ return damageAmount;
+ } else {
+- if (this.hasEffect(MobEffects.DAMAGE_RESISTANCE) && !damageSource.is(DamageTypeTags.BYPASSES_RESISTANCE)) {
+- int i = (this.getEffect(MobEffects.DAMAGE_RESISTANCE).getAmplifier() + 1) * 5;
+- int i1 = 25 - i;
+- float f = damageAmount * (float)i1;
+- float f1 = damageAmount;
+- damageAmount = Math.max(f / 25.0F, 0.0F);
+- float f2 = f1 - damageAmount;
+- if (f2 > 0.0F && f2 < 3.4028235E37F) {
++ int i;
++
++ // 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 = damageAmount * (float) j;
++ float f2 = damageAmount;
++
++ damageAmount = Math.max(f1 / 25.0F, 0.0F);
++ float f3 = f2 - damageAmount;
++
++ if (f3 > 0.0F && f3 < 3.4028235E37F) {
+ if (this instanceof ServerPlayer) {
+ ((ServerPlayer)this).awardStat(Stats.DAMAGE_RESISTED, Math.round(f2 * 10.0F));
+ } else if (damageSource.getEntity() instanceof ServerPlayer) {
+@@ -1653,24 +2027,173 @@
+ }
+ }
+
+- protected void actuallyHurt(DamageSource damageSource, float damageAmount) {
+- if (!this.isInvulnerableTo(damageSource)) {
+- damageAmount = this.getDamageAfterArmorAbsorb(damageSource, damageAmount);
+- damageAmount = this.getDamageAfterMagicAbsorb(damageSource, damageAmount);
+- float var9 = Math.max(damageAmount - this.getAbsorptionAmount(), 0.0F);
+- this.setAbsorptionAmount(this.getAbsorptionAmount() - (damageAmount - var9));
+- float f1 = damageAmount - var9;
+- if (f1 > 0.0F && f1 < 3.4028235E37F && damageSource.getEntity() instanceof ServerPlayer serverPlayer) {
+- serverPlayer.awardStat(Stats.DAMAGE_DEALT_ABSORBED, Math.round(f1 * 10.0F));
++ // 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));
++
++ }
++ return -0.0;
++ }
++ };
++ float hardHatModifier = hardHat.apply((double) f).floatValue();
++ f += hardHatModifier;
++
++ Function<Double, Double> blocking = new Function<Double, Double>() {
++ @Override
++ public Double apply(Double f) {
++ return -((LivingEntity.this.isDamageSourceBlocked(damagesource)) ? f : 0.0);
++ }
++ };
++ float blockingModifier = blocking.apply((double) f).floatValue();
++ f += blockingModifier;
++
++ Function<Double, Double> armor = new Function<Double, Double>() {
++ @Override
++ public Double apply(Double f) {
++ return -(f - LivingEntity.this.getDamageAfterArmorAbsorb(damagesource, f.floatValue()));
++ }
++ };
++ float armorModifier = armor.apply((double) f).floatValue();
++ f += armorModifier;
++
++ Function<Double, Double> resistance = new Function<Double, Double>() {
++ @Override
++ public Double apply(Double f) {
++ if (!damagesource.is(DamageTypeTags.BYPASSES_EFFECTS) && LivingEntity.this.hasEffect(MobEffects.DAMAGE_RESISTANCE) && !damagesource.is(DamageTypeTags.BYPASSES_RESISTANCE)) {
++ int i = (LivingEntity.this.getEffect(MobEffects.DAMAGE_RESISTANCE).getAmplifier() + 1) * 5;
++ int j = 25 - i;
++ float f1 = f.floatValue() * (float) j;
++ return -(f - (f1 / 25.0F));
++ }
++ return -0.0;
++ }
++ };
++ float resistanceModifier = resistance.apply((double) f).floatValue();
++ f += resistanceModifier;
++
++ Function<Double, Double> magic = new Function<Double, Double>() {
++ @Override
++ public Double apply(Double f) {
++ return -(f - LivingEntity.this.getDamageAfterMagicAbsorb(damagesource, f.floatValue()));
++ }
++ };
++ float magicModifier = magic.apply((double) f).floatValue();
++ f += magicModifier;
++
++ Function<Double, Double> absorption = new Function<Double, Double>() {
++ @Override
++ public Double apply(Double f) {
++ return -(Math.max(f - Math.max(f - LivingEntity.this.getAbsorptionAmount(), 0.0F), 0.0F));
++ }
++ };
++ float absorptionModifier = absorption.apply((double) f).floatValue();
++
++ EntityDamageEvent event = CraftEventFactory.handleLivingEntityDamageEvent(this, damagesource, originalDamage, hardHatModifier, blockingModifier, armorModifier, resistanceModifier, magicModifier, absorptionModifier, hardHat, blocking, armor, resistance, magic, absorption);
++ if (damagesource.getEntity() instanceof net.minecraft.world.entity.player.Player) {
++ ((net.minecraft.world.entity.player.Player) damagesource.getEntity()).resetAttackStrengthTicker(); // Moved from EntityHuman in order to make the cooldown reset get called after the damage event is fired
+ }
+
+- if (var9 != 0.0F) {
+- this.getCombatTracker().recordDamage(damageSource, var9);
+- this.setHealth(this.getHealth() - var9);
+- this.setAbsorptionAmount(this.getAbsorptionAmount() - var9);
++ f = (float) event.getFinalDamage();
++
++ // Resistance
++ if (event.getDamage(DamageModifier.RESISTANCE) < 0) {
++ float f3 = (float) -event.getDamage(DamageModifier.RESISTANCE);
++ if (f3 > 0.0F && f3 < 3.4028235E37F) {
++ if (this instanceof ServerPlayer) {
++ ((ServerPlayer) this).awardStat(Stats.DAMAGE_RESISTED, Math.round(f3 * 10.0F));
++ } else if (damagesource.getEntity() instanceof ServerPlayer) {
++ ((ServerPlayer) damagesource.getEntity()).awardStat(Stats.DAMAGE_DEALT_RESISTED, Math.round(f3 * 10.0F));
++ }
++ }
++ }
++
++ // Apply damage to helmet
++ if (damagesource.is(DamageTypeTags.DAMAGES_HELMET) && !this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
++ this.hurtHelmet(damagesource, f);
++ }
++
++ // Apply damage to armor
++ if (!damagesource.is(DamageTypeTags.BYPASSES_ARMOR)) {
++ float armorDamage = (float) (event.getDamage() + event.getDamage(DamageModifier.BLOCKING) + event.getDamage(DamageModifier.HARD_HAT));
++ this.hurtArmor(damagesource, armorDamage);
++ }
++
++ // Apply blocking code // PAIL: steal from above
++ if (event.getDamage(DamageModifier.BLOCKING) < 0) {
++ this.level().broadcastEntityEvent(this, (byte) 29); // SPIGOT-4635 - shield damage sound
++ this.hurtCurrentlyUsedShield((float) -event.getDamage(DamageModifier.BLOCKING));
++ Entity entity = damagesource.getDirectEntity();
++
++ if (entity instanceof LivingEntity) {
++ this.blockUsingShield((LivingEntity) entity);
++ }
++ }
++
++ absorptionModifier = (float) -event.getDamage(DamageModifier.ABSORPTION);
++ this.setAbsorptionAmount(Math.max(this.getAbsorptionAmount() - absorptionModifier, 0.0F));
++ float f2 = absorptionModifier;
++
++ if (f2 > 0.0F && f2 < 3.4028235E37F && this instanceof net.minecraft.world.entity.player.Player) {
++ ((net.minecraft.world.entity.player.Player) this).awardStat(Stats.DAMAGE_ABSORBED, Math.round(f2 * 10.0F));
++ }
++ if (f2 > 0.0F && f2 < 3.4028235E37F) {
++ Entity entity = damagesource.getEntity();
++
++ if (entity instanceof ServerPlayer) {
++ ServerPlayer entityplayer = (ServerPlayer) entity;
++
++ entityplayer.awardStat(Stats.DAMAGE_DEALT_ABSORBED, Math.round(f2 * 10.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);
++ // 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() {
+@@ -1699,9 +2221,19 @@
+ }
+
+ public final void setArrowCount(int count) {
+- this.entityData.set(DATA_ARROW_COUNT_ID, 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 this.entityData.get(DATA_STINGER_COUNT_ID);
+ }
+@@ -1932,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
+ public abstract void setItemSlot(EquipmentSlot slot, ItemStack stack);
+
+@@ -2149,6 +2714,7 @@
+ }
+
+ if (this.onGround() && !this.level().isClientSide) {
++ if (getSharedFlag(7) && !CraftEventFactory.callToggleGlideEvent(this, false).isCancelled()) // CraftBukkit
+ this.setSharedFlag(7, false);
+ }
+ } else {
+@@ -2310,7 +2885,7 @@
+ }
+ }
+
+- this.detectEquipmentUpdates();
++ this.detectEquipmentUpdatesPublic(); // CraftBukkit
+ if (this.tickCount % 20 == 0) {
+ this.getCombatTracker().recheckStatus();
+ }
+@@ -2404,7 +2982,7 @@
+ this.refreshDirtyAttributes();
+ }
+
+- private void detectEquipmentUpdates() {
++ public void detectEquipmentUpdatesPublic() { // CraftBukkit
+ Map<EquipmentSlot, ItemStack> map = this.collectEquipmentChanges();
+ if (map != null) {
+ this.handleHandSwap(map);
+@@ -2679,7 +3284,8 @@
+ }
+
+ if (!this.level().isClientSide) {
+- this.setSharedFlag(7, sharedFlag);
++ if (flag != this.getSharedFlag(7) && !CraftEventFactory.callToggleGlideEvent(this, flag).isCancelled()) // CraftBukkit
++ this.setSharedFlag(7, flag);
+ }
+ }
+
+@@ -2850,15 +3475,22 @@
+
+ @Override
+ public boolean isPickable() {
+- return !this.isRemoved();
++ return !this.isRemoved() && this.collides; // CraftBukkit
+ }
+
+ @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;
+ }
+@@ -3042,10 +3684,26 @@
+ } else {
+ if (!this.useItem.isEmpty() && this.isUsingItem()) {
+ this.triggerItemUseEffects(this.useItem, 16);
+- ItemStack itemStack = this.useItem.finishUsingItem(this.level(), this);
+- if (itemStack != this.useItem) {
+- this.setItemInHand(usedItemHand, itemStack);
++ // 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
+
+ this.stopUsingItem();
+ }
+@@ -3115,39 +3780,72 @@
+ return this.fallFlyTicks;
+ }
+
+- public boolean randomTeleport(double x, double y, double z, boolean broadcastTeleport) {
+- double x1 = this.getX();
+- double y1 = this.getY();
+- double z1 = this.getZ();
+- double d = y;
+- boolean flag = false;
+- BlockPos blockPos = BlockPos.containing(x, y, z);
+- Level level = this.level();
+- if (level.hasChunkAt(blockPos)) {
+- boolean flag1 = false;
++ 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);
++ }
+
+- while (!flag1 && blockPos.getY() > level.getMinBuildHeight()) {
+- BlockPos blockPos1 = blockPos.below();
+- BlockState blockState = level.getBlockState(blockPos1);
+- if (blockState.blocksMotion()) {
+- flag1 = true;
++ 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();
++ double d6 = d1;
++ boolean flag1 = false;
++ BlockPos blockposition = BlockPos.containing(d0, d1, d2);
++ Level world = this.level();
++
++ if (world.hasChunkAt(blockposition)) {
++ boolean flag2 = false;
++
++ while (!flag2 && blockposition.getY() > world.getMinBuildHeight()) {
++ BlockPos blockposition1 = blockposition.below();
++ IBlockData iblockdata = world.getBlockState(blockposition1);
++
++ if (iblockdata.blocksMotion()) {
++ flag2 = true;
+ } else {
+ d--;
+ blockPos = blockPos1;
+ }
+ }
+
+- if (flag1) {
+- this.teleportTo(x, d, z);
+- if (level.noCollision(this) && !level.containsAnyLiquid(this.getBoundingBox())) {
+- flag = true;
++ if (flag2) {
++ // 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 (!flag) {
+- this.teleportTo(x1, y1, z1);
+- return false;
++ if (!flag1) {
++ // this.enderTeleportTo(d3, d4, d5); // CraftBukkit - already set the location back
++ return Optional.of(false); // CraftBukkit
+ } else {
+ if (broadcastTeleport) {
+ level.broadcastEntityEvent(this, (byte)46);
+@@ -3157,7 +3857,7 @@
+ pathfinderMob.getNavigation().stop();
+ }
+
+- return true;
++ return Optional.of(true); // CraftBukkit
+ }
+ }
+
+@@ -3321,9 +4021,14 @@
+ private void addEatEffect(ItemStack food, Level level, LivingEntity livingEntity) {
+ Item item = food.getItem();
+ if (item.isEdible()) {
+- for (Pair<MobEffectInstance, Float> pair : item.getFoodProperties().getEffects()) {
+- if (!level.isClientSide && pair.getFirst() != null && level.random.nextFloat() < pair.getSecond()) {
+- livingEntity.addEffect(new MobEffectInstance(pair.getFirst()));
++ List<Pair<MobEffectInstance, Float>> list = item.getFoodProperties().getEffects();
++ Iterator iterator = list.iterator();
++
++ while (iterator.hasNext()) {
++ 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()), EntityPotionEffectEvent.Cause.FOOD); // CraftBukkit
+ }
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/Mob.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/Mob.java.patch
new file mode 100644
index 0000000000..79388388c3
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/Mob.java.patch
@@ -0,0 +1,358 @@
+--- a/net/minecraft/world/entity/Mob.java
++++ b/net/minecraft/world/entity/Mob.java
+@@ -72,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 {
+ private static final EntityDataAccessor<Byte> DATA_MOB_FLAGS_ID = SynchedEntityData.defineId(Mob.class, EntityDataSerializers.BYTE);
+@@ -119,6 +134,8 @@
+ private BlockPos restrictCenter = BlockPos.ZERO;
+ private float restrictRadius = -1.0F;
+
++ public boolean aware = true; // CraftBukkit
++
+ protected Mob(EntityType<? extends Mob> entityType, Level level) {
+ super(entityType, level);
+ this.goalSelector = new GoalSelector(level.getProfilerSupplier());
+@@ -136,8 +161,11 @@
+ }
+ }
+
+- protected void registerGoals() {
++ // CraftBukkit start
++ public void setPersistenceRequired(boolean persistenceRequired) {
++ this.persistenceRequired = persistenceRequired;
+ }
++ // CraftBukkit end
+
+ public static AttributeSupplier.Builder createMobAttributes() {
+ return LivingEntity.createLivingAttributes().add(Attributes.FOLLOW_RANGE, 16.0).add(Attributes.ATTACK_KNOCKBACK);
+@@ -218,9 +278,40 @@
+ }
+
+ public void setTarget(@Nullable LivingEntity target) {
+- this.target = 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
+ public boolean canAttackType(EntityType<?> type) {
+ return type != EntityType.GHAST;
+@@ -350,6 +449,12 @@
+ return null;
+ }
+
++ // CraftBukkit start - Add delegate method
++ public SoundEvent getAmbientSound0() {
++ return getAmbientSound();
++ }
++ // CraftBukkit end
++
+ @Override
+ public void addAdditionalSaveData(CompoundTag compound) {
+ super.addAdditionalSaveData(compound);
+@@ -392,9 +512,9 @@
+ list3.add(FloatTag.valueOf(f1));
+ }
+
+- compound.put("HandDropChances", list3);
+- if (this.leashHolder != null) {
+- CompoundTag compoundTag1 = 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();
+ compoundTag1.putUUID("UUID", uUID);
+@@ -421,16 +543,27 @@
+ if (this.isNoAi()) {
+ compound.putBoolean("NoAI", this.isNoAi());
+ }
++
++ compound.putBoolean("Bukkit.Aware", this.aware); // CraftBukkit
+ }
+
+ @Override
+ 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)) {
+ this.setCanPickUpLoot(compound.getBoolean("CanPickUpLoot"));
+ }
+
+- this.persistenceRequired = compound.getBoolean("PersistenceRequired");
++ boolean data = compound.getBoolean("PersistenceRequired");
++ if (isLevelAtLeast(compound, 1) || data) {
++ this.persistenceRequired = data;
++ }
++ // CraftBukkit end
++ ListTag nbttaglist;
++ int i;
++
+ if (compound.contains("ArmorItems", 9)) {
+ ListTag list = compound.getList("ArmorItems", 10);
+
+@@ -474,6 +610,11 @@
+ }
+
+ this.setNoAi(compound.getBoolean("NoAI"));
++ // CraftBukkit start
++ if (compound.contains("Bukkit.Aware")) {
++ this.aware = compound.getBoolean("Bukkit.Aware");
++ }
++ // CraftBukkit end
+ }
+
+ @Override
+@@ -543,9 +681,10 @@
+ }
+
+ protected void pickUpItem(ItemEntity itemEntity) {
+- ItemStack item = itemEntity.getItem();
+- ItemStack itemStack = this.equipItemIfPossible(item.copy());
+- if (!itemStack.isEmpty()) {
++ ItemStack itemstack = itemEntity.getItem();
++ ItemStack itemstack1 = this.equipItemIfPossible(itemstack.copy(), itemEntity); // CraftBukkit - add item
++
++ if (!itemstack1.isEmpty()) {
+ this.onItemPickup(itemEntity);
+ this.take(itemEntity, itemStack.getCount());
+ item.shrink(itemStack.getCount());
+@@ -556,19 +696,35 @@
+ }
+
+ public ItemStack equipItemIfPossible(ItemStack stack) {
+- EquipmentSlot equipmentSlotForItem = getEquipmentSlotForItem(stack);
+- ItemStack itemBySlot = this.getItemBySlot(equipmentSlotForItem);
+- boolean canReplaceCurrentItem = this.canReplaceCurrentItem(stack, itemBySlot);
+- if (equipmentSlotForItem.isArmor() && !canReplaceCurrentItem) {
+- equipmentSlotForItem = EquipmentSlot.MAINHAND;
+- itemBySlot = this.getItemBySlot(equipmentSlotForItem);
+- canReplaceCurrentItem = itemBySlot.isEmpty();
++ // 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 (enumitemslot.isArmor() && !flag) {
++ enumitemslot = EquipmentSlot.MAINHAND;
++ itemstack1 = this.getItemBySlot(enumitemslot);
++ flag = itemstack1.isEmpty();
+ }
+
+- if (canReplaceCurrentItem && this.canHoldItem(stack)) {
+- double d = (double)this.getEquipmentDropChance(equipmentSlotForItem);
+- if (!itemBySlot.isEmpty() && (double)Math.max(this.random.nextFloat() - 0.1F, 0.0F) < d) {
+- this.spawnAtLocation(itemBySlot);
++ // 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 (equipmentSlotForItem.isArmor() && stack.getCount() > 1) {
+@@ -710,7 +875,8 @@
+
+ @Override
+ protected final void serverAiStep() {
+- this.noActionTime++;
++ ++this.noActionTime;
++ if (!this.aware) return; // CraftBukkit
+ this.level().getProfiler().push("sensing");
+ this.sensing.tick();
+ this.level().getProfiler().pop();
+@@ -1075,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);
+@@ -1095,9 +1297,16 @@
+ }
+ }
+
+- private InteractionResult checkAndHandleImportantInteractions(Player player, InteractionHand hand) {
+- ItemStack itemInHand = player.getItemInHand(hand);
+- if (itemInHand.is(Items.LEAD) && this.canBeLeashed(player)) {
++ private InteractionResult checkAndHandleImportantInteractions(Player player, EnumHand hand) {
++ ItemStack itemstack = player.getItemInHand(hand);
++
++ 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);
+ itemInHand.shrink(1);
+ return InteractionResult.sidedSuccess(this.level().isClientSide);
+@@ -1162,8 +1372,15 @@
+ return this.restrictRadius != -1.0F;
+ }
+
++ // CraftBukkit start
+ @Nullable
+ 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 {
+@@ -1196,7 +1418,12 @@
+ }
+ }
+
+- this.level().addFreshEntity(mob);
++ // CraftBukkit start
++ if (CraftEventFactory.callEntityTransformEvent(this, t0, transformReason).isCancelled()) {
++ return null;
++ }
++ this.level().addFreshEntity(t0, spawnReason);
++ // CraftBukkit end
+ if (this.isPassenger()) {
+ Entity vehicle = this.getVehicle();
+ this.stopRiding();
+@@ -1216,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
+ }
+ }
+ }
+@@ -1226,7 +1456,9 @@
+ this.leashHolder = null;
+ this.leashInfoTag = null;
+ if (!this.level().isClientSide && dropLeash) {
+- this.spawnAtLocation(Items.LEAD);
++ this.forceDrops = true; // CraftBukkit
++ this.spawnAtLocation((IMaterial) Items.LEAD);
++ this.forceDrops = false; // CraftBukkit
+ }
+
+ if (!this.level().isClientSide && broadcastPacket && this.level() instanceof ServerLevel) {
+@@ -1271,8 +1505,10 @@
+
+ @Override
+ public boolean startRiding(Entity entity, boolean force) {
+- boolean flag = super.startRiding(entity, force);
+- if (flag && this.isLeashed()) {
++ boolean flag1 = super.startRiding(entity, force);
++
++ if (flag1 && this.isLeashed()) {
++ this.level().getCraftServer().getPluginManager().callEvent(new EntityUnleashEvent(this.getBukkitEntity(), UnleashReason.UNKNOWN)); // CraftBukkit
+ this.dropLeash(true, true);
+ }
+
+@@ -1295,7 +1533,9 @@
+ }
+
+ if (this.tickCount > 100) {
+- this.spawnAtLocation(Items.LEAD);
++ this.forceDrops = true; // CraftBukkit
++ this.spawnAtLocation((IMaterial) Items.LEAD);
++ this.forceDrops = false; // CraftBukkit
+ this.leashInfoTag = null;
+ }
+ }
+@@ -1375,9 +1614,17 @@
+ f1 += (float)EnchantmentHelper.getKnockbackBonus(this);
+ }
+
+- int fireAspect = EnchantmentHelper.getFireAspect(this);
+- if (fireAspect > 0) {
+- entity.setSecondsOnFire(fireAspect * 4);
++ int i = EnchantmentHelper.getFireAspect(this);
++
++ if (i > 0) {
++ // 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);
+@@ -1451,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(slot -> {
+ if (!slot.isEmpty()) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/NeutralMob.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/NeutralMob.java.patch
new file mode 100644
index 0000000000..a3a2598a99
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/NeutralMob.java.patch
@@ -0,0 +1,24 @@
+--- a/net/minecraft/world/entity/NeutralMob.java
++++ b/net/minecraft/world/entity/NeutralMob.java
+@@ -100,9 +106,9 @@
+ }
+
+ default void stopBeingAngry() {
+- this.setLastHurtByMob(null);
+- this.setPersistentAngerTarget(null);
+- this.setTarget(null);
++ this.setLastHurtByMob((LivingEntity) null);
++ this.setPersistentAngerTarget((UUID) null);
++ this.setTarget((LivingEntity) null, org.bukkit.event.entity.EntityTargetEvent.TargetReason.FORGOT_TARGET, true); // CraftBukkit
+ this.setRemainingPersistentAngerTime(0);
+ }
+
+@@ -115,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-vineflower-stripped/net/minecraft/world/entity/PathfinderMob.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/PathfinderMob.java.patch
new file mode 100644
index 0000000000..444be03daa
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -9,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 {
+ protected static final float DEFAULT_WALK_TARGET_VALUE = 0.0F;
+@@ -49,6 +54,7 @@
+ float f = this.distanceTo(leashHolder);
+ 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);
+ }
+
+@@ -57,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-vineflower-stripped/net/minecraft/world/entity/ai/attributes/Attributes.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/attributes/Attributes.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/attributes/Attributes.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java.patch
new file mode 100644
index 0000000000..6f6c7099a9
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java.patch
@@ -0,0 +1,88 @@
+--- a/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java
++++ b/net/minecraft/world/entity/ai/behavior/AssignProfessionFromJobSite.java
+@@ -13,41 +9,52 @@
+ 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 static BehaviorControl<Villager> create() {
+- return BehaviorBuilder.create(
+- instance -> instance.group(instance.present(MemoryModuleType.POTENTIAL_JOB_SITE), instance.registered(MemoryModuleType.JOB_SITE))
+- .apply(
+- instance,
+- (potentialJobSite, jobSite) -> (level, villager, gameTime) -> {
+- GlobalPos globalPos = instance.get(potentialJobSite);
+- if (!globalPos.pos().closerToCenterThan(villager.position(), 2.0) && !villager.assignProfessionWhenSpawned()) {
+- return false;
+- } else {
+- potentialJobSite.erase();
+- jobSite.set(globalPos);
+- level.broadcastEntityEvent(villager, (byte)14);
+- if (villager.getVillagerData().getProfession() != VillagerProfession.NONE) {
+- return true;
+- } else {
+- MinecraftServer server = level.getServer();
+- Optional.ofNullable(server.getLevel(globalPos.dimension()))
+- .flatMap(posLevel -> posLevel.getPoiManager().getType(globalPos.pos()))
+- .flatMap(
+- poi -> BuiltInRegistries.VILLAGER_PROFESSION
+- .stream()
+- .filter(profession -> profession.heldJobSite().test((Holder<PoiType>)poi))
+- .findFirst()
+- )
+- .ifPresent(profession -> {
+- villager.setVillagerData(villager.getVillagerData().setProfession(profession));
+- villager.refreshBrain(level);
+- });
+- return true;
+- }
++ return BehaviorBuilder.create((behaviorbuilder_b) -> {
++ return behaviorbuilder_b.group(behaviorbuilder_b.present(MemoryModuleType.POTENTIAL_JOB_SITE), behaviorbuilder_b.registered(MemoryModuleType.JOB_SITE)).apply(behaviorbuilder_b, (memoryaccessor, memoryaccessor1) -> {
++ return (worldserver, entityvillager, i) -> {
++ GlobalPos globalpos = (GlobalPos) behaviorbuilder_b.get(memoryaccessor);
++
++ if (!globalpos.pos().closerToCenterThan(entityvillager.position(), 2.0D) && !entityvillager.assignProfessionWhenSpawned()) {
++ return false;
++ } else {
++ memoryaccessor.erase();
++ memoryaccessor1.set(globalpos);
++ worldserver.broadcastEntityEvent(entityvillager, (byte) 14);
++ if (entityvillager.getVillagerData().getProfession() != VillagerProfession.NONE) {
++ return true;
++ } else {
++ MinecraftServer minecraftserver = worldserver.getServer();
++
++ Optional.ofNullable(minecraftserver.getLevel(globalpos.dimension())).flatMap((worldserver1) -> {
++ return worldserver1.getPoiManager().getType(globalpos.pos());
++ }).flatMap((holder) -> {
++ return BuiltInRegistries.VILLAGER_PROFESSION.stream().filter((villagerprofession) -> {
++ return villagerprofession.heldJobSite().test(holder);
++ }).findFirst();
++ }).ifPresent((villagerprofession) -> {
++ // 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-vineflower-stripped/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java.patch
new file mode 100644
index 0000000000..1b5acf363d
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java.patch
@@ -0,0 +1,80 @@
+--- a/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java
++++ b/net/minecraft/world/entity/ai/behavior/BabyFollowAdult.java
+@@ -9,6 +7,12 @@
+ import net.minecraft.world.entity.ai.behavior.declarative.MemoryAccessor;
+ 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 {
+ public static OneShot<AgeableMob> create(UniformInt followRange, float speedModifier) {
+@@ -16,33 +25,37 @@
+ }
+
+ public static OneShot<AgeableMob> create(UniformInt followRange, Function<LivingEntity, Float> speedModifier) {
+- return BehaviorBuilder.create(
+- instance -> instance.group(
+- instance.present(MemoryModuleType.NEAREST_VISIBLE_ADULT),
+- instance.registered(MemoryModuleType.LOOK_TARGET),
+- instance.absent(MemoryModuleType.WALK_TARGET)
+- )
+- .apply(
+- instance,
+- (nearestVisibleAdult, lookTarget, walkTarget) -> (level, mob, gameTime) -> {
+- if (!mob.isBaby()) {
+- return false;
+- } else {
+- AgeableMob ageableMob = instance.get(nearestVisibleAdult);
+- if (mob.closerThan(ageableMob, (double)(followRange.getMaxValue() + 1))
+- && !mob.closerThan(ageableMob, (double)followRange.getMinValue())) {
+- WalkTarget walkTarget1 = new WalkTarget(
+- new EntityTracker(ageableMob, false), speedModifier.apply(mob), followRange.getMinValue() - 1
+- );
+- lookTarget.set(new EntityTracker(ageableMob, true));
+- walkTarget.set(walkTarget1);
+- return true;
+- } else {
+- return false;
+- }
+- }
++ return BehaviorBuilder.create((behaviorbuilder_b) -> {
++ return behaviorbuilder_b.group(behaviorbuilder_b.present(MemoryModuleType.NEAREST_VISIBLE_ADULT), behaviorbuilder_b.registered(MemoryModuleType.LOOK_TARGET), behaviorbuilder_b.absent(MemoryModuleType.WALK_TARGET)).apply(behaviorbuilder_b, (memoryaccessor, memoryaccessor1, memoryaccessor2) -> {
++ return (worldserver, entityageable, i) -> {
++ if (!entityageable.isBaby()) {
++ return false;
++ } else {
++ LivingEntity entityageable1 = (AgeableMob) behaviorbuilder_b.get(memoryaccessor); // CraftBukkit - type
++
++ 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(entityageable1, true));
++ memoryaccessor2.set(memorytarget);
++ return true;
++ } else {
++ return false;
++ }
++ }
++ };
++ });
++ });
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java.patch
new file mode 100644
index 0000000000..fc5a721a15
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -81,14 +92,24 @@
+ }
+
+ public static void throwItem(LivingEntity entity, ItemStack stack, Vec3 offset, Vec3 speedMultiplier, float yOffset) {
+- double d = entity.getEyeY() - (double)yOffset;
+- ItemEntity itemEntity = new ItemEntity(entity.level(), entity.getX(), d, entity.getZ(), stack);
+- itemEntity.setThrower(entity);
+- Vec3 vec3 = offset.subtract(entity.position());
+- vec3 = vec3.normalize().multiply(speedMultiplier.x, speedMultiplier.y, speedMultiplier.z);
+- itemEntity.setDeltaMovement(vec3);
+- itemEntity.setDefaultPickUpDelay();
+- entity.level().addFreshEntity(itemEntity);
++ 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);
++
++ entityitem.setThrower(entity);
++ Vec3 vec3d2 = offset.subtract(entity.position());
++
++ 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 radius) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java.patch
new file mode 100644
index 0000000000..7dbdb7864e
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java.patch
@@ -0,0 +1,70 @@
+--- a/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java
++++ b/net/minecraft/world/entity/ai/behavior/GoToWantedItem.java
+@@ -16,35 +20,38 @@
+ }
+
+ public static <E extends LivingEntity> BehaviorControl<E> create(Predicate<E> canWalkToItem, float speedModifier, boolean hasTarget, int maxDistToWalk) {
+- return BehaviorBuilder.create(
+- instance -> {
+- BehaviorBuilder<E, ? extends MemoryAccessor<? extends K1, WalkTarget>> behaviorBuilder = hasTarget
+- ? instance.registered(MemoryModuleType.WALK_TARGET)
+- : instance.absent(MemoryModuleType.WALK_TARGET);
+- return instance.group(
+- instance.registered(MemoryModuleType.LOOK_TARGET),
+- behaviorBuilder,
+- instance.present(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM),
+- instance.registered(MemoryModuleType.ITEM_PICKUP_COOLDOWN_TICKS)
+- )
+- .apply(
+- instance,
+- (lookTarget, walkTarget, nearestVisibleWantedItem, itemPickupCooldownTicks) -> (level, entity, gameTime) -> {
+- ItemEntity itemEntity = instance.get(nearestVisibleWantedItem);
+- if (instance.tryGet(itemPickupCooldownTicks).isEmpty()
+- && canWalkToItem.test(entity)
+- && itemEntity.closerThan(entity, (double)maxDistToWalk)
+- && entity.level().getWorldBorder().isWithinBounds(itemEntity.blockPosition())) {
+- WalkTarget walkTarget1 = new WalkTarget(new EntityTracker(itemEntity, false), speedModifier, 0);
+- lookTarget.set(new EntityTracker(itemEntity, true));
+- walkTarget.set(walkTarget1);
+- return true;
+- } else {
+- return false;
+- }
++ return BehaviorBuilder.create((behaviorbuilder_b) -> {
++ BehaviorBuilder<E, ? extends MemoryAccessor<? extends K1, WalkTarget>> behaviorbuilder = hasTarget ? behaviorbuilder_b.registered(MemoryModuleType.WALK_TARGET) : behaviorbuilder_b.absent(MemoryModuleType.WALK_TARGET);
++
++ return behaviorbuilder_b.group(behaviorbuilder_b.registered(MemoryModuleType.LOOK_TARGET), behaviorbuilder, behaviorbuilder_b.present(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM), behaviorbuilder_b.registered(MemoryModuleType.ITEM_PICKUP_COOLDOWN_TICKS)).apply(behaviorbuilder_b, (memoryaccessor, memoryaccessor1, memoryaccessor2, memoryaccessor3) -> {
++ return (worldserver, entityliving, j) -> {
++ ItemEntity entityitem = (ItemEntity) behaviorbuilder_b.get(memoryaccessor2);
++
++ 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);
++
++ 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-vineflower-stripped/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java.patch
new file mode 100644
index 0000000000..803d5c87b9
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java.patch
@@ -0,0 +1,59 @@
+--- a/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java
++++ b/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java
+@@ -19,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> {
+ private static final int HARVEST_DURATION = 200;
+@@ -109,8 +105,11 @@
+ BlockState blockState = level.getBlockState(this.aboveFarmlandPos);
+ Block block = blockState.getBlock();
+ Block block1 = level.getBlockState(this.aboveFarmlandPos.below()).getBlock();
+- if (block instanceof CropBlock && ((CropBlock)block).isMaxAge(blockState)) {
++
++ 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 && owner.hasFarmSeeds()) {
+@@ -119,11 +118,20 @@
+ for (int i = 0; i < inventory.getContainerSize(); i++) {
+ ItemStack item = inventory.getItem(i);
+ boolean flag = false;
+- if (!item.isEmpty() && item.is(ItemTags.VILLAGER_PLANTABLE_SEEDS) && item.getItem() instanceof BlockItem blockItem) {
+- BlockState blockState1 = blockItem.getBlock().defaultBlockState();
+- level.setBlockAndUpdate(this.aboveFarmlandPos, blockState1);
+- level.gameEvent(GameEvent.BLOCK_PLACE, this.aboveFarmlandPos, GameEvent.Context.of(owner, blockState1));
+- flag = true;
++
++ if (!itemstack.isEmpty() && itemstack.is(ItemTags.VILLAGER_PLANTABLE_SEEDS)) {
++ Item item = itemstack.getItem();
++
++ if (item instanceof BlockItem) {
++ BlockItem itemblock = (BlockItem) item;
++ IBlockData iblockdata1 = itemblock.getBlock().defaultBlockState();
++
++ 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
++ }
+ }
+
+ if (flag) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java.patch
new file mode 100644
index 0000000000..ad54be668c
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java.patch
@@ -0,0 +1,134 @@
+--- a/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java
++++ b/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java
+@@ -31,62 +33,78 @@
+ private static final double MAX_DISTANCE_TO_HOLD_DOOR_OPEN_FOR_OTHER_MOBS = 2.0;
+
+ public static BehaviorControl<LivingEntity> create() {
+- MutableObject<Node> mutableObject = new MutableObject<>(null);
+- MutableInt mutableInt = new MutableInt(0);
+- return BehaviorBuilder.create(
+- instance -> instance.group(
+- instance.present(MemoryModuleType.PATH),
+- instance.registered(MemoryModuleType.DOORS_TO_CLOSE),
+- instance.registered(MemoryModuleType.NEAREST_LIVING_ENTITIES)
+- )
+- .apply(
+- instance,
+- (navigationPath, doorsToClose, nearestLivingEntities) -> (level, entity, gameTime) -> {
+- Path path = instance.get(navigationPath);
+- Optional<Set<GlobalPos>> optional = instance.tryGet(doorsToClose);
+- if (!path.notStarted() && !path.isDone()) {
+- if (Objects.equals(mutableObject.getValue(), path.getNextNode())) {
+- mutableInt.setValue(20);
+- } else if (mutableInt.decrementAndGet() > 0) {
+- return false;
+- }
+-
+- mutableObject.setValue(path.getNextNode());
+- Node previousNode = path.getPreviousNode();
+- Node nextNode = path.getNextNode();
+- BlockPos blockPos = previousNode.asBlockPos();
+- BlockState blockState = level.getBlockState(blockPos);
+- if (blockState.is(BlockTags.WOODEN_DOORS, state -> state.getBlock() instanceof DoorBlock)) {
+- DoorBlock doorBlock = (DoorBlock)blockState.getBlock();
+- if (!doorBlock.isOpen(blockState)) {
+- doorBlock.setOpen(entity, level, blockState, blockPos, true);
+- }
+-
+- optional = rememberDoorToClose(doorsToClose, optional, level, blockPos);
+- }
+-
+- BlockPos blockPos1 = nextNode.asBlockPos();
+- BlockState blockState1 = level.getBlockState(blockPos1);
+- if (blockState1.is(BlockTags.WOODEN_DOORS, state -> state.getBlock() instanceof DoorBlock)) {
+- DoorBlock doorBlock1 = (DoorBlock)blockState1.getBlock();
+- if (!doorBlock1.isOpen(blockState1)) {
+- doorBlock1.setOpen(entity, level, blockState1, blockPos1, true);
+- optional = rememberDoorToClose(doorsToClose, optional, level, blockPos1);
+- }
+- }
+-
+- optional.ifPresent(
+- doorPositions -> closeDoorsThatIHaveOpenedOrPassedThrough(
+- level, entity, previousNode, nextNode, (Set<GlobalPos>)doorPositions, instance.tryGet(nearestLivingEntities)
+- )
+- );
+- return true;
+- } else {
++ MutableObject<Node> mutableobject = new MutableObject((Object) null);
++ MutableInt mutableint = new MutableInt(0);
++
++ return BehaviorBuilder.create((behaviorbuilder_b) -> {
++ return behaviorbuilder_b.group(behaviorbuilder_b.present(MemoryModuleType.PATH), behaviorbuilder_b.registered(MemoryModuleType.DOORS_TO_CLOSE), behaviorbuilder_b.registered(MemoryModuleType.NEAREST_LIVING_ENTITIES)).apply(behaviorbuilder_b, (memoryaccessor, memoryaccessor1, memoryaccessor2) -> {
++ return (worldserver, entityliving, i) -> {
++ Path pathentity = (Path) behaviorbuilder_b.get(memoryaccessor);
++ Optional<Set<GlobalPos>> optional = behaviorbuilder_b.tryGet(memoryaccessor1);
++
++ if (!pathentity.notStarted() && !pathentity.isDone()) {
++ if (Objects.equals(mutableobject.getValue(), pathentity.getNextNode())) {
++ mutableint.setValue(20);
++ } else if (mutableint.decrementAndGet() > 0) {
++ return false;
++ }
++
++ mutableobject.setValue(pathentity.getNextNode());
++ Node pathpoint = pathentity.getPreviousNode();
++ Node pathpoint1 = pathentity.getNextNode();
++ BlockPos blockposition = pathpoint.asBlockPos();
++ IBlockData iblockdata = worldserver.getBlockState(blockposition);
++
++ if (iblockdata.is(BlockTags.WOODEN_DOORS, (blockbase_blockdata) -> {
++ return blockbase_blockdata.getBlock() instanceof DoorBlock;
++ })) {
++ DoorBlock blockdoor = (DoorBlock) iblockdata.getBlock();
++
++ 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, worldserver, blockposition);
++ }
++
++ BlockPos blockposition1 = pathpoint1.asBlockPos();
++ IBlockData iblockdata1 = worldserver.getBlockState(blockposition1);
++
++ if (iblockdata1.is(BlockTags.WOODEN_DOORS, (blockbase_blockdata) -> {
++ return blockbase_blockdata.getBlock() instanceof DoorBlock;
++ })) {
++ DoorBlock blockdoor1 = (DoorBlock) iblockdata1.getBlock();
++
++ 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);
++ }
++ }
++
++ optional.ifPresent((set) -> {
++ closeDoorsThatIHaveOpenedOrPassedThrough(worldserver, entityliving, pathpoint, pathpoint1, set, behaviorbuilder_b.tryGet(memoryaccessor2));
++ });
++ return true;
++ } else {
++ return false;
++ }
++ };
++ });
++ });
+ }
+
+ public static void closeDoorsThatIHaveOpenedOrPassedThrough(
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java.patch
new file mode 100644
index 0000000000..3aef9ce46c
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java.patch
@@ -0,0 +1,40 @@
+--- a/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java
++++ b/net/minecraft/world/entity/ai/behavior/PrepareRamNearestTarget.java
+@@ -25,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> {
+ public static final int TIME_OUT_DURATION = 160;
+@@ -71,10 +60,22 @@
+
+ @Override
+ protected void start(ServerLevel level, PathfinderMob entity, long gameTime) {
+- Brain<?> brain = entity.getBrain();
+- brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES)
+- .flatMap(memory -> memory.findClosest(entity1 -> this.ramTargeting.test(entity, entity1)))
+- .ifPresent(entity1 -> this.chooseRamPosition(entity, entity1));
++ Brain<?> behaviorcontroller = entity.getBrain();
++
++ behaviorcontroller.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES).flatMap((nearestvisiblelivingentities) -> {
++ return nearestvisiblelivingentities.findClosest((entityliving) -> {
++ return this.ramTargeting.test(entity, entityliving);
++ });
++ }).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);
++ });
+ }
+
+ @Override
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/ResetProfession.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/ResetProfession.java.patch
new file mode 100644
index 0000000000..25e3b3da22
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/ResetProfession.java.patch
@@ -0,0 +1,57 @@
+--- a/net/minecraft/world/entity/ai/behavior/ResetProfession.java
++++ b/net/minecraft/world/entity/ai/behavior/ResetProfession.java
+@@ -8,26 +6,35 @@
+ 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 static BehaviorControl<Villager> create() {
+- return BehaviorBuilder.create(
+- instance -> instance.group(instance.absent(MemoryModuleType.JOB_SITE))
+- .apply(
+- instance,
+- jobSite -> (level, villager, gameTime) -> {
+- 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(level);
+- return true;
+- } else {
+- return false;
+- }
+- }
+- )
+- );
++ return BehaviorBuilder.create((behaviorbuilder_b) -> {
++ return behaviorbuilder_b.group(behaviorbuilder_b.absent(MemoryModuleType.JOB_SITE)).apply(behaviorbuilder_b, (memoryaccessor) -> {
++ return (worldserver, entityvillager, i) -> {
++ VillagerData villagerdata = entityvillager.getVillagerData();
++
++ 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-vineflower-stripped/net/minecraft/world/entity/ai/behavior/StartAttacking.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/StartAttacking.java.patch
new file mode 100644
index 0000000000..0a8ebe023a
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/StartAttacking.java.patch
@@ -0,0 +1,41 @@
+--- a/net/minecraft/world/entity/ai/behavior/StartAttacking.java
++++ b/net/minecraft/world/entity/ai/behavior/StartAttacking.java
+@@ -9,6 +8,10 @@
+ import net.minecraft.world.entity.ai.behavior.declarative.BehaviorBuilder;
+ import net.minecraft.world.entity.ai.behavior.declarative.MemoryAccessor;
+ 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 {
+ public static <E extends Mob> BehaviorControl<E> create(Function<E, Optional<? extends LivingEntity>> targetFinder) {
+@@ -22,8 +40,9 @@
+ if (!canAttack.test(mob)) {
+ return false;
+ } else {
+- Optional<? extends LivingEntity> optional = targetFinder.apply(mob);
+- if (optional.isEmpty()) {
++ // CraftBukkit start
++ EntityTargetEvent event = CraftEventFactory.callEntityTargetLivingEvent(entityinsentient, entityliving, (entityliving instanceof ServerPlayer) ? EntityTargetEvent.TargetReason.CLOSEST_PLAYER : EntityTargetEvent.TargetReason.CLOSEST_ENTITY);
++ if (event.isCancelled()) {
+ return false;
+ } else {
+ LivingEntity livingEntity = optional.get();
+@@ -35,6 +45,15 @@
+ return true;
+ }
+ }
++ 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-vineflower-stripped/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java.patch
new file mode 100644
index 0000000000..9e38734ea7
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java.patch
@@ -0,0 +1,73 @@
+--- a/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java
++++ b/net/minecraft/world/entity/ai/behavior/StopAttackingIfTargetInvalid.java
+@@ -10,6 +8,12 @@
+ import net.minecraft.world.entity.ai.behavior.declarative.MemoryAccessor;
+ 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;
+
+@@ -27,29 +38,34 @@
+ }, true);
+ }
+
+- public static <E extends Mob> BehaviorControl<E> create(
+- Predicate<LivingEntity> canStopAttacking, BiConsumer<E, LivingEntity> onStopAttacking, boolean canGrowTiredOfTryingToReachTarget
+- ) {
+- return BehaviorBuilder.create(
+- instance -> instance.group(instance.present(MemoryModuleType.ATTACK_TARGET), instance.registered(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE))
+- .apply(
+- instance,
+- (attackTarget, cantReachWalkTargetSince) -> (level, mob, gameTime) -> {
+- LivingEntity livingEntity = instance.get(attackTarget);
+- if (mob.canAttack(livingEntity)
+- && (!canGrowTiredOfTryingToReachTarget || !isTiredOfTryingToReachTarget(mob, instance.tryGet(cantReachWalkTargetSince)))
+- && livingEntity.isAlive()
+- && livingEntity.level() == mob.level()
+- && !canStopAttacking.test(livingEntity)) {
+- return true;
+- } else {
+- onStopAttacking.accept(mob, livingEntity);
+- attackTarget.erase();
+- return true;
+- }
+- }
+- )
+- );
++ public static <E extends Mob> BehaviorControl<E> create(Predicate<LivingEntity> canStopAttacking, BiConsumer<E, LivingEntity> onStopAttacking, boolean canGrowTiredOfTryingToReachTarget) {
++ return BehaviorBuilder.create((behaviorbuilder_b) -> {
++ return behaviorbuilder_b.group(behaviorbuilder_b.present(MemoryModuleType.ATTACK_TARGET), behaviorbuilder_b.registered(MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE)).apply(behaviorbuilder_b, (memoryaccessor, memoryaccessor1) -> {
++ return (worldserver, entityinsentient, i) -> {
++ LivingEntity entityliving = (LivingEntity) behaviorbuilder_b.get(memoryaccessor);
++
++ if (entityinsentient.canAttack(entityliving) && (!canGrowTiredOfTryingToReachTarget || !isTiredOfTryingToReachTarget(entityinsentient, behaviorbuilder_b.tryGet(memoryaccessor1))) && entityliving.isAlive() && entityliving.level() == entityinsentient.level() && !canStopAttacking.test(entityliving)) {
++ return true;
++ } else {
++ // 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;
++ }
++ };
++ });
++ });
+ }
+
+ private static boolean isTiredOfTryingToReachTarget(LivingEntity entity, Optional<Long> timeSinceInvalidTarget) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java.patch
new file mode 100644
index 0000000000..df807a5849
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java.patch
@@ -0,0 +1,38 @@
+--- a/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java
++++ b/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java
+@@ -16,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> {
+ private static final int INTERACT_DIST_SQR = 5;
+@@ -113,13 +120,19 @@
+ if (breedOffspring == null) {
+ return Optional.empty();
+ } else {
++ 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);
+- breedOffspring.setAge(-24000);
+- breedOffspring.moveTo(parent.getX(), parent.getY(), parent.getZ(), 0.0F, 0.0F);
+- level.addFreshEntityWithPassengers(breedOffspring);
+- level.broadcastEntityEvent(breedOffspring, (byte)12);
+- return Optional.of(breedOffspring);
++ level.addFreshEntityWithPassengers(entityvillager2, CreatureSpawnEvent.SpawnReason.BREEDING);
++ // CraftBukkit end
++ level.broadcastEntityEvent(entityvillager2, (byte) 12);
++ return Optional.of(entityvillager2);
+ }
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java.patch
new file mode 100644
index 0000000000..099dd811ea
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -73,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-vineflower-stripped/net/minecraft/world/entity/ai/goal/EatBlockGoal.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/goal/EatBlockGoal.java.patch
new file mode 100644
index 0000000000..d0cc4e84a3
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/goal/EatBlockGoal.java.patch
@@ -0,0 +1,44 @@
+--- 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;
+ private static final Predicate<BlockState> IS_TALL_GRASS = BlockStatePredicate.forBlock(Blocks.SHORT_GRASS);
+@@ -59,19 +65,21 @@
+ public void tick() {
+ this.eatAnimationTick = Math.max(0, this.eatAnimationTick - 1);
+ if (this.eatAnimationTick == this.adjustedTickDelay(4)) {
+- BlockPos blockPos = this.mob.blockPosition();
+- if (IS_TALL_GRASS.test(this.level.getBlockState(blockPos))) {
+- if (this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+- this.level.destroyBlock(blockPos, false);
++ BlockPos blockposition = this.mob.blockPosition();
++
++ if (EatBlockGoal.IS_TALL_GRASS.test(this.level.getBlockState(blockposition))) {
++ if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition, Blocks.AIR.defaultBlockState(), !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit
++ 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);
++ BlockPos blockposition1 = blockposition.below();
++
++ if (this.level.getBlockState(blockposition1).is(Blocks.GRASS_BLOCK)) {
++ if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.AIR.defaultBlockState(), !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit
++ 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-vineflower-stripped/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/goal/FollowOwnerGoal.java.patch
new file mode 100644
index 0000000000..14d66ab275
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -12,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 {
+ public static final int TELEPORT_WHEN_DISTANCE_IS = 12;
+@@ -118,7 +127,14 @@
+ } else if (!this.canTeleportTo(new BlockPos(x, y, z))) {
+ return false;
+ } else {
+- this.tamable.moveTo((double)x + 0.5, (double)y, (double)z + 0.5, 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-vineflower-stripped/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java.patch
new file mode 100644
index 0000000000..ccacbd0a95
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java.patch
@@ -0,0 +1,51 @@
+--- 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 {
+ private final Block blockToRemove;
+@@ -104,24 +100,19 @@
+ }
+
+ if (this.ticksSinceReachedGoal > 60) {
+- level.removeBlock(posWithBlock, false);
+- if (!level.isClientSide) {
+- for (int i = 0; i < 20; i++) {
+- double d = random.nextGaussian() * 0.02;
+- double d1 = random.nextGaussian() * 0.02;
+- double d2 = random.nextGaussian() * 0.02;
+- ((ServerLevel)level)
+- .sendParticles(
+- ParticleTypes.POOF,
+- (double)posWithBlock.getX() + 0.5,
+- (double)posWithBlock.getY(),
+- (double)posWithBlock.getZ() + 0.5,
+- 1,
+- d,
+- d1,
+- d2,
+- 0.15F
+- );
++ // 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;
++ double d2 = randomsource.nextGaussian() * 0.02D;
++
++ ((ServerLevel) world).sendParticles(ParticleTypes.POOF, (double) blockposition1.getX() + 0.5D, (double) blockposition1.getY(), (double) blockposition1.getZ() + 0.5D, 1, d0, d1, d2, 0.15000000596046448D);
+ }
+
+ this.playBreakSound(level, posWithBlock);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java.patch
new file mode 100644
index 0000000000..b3a0825d6b
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java.patch
@@ -0,0 +1,32 @@
+--- 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 {
+ private final AbstractHorse horse;
+@@ -55,11 +62,13 @@
+ return;
+ }
+
+- if (firstPassenger instanceof Player player) {
+- int temper = this.horse.getTemper();
+- int maxTemper = this.horse.getMaxTemper();
+- if (maxTemper > 0 && this.horse.getRandom().nextInt(maxTemper) < temper) {
+- this.horse.tameWithName(player);
++ if (entity instanceof Player) {
++ Player entityhuman = (Player) entity;
++ int i = this.horse.getTemper();
++ int j = this.horse.getMaxTemper();
++
++ 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-vineflower-stripped/net/minecraft/world/entity/ai/goal/SitWhenOrderedToGoal.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/goal/SitWhenOrderedToGoal.java.patch
new file mode 100644
index 0000000000..56c93d0edc
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -20,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-vineflower-stripped/net/minecraft/world/entity/ai/goal/TemptGoal.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/goal/TemptGoal.java.patch
new file mode 100644
index 0000000000..2630595259
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -7,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 {
+ private static final TargetingConditions TEMP_TARGETING = TargetingConditions.forNonCombat().range(10.0).ignoreLineOfSight();
+@@ -19,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;
+@@ -41,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-vineflower-stripped/net/minecraft/world/entity/ai/sensing/TemptingSensor.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/sensing/TemptingSensor.java.patch
new file mode 100644
index 0000000000..5dfbcde9c3
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/sensing/TemptingSensor.java.patch
@@ -0,0 +1,38 @@
+--- a/net/minecraft/world/entity/ai/sensing/TemptingSensor.java
++++ b/net/minecraft/world/entity/ai/sensing/TemptingSensor.java
+@@ -15,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> {
+ public static final int TEMPTATION_RANGE = 10;
+@@ -38,8 +49,19 @@
+ .sorted(Comparator.comparingDouble(entity::distanceToSqr))
+ .collect(Collectors.toList());
+ if (!list.isEmpty()) {
+- Player player = list.get(0);
+- brain.setMemory(MemoryModuleType.TEMPTING_PLAYER, player);
++ Player entityhuman = (Player) list.get(0);
++
++ // 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-vineflower-stripped/net/minecraft/world/entity/ai/village/VillageSiege.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ai/village/VillageSiege.java.patch
new file mode 100644
index 0000000000..0ec3865e87
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -104,8 +121,8 @@
+ return;
+ }
+
+- zombie.moveTo(vec3.x, vec3.y, vec3.z, level.random.nextFloat() * 360.0F, 0.0F);
+- level.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-vineflower-stripped/net/minecraft/world/entity/ambient/Bat.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/ambient/Bat.java.patch
new file mode 100644
index 0000000000..450a695e17
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -27,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 {
+ public static final float FLAP_LENGTH_SECONDS = 0.5F;
+@@ -138,13 +146,13 @@
+ this.yHeadRot = (float)this.random.nextInt(360);
+ }
+
+- if (this.level().getNearestPlayer(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 (!isSilent) {
+ this.level().levelEvent(null, 1025, blockPos, 0);
+ }
+ }
+- } else {
++ } else if (CraftEventFactory.handleBatToggleSleepEvent(this, true)) { // CraftBukkit - Call BatToggleSleepEvent
+ this.setResting(false);
+ if (!isSilent) {
+ this.level().levelEvent(null, 1025, blockPos, 0);
+@@ -178,7 +179,7 @@
+ float f1 = Mth.wrapDegrees(f - this.getYRot());
+ 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);
+ }
+ }
+@@ -203,7 +204,7 @@
+ if (this.isInvulnerableTo(source)) {
+ 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-vineflower-stripped/net/minecraft/world/entity/animal/Animal.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Animal.java.patch
new file mode 100644
index 0000000000..f5e68c0a52
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Animal.java.patch
@@ -0,0 +1,121 @@
+--- a/net/minecraft/world/entity/animal/Animal.java
++++ b/net/minecraft/world/entity/animal/Animal.java
+@@ -29,12 +29,18 @@
+ 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);
+@@ -74,8 +83,13 @@
+ if (this.isInvulnerableTo(source)) {
+ return false;
+ } else {
++ // CraftBukkit start
++ boolean result = super.hurt(source, amount);
++ if (result) {
+ this.inLove = 0;
+- return super.hurt(source, amount);
++ }
++ return result;
++ // CraftBukkit end
+ }
+ }
+
+@@ -166,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);
+ }
+@@ -205,27 +230,51 @@
+ }
+
+ public void spawnChildFromBreeding(ServerLevel level, Animal mate) {
+- AgeableMob breedOffspring = this.getBreedOffspring(level, mate);
+- if (breedOffspring != null) {
+- breedOffspring.setBaby(true);
+- breedOffspring.moveTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F);
+- this.finalizeSpawnChildFromBreeding(level, mate, breedOffspring);
+- level.addFreshEntityWithPassengers(breedOffspring);
++ AgeableMob entityageable = this.getBreedOffspring(level, mate);
++
++ 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 level, Animal animal, @Nullable AgeableMob baby) {
+- Optional.ofNullable(this.getLoveCause()).or(() -> Optional.ofNullable(animal.getLoveCause())).ifPresent(player -> {
+- player.awardStat(Stats.ANIMALS_BRED);
+- CriteriaTriggers.BRED_ANIMALS.trigger(player, this, animal, 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(entityanimal.getLoveCause());
++ }).ifPresent((entityplayer) -> {
++ entityplayer.awardStat(Stats.ANIMALS_BRED);
++ CriteriaTriggers.BRED_ANIMALS.trigger(entityplayer, this, entityanimal, entityageable);
+ });
+ this.setAge(6000);
+ animal.setAge(6000);
+ this.resetLove();
+- animal.resetLove();
+- level.broadcastEntityEvent(this, (byte)18);
+- if (level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
+- level.addFreshEntity(new ExperienceOrb(level, 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-vineflower-stripped/net/minecraft/world/entity/animal/Bee.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Bee.java.patch
new file mode 100644
index 0000000000..26d2dc069c
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Bee.java.patch
@@ -0,0 +1,128 @@
+--- a/net/minecraft/world/entity/animal/Bee.java
++++ b/net/minecraft/world/entity/animal/Bee.java
+@@ -88,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 {
+ public static final float FLAP_DEGREES_PER_TICK = 120.32113F;
+@@ -184,13 +192,20 @@
+
+ @Override
+ public void addAdditionalSaveData(CompoundTag compound) {
+- super.addAdditionalSaveData(compound);
+- if (this.hasHive()) {
+- compound.put("HivePos", NbtUtils.writeBlockPos(this.getHivePos()));
++ // CraftBukkit start - selectively save data
++ addAdditionalSaveData(compound, true);
++ }
++
++ @Override
++ 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()) {
+- compound.put("FlowerPos", NbtUtils.writeBlockPos(this.getSavedFlowerPos()));
++ if (includeAll && this.hasSavedFlowerPos()) { // CraftBukkit - selectively save flower
++ nbttagcompound.put("FlowerPos", NbtUtils.writeBlockPos(this.getSavedFlowerPos()));
+ }
+
+ compound.putBoolean("HasNectar", this.hasNectar());
+@@ -236,8 +253,8 @@
+ i = 18;
+ }
+
+- if (i > 0) {
+- ((LivingEntity)entity).addEffect(new MobEffectInstance(MobEffects.POISON, i * 20, 0), this);
++ if (b0 > 0) {
++ ((LivingEntity) entity).addEffect(new MobEffectInstance(MobEffects.POISON, b0 * 20, 0), this, EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+ }
+ }
+
+@@ -632,11 +652,14 @@
+ if (this.isInvulnerableTo(source)) {
+ 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(source, amount);
++ return result; // CraftBukkit
+ }
+ }
+
+@@ -760,7 +1013,10 @@
+ private int ticksStuck;
+
+ BeeGoToHiveGoal() {
+- this.setFlags(EnumSet.of(Goal.Flag.MOVE));
++ super();
++ 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.Type.MOVE));
+ }
+
+ @Override
+@@ -873,7 +1130,9 @@
+ int travellingTicks = Bee.this.level().random.nextInt(10);
+
+ BeeGoToKnownFlowerGoal() {
+- this.setFlags(EnumSet.of(Goal.Flag.MOVE));
++ super();
++ 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
+@@ -965,9 +1231,9 @@
+ ((BonemealableBlock)blockState.getBlock()).performBonemeal((ServerLevel)Bee.this.level(), Bee.this.random, blockPos, blockState);
+ }
+
+- 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();
+ }
+ }
+@@ -1105,20 +1302,9 @@
+ }
+
+ @Override
+- public boolean canBeeContinueToUse() {
+- if (!this.pollinating) {
+- return false;
+- } else if (!Bee.this.hasSavedFlowerPos()) {
+- return false;
+- } else if (Bee.this.level().isRaining()) {
+- return false;
+- } else if (this.hasPollinatedLongEnough()) {
+- return Bee.this.random.nextFloat() < 0.2F;
+- } else if (Bee.this.tickCount % 20 == 0 && !Bee.this.isFlowerValid(Bee.this.savedFlowerPos)) {
+- Bee.this.savedFlowerPos = null;
+- return false;
+- } else {
+- return true;
++ 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-vineflower-stripped/net/minecraft/world/entity/animal/Bucketable.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Bucketable.java.patch
new file mode 100644
index 0000000000..c8e76a8437
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Bucketable.java.patch
@@ -0,0 +1,52 @@
+--- 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 {
+ boolean fromBucket();
+@@ -85,18 +95,27 @@
+ }
+ }
+
+- static <T extends LivingEntity & Bucketable> Optional<InteractionResult> bucketMobPickup(Player player, InteractionHand hand, T entity) {
+- ItemStack itemInHand = player.getItemInHand(hand);
+- if (itemInHand.getItem() == Items.WATER_BUCKET && entity.isAlive()) {
+- entity.playSound(entity.getPickupSound(), 1.0F, 1.0F);
+- ItemStack bucketItemStack = entity.getBucketItemStack();
+- entity.saveToBucketTag(bucketItemStack);
+- ItemStack itemStack = ItemUtils.createFilledResult(itemInHand, player, bucketItemStack, false);
+- player.setItemInHand(hand, itemStack);
+- Level level = entity.level();
+- if (!level.isClientSide) {
+- CriteriaTriggers.FILLED_BUCKET.trigger((ServerPlayer)player, bucketItemStack);
++ static <T extends LivingEntity & Bucketable> Optional<InteractionResult> bucketMobPickup(Player player, EnumHand hand, T entity) {
++ ItemStack itemstack = player.getItemInHand(hand);
++
++ 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) 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);
+
+ entity.discard();
+ return Optional.of(InteractionResult.sidedSuccess(level.isClientSide));
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Cat.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Cat.java.patch
new file mode 100644
index 0000000000..65713dfd29
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Cat.java.patch
@@ -0,0 +1,66 @@
+--- a/net/minecraft/world/entity/animal/Cat.java
++++ b/net/minecraft/world/entity/animal/Cat.java
+@@ -402,9 +411,9 @@
+ return InteractionResult.CONSUME;
+ }
+ }
+- } else if (this.isFood(itemInHand)) {
+- this.usePlayerItem(player, hand, itemInHand);
+- if (this.random.nextInt(3) == 0) {
++ } else if (this.isFood(itemstack)) {
++ 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);
+@@ -462,7 +472,10 @@
+ return new Vector3f(0.0F, entityDimensions.height - 0.1875F * f, 0.0F);
+ }
+
+- static class CatAvoidEntityGoal<T extends LivingEntity> extends AvoidEntityGoal<T> {
++ private static class CatTemptGoal extends TemptGoal {
++
++ @Nullable
++ private LivingEntity selectedPlayer; // CraftBukkit
+ private final Cat cat;
+
+ public CatAvoidEntityGoal(Cat cat, Class<T> entityClassToAvoid, float maxDist, double walkSpeedModifier, double sprintSpeedModifier) {
+@@ -587,18 +605,26 @@
+ .withParameter(LootContextParams.THIS_ENTITY, this.cat)
+ .create(LootContextParamSets.GIFT);
+
+- for (ItemStack itemStack : lootTable.getRandomItems(lootParams)) {
+- this.cat
+- .level()
+- .addFreshEntity(
+- new ItemEntity(
+- this.cat.level(),
+- (double)mutableBlockPos.getX() - (double)Mth.sin(this.cat.yBodyRot * (float) (Math.PI / 180.0)),
+- (double)mutableBlockPos.getY(),
+- (double)mutableBlockPos.getZ() + (double)Mth.cos(this.cat.yBodyRot * (float) (Math.PI / 180.0)),
+- itemStack
+- )
+- );
++ blockposition_mutableblockposition.set(this.cat.isLeashed() ? this.cat.getLeashHolder().blockPosition() : this.cat.blockPosition());
++ this.cat.randomTeleport((double) (blockposition_mutableblockposition.getX() + randomsource.nextInt(11) - 5), (double) (blockposition_mutableblockposition.getY() + randomsource.nextInt(5) - 2), (double) (blockposition_mutableblockposition.getZ() + randomsource.nextInt(11) - 5), false);
++ blockposition_mutableblockposition.set(this.cat.blockPosition());
++ LootTable loottable = this.cat.level().getServer().getLootData().getLootTable(BuiltInLootTables.CAT_MORNING_GIFT);
++ LootParams lootparams = (new LootParams.Builder((ServerLevel) this.cat.level())).withParameter(LootContextParams.ORIGIN, this.cat.position()).withParameter(LootContextParams.THIS_ENTITY, this.cat).create(LootContextParamSets.GIFT);
++ List<ItemStack> list = loottable.getRandomItems(lootparams);
++ Iterator iterator = list.iterator();
++
++ while (iterator.hasNext()) {
++ ItemStack itemstack = (ItemStack) iterator.next();
++
++ // 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-vineflower-stripped/net/minecraft/world/entity/animal/Chicken.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Chicken.java.patch
new file mode 100644
index 0000000000..c67f62d332
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -95,7 +97,9 @@
+ this.flap = 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(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-vineflower-stripped/net/minecraft/world/entity/animal/Cow.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Cow.java.patch
new file mode 100644
index 0000000000..1dbc9445d4
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Cow.java.patch
@@ -0,0 +1,43 @@
+--- 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) {
+ super(entityType, level);
+@@ -80,12 +87,22 @@
+ }
+
+ @Override
+- public InteractionResult mobInteract(Player player, InteractionHand hand) {
+- ItemStack itemInHand = player.getItemInHand(hand);
+- if (itemInHand.is(Items.BUCKET) && !this.isBaby()) {
++ public InteractionResult mobInteract(Player player, EnumHand hand) {
++ ItemStack itemstack = player.getItemInHand(hand);
++
++ 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 itemStack = ItemUtils.createFilledResult(itemInHand, player, Items.MILK_BUCKET.getDefaultInstance());
+- player.setItemInHand(hand, itemStack);
++ ItemStack itemstack1 = ItemUtils.createFilledResult(itemstack, player, CraftItemStack.asNMSCopy(event.getItemStack())); // CraftBukkit
++
++ player.setItemInHand(hand, itemstack1);
+ return InteractionResult.sidedSuccess(this.level().isClientSide);
+ } else {
+ return super.mobInteract(player, hand);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Dolphin.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Dolphin.java.patch
new file mode 100644
index 0000000000..12722c7dcd
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Dolphin.java.patch
@@ -0,0 +1,67 @@
+--- a/net/minecraft/world/entity/animal/Dolphin.java
++++ b/net/minecraft/world/entity/animal/Dolphin.java
+@@ -60,8 +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);
+@@ -183,7 +195,7 @@
+
+ @Override
+ public int getMaxAirSupply() {
+- return 4800;
++ return maxAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
+ }
+
+ @Override
+@@ -220,8 +233,15 @@
+ @Override
+ protected void pickUpItem(ItemEntity itemEntity) {
+ if (this.getItemBySlot(EquipmentSlot.MAINHAND).isEmpty()) {
+- ItemStack item = itemEntity.getItem();
+- if (this.canHoldItem(item)) {
++ ItemStack itemstack = itemEntity.getItem();
++
++ if (this.canHoldItem(itemstack)) {
++ // 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, item);
+ this.setGuaranteedDrop(EquipmentSlot.MAINHAND);
+@@ -490,7 +500,7 @@
+
+ @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
+@@ -509,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-vineflower-stripped/net/minecraft/world/entity/animal/Fox.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Fox.java.patch
new file mode 100644
index 0000000000..d9ce3fc633
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Fox.java.patch
@@ -0,0 +1,106 @@
+--- a/net/minecraft/world/entity/animal/Fox.java
++++ b/net/minecraft/world/entity/animal/Fox.java
+@@ -508,11 +521,14 @@
+
+ @Override
+ protected void pickUpItem(ItemEntity itemEntity) {
+- ItemStack item = itemEntity.getItem();
+- if (this.canHoldItem(item)) {
+- int count = item.getCount();
+- if (count > 1) {
+- this.dropItemStack(item.split(count - 1));
++ ItemStack itemstack = itemEntity.getItem();
++
++ 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) {
++ this.dropItemStack(itemstack.split(i - 1));
+ }
+
+ this.spitOutItem(this.getItemBySlot(EquipmentSlot.MAINHAND));
+@@ -874,6 +884,16 @@
+ if (loveCause1 != null && loveCause != loveCause1) {
+ fox.addTrustedUUID(loveCause1.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 (serverPlayer != null) {
+ serverPlayer.awardStat(Stats.ANIMALS_BRED);
+@@ -884,15 +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);
+- this.level.broadcastEntityEvent(this.animal, (byte)18);
++ 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
+ }
+ }
+ }
+@@ -1203,16 +1299,34 @@
+ }
+ }
+
+- if (target != null && Fox.this.distanceTo(target) <= 2.0F) {
+- Fox.this.doHurtTarget(target);
+- } else if (Fox.this.getXRot() > 0.0F
+- && Fox.this.onGround()
+- && (float)Fox.this.getDeltaMovement().y != 0.0F
+- && Fox.this.level().getBlockState(Fox.this.blockPosition()).is(Blocks.SNOW)) {
+- Fox.this.setXRot(60.0F);
+- Fox.this.setTarget(null);
+- Fox.this.setFaceplanted(true);
++ private void pickGlowBerry(IBlockData state) {
++ CaveVines.use(Fox.this, state, Fox.this.level(), this.blockPos);
++ }
++
++ private void pickSweetBerries(IBlockData state) {
++ int i = (Integer) state.getValue(SweetBerryBushBlock.AGE);
++
++ 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);
++
++ if (itemstack.isEmpty()) {
++ Fox.this.setItemSlot(EquipmentSlot.MAINHAND, new ItemStack(Items.SWEET_BERRIES));
++ --j;
++ }
++
++ if (j > 0) {
++ Block.popResource(Fox.this.level(), this.blockPos, new ItemStack(Items.SWEET_BERRIES, j));
++ }
++
++ Fox.this.playSound(SoundEvents.SWEET_BERRY_BUSH_PICK_BERRIES, 1.0F, 1.0F);
++ Fox.this.level().setBlock(this.blockPos, (IBlockData) state.setValue(SweetBerryBushBlock.AGE, 1), 2);
++ Fox.this.level().gameEvent(GameEvent.BLOCK_CHANGE, this.blockPos, GameEvent.Context.of((Entity) Fox.this));
+ }
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/IronGolem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/IronGolem.java.patch
new file mode 100644
index 0000000000..519c37e6c0
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -102,8 +101,8 @@
+
+ @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-vineflower-stripped/net/minecraft/world/entity/animal/MushroomCow.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/MushroomCow.java.patch
new file mode 100644
index 0000000000..2cb2fbe4b2
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/MushroomCow.java.patch
@@ -0,0 +1,86 @@
+--- 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);
+ private static final int MUTATE_CHANCE = 1024;
+@@ -108,7 +120,12 @@
+
+ this.playSound(soundEvent, 1.0F, 1.0F);
+ return InteractionResult.sidedSuccess(this.level().isClientSide);
+- } else if (itemInHand.is(Items.SHEARS) && this.readyForShearing()) {
++ } 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) {
+@@ -167,13 +169,14 @@
+ public void shear(SoundSource category) {
+ this.level().playSound(null, this, SoundEvents.MOOSHROOM_SHEAR, category, 1.0F, 1.0F);
+ if (!this.level().isClientSide()) {
+- Cow cow = EntityType.COW.create(this.level());
+- if (cow != null) {
+- ((ServerLevel)this.level()).sendParticles(ParticleTypes.EXPLOSION, this.getX(), this.getY(0.5), this.getZ(), 1, 0.0, 0.0, 0.0, 0.0);
+- this.discard();
+- cow.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot());
+- cow.setHealth(this.getHealth());
+- cow.yBodyRot = this.yBodyRot;
++ Cow entitycow = (Cow) EntityType.COW.create(this.level());
++
++ if (entitycow != 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(); // 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());
+@@ -183,14 +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);
+
+- for (int i = 0; i < 5; i++) {
+- this.level()
+- .addFreshEntity(
+- new ItemEntity(this.level(), this.getX(), this.getY(1.0), this.getZ(), new ItemStack(this.getVariant().blockState.getBlock()))
+- );
++ this.discard(); // CraftBukkit - from above
++ // CraftBukkit end
++
++ for (int i = 0; i < 5; ++i) {
++ // 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-vineflower-stripped/net/minecraft/world/entity/animal/Ocelot.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Ocelot.java.patch
new file mode 100644
index 0000000000..5e8c505644
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -173,7 +179,7 @@
+ if ((this.temptGoal == null || this.temptGoal.isRunning()) && !this.isTrusting() && this.isFood(itemInHand) && player.distanceToSqr(this) < 9.0) {
+ this.usePlayerItem(player, hand, itemInHand);
+ 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-vineflower-stripped/net/minecraft/world/entity/animal/Panda.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Panda.java.patch
new file mode 100644
index 0000000000..c22cdec6b8
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Panda.java.patch
@@ -0,0 +1,43 @@
+--- a/net/minecraft/world/entity/animal/Panda.java
++++ b/net/minecraft/world/entity/animal/Panda.java
+@@ -64,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);
+ private static final EntityDataAccessor<Integer> SNEEZE_COUNTER = SynchedEntityData.defineId(Panda.class, EntityDataSerializers.INT);
+@@ -524,7 +539,7 @@
+
+ @Override
+ protected void pickUpItem(ItemEntity itemEntity) {
+- if (this.getItemBySlot(EquipmentSlot.MAINHAND).isEmpty() && PANDA_ITEMS.test(itemEntity)) {
++ if (!CraftEventFactory.callEntityPickupItemEvent(this, itemEntity, 0, !(this.getItemBySlot(EquipmentSlot.MAINHAND).isEmpty() && Panda.PANDA_ITEMS.test(itemEntity))).isCancelled()) { // CraftBukkit
+ this.onItemPickup(itemEntity);
+ ItemStack item = itemEntity.getItem();
+ this.setItemSlot(EquipmentSlot.MAINHAND, item);
+@@ -1066,16 +1115,9 @@
+ }
+
+ @Override
+- public boolean canContinueToUse() {
+- return !Panda.this.isInWater()
+- && (Panda.this.isLazy() || Panda.this.random.nextInt(reducedTickDelay(600)) != 1)
+- && Panda.this.random.nextInt(reducedTickDelay(2000)) != 1;
+- }
+-
+- @Override
+- public void tick() {
+- if (!Panda.this.isSitting() && !Panda.this.getItemBySlot(EquipmentSlot.MAINHAND).isEmpty()) {
+- Panda.this.tryToSit();
++ protected void alertOther(Mob mob, LivingEntity target) {
++ if (mob instanceof Panda && mob.isAggressive()) {
++ mob.setTarget(target, EntityTargetEvent.TargetReason.TARGET_ATTACKED_ENTITY, true); // CraftBukkit
+ }
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Parrot.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Parrot.java.patch
new file mode 100644
index 0000000000..e233a11dc5
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -271,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 {
+@@ -285,7 +277,7 @@
+ itemInHand.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);
+ }
+@@ -392,7 +384,7 @@
+
+ @Override
+ public boolean isPushable() {
+- return true;
++ return super.isPushable(); // CraftBukkit - collidable API
+ }
+
+ @Override
+@@ -407,11 +399,14 @@
+ if (this.isInvulnerableTo(source)) {
+ 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(source, amount);
++ return result; // CraftBukkit
+ }
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Pig.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Pig.java.patch
new file mode 100644
index 0000000000..59fd343ecf
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Pig.java.patch
@@ -0,0 +1,33 @@
+--- a/net/minecraft/world/entity/animal/Pig.java
++++ b/net/minecraft/world/entity/animal/Pig.java
+@@ -49,7 +51,12 @@
+ 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
++
++public class Pig extends Animal implements ISteerable, Saddleable {
++
+ 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);
+ private static final Ingredient FOOD_ITEMS = Ingredient.of(Items.CARROT, Items.POTATO, Items.BEETROOT);
+@@ -222,8 +255,14 @@
+ zombifiedPiglin.setCustomNameVisible(this.isCustomNameVisible());
+ }
+
+- zombifiedPiglin.setPersistenceRequired();
+- level.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(level, lightning);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Pufferfish.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Pufferfish.java.patch
new file mode 100644
index 0000000000..2a6bd1c25f
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Pufferfish.java.patch
@@ -0,0 +1,25 @@
+--- a/net/minecraft/world/entity/animal/Pufferfish.java
++++ b/net/minecraft/world/entity/animal/Pufferfish.java
+@@ -133,9 +141,10 @@
+ }
+
+ private void touch(Mob mob) {
+- int puffState = this.getPuffState();
+- if (mob.hurt(this.damageSources().mobAttack(this), (float)(1 + puffState))) {
+- mob.addEffect(new MobEffectInstance(MobEffects.POISON, 60 * puffState, 0), this);
++ int i = this.getPuffState();
++
++ if (mob.hurt(this.damageSources().mobAttack(this), (float) (1 + i))) {
++ 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);
+ }
+ }
+@@ -148,7 +159,7 @@
+ ((ServerPlayer)entity).connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.PUFFER_FISH_STING, 0.0F));
+ }
+
+- entity.addEffect(new MobEffectInstance(MobEffects.POISON, 60 * puffState, 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-vineflower-stripped/net/minecraft/world/entity/animal/Rabbit.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Rabbit.java.patch
new file mode 100644
index 0000000000..9808520a10
--- /dev/null
+++ b/patch-remap/mache-vineflower-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> {
+ public static final double STROLL_SPEED_MOD = 0.6;
+@@ -571,12 +579,22 @@
+ if (this.canRaid && block instanceof CarrotBlock) {
+ int i = 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.setValue(CarrotBlock.AGE, Integer.valueOf(i - 1)), 2);
+- level.gameEvent(GameEvent.BLOCK_CHANGE, blockPos, GameEvent.Context.of(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-vineflower-stripped/net/minecraft/world/entity/animal/Sheep.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Sheep.java.patch
new file mode 100644
index 0000000000..d2e75670e0
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Sheep.java.patch
@@ -0,0 +1,89 @@
+--- a/net/minecraft/world/entity/animal/Sheep.java
++++ b/net/minecraft/world/entity/animal/Sheep.java
+@@ -64,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 {
+ private static final int EAT_ANIMATION_TICKS = 40;
+@@ -210,6 +252,11 @@
+ ItemStack itemInHand = player.getItemInHand(hand);
+ if (itemInHand.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);
+ itemInHand.hurtAndBreak(1, player, entity -> entity.broadcastBreakEvent(hand));
+@@ -228,17 +277,13 @@
+ this.setSheared(true);
+ int i = 1 + this.random.nextInt(3);
+
+- for (int i1 = 0; i1 < i; i1++) {
+- ItemEntity itemEntity = this.spawnAtLocation(ITEM_BY_DYE.get(this.getColor()), 1);
+- 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)
+- )
+- );
++ for (int j = 0; j < i; ++j) {
++ this.forceDrops = true; // CraftBukkit
++ ItemEntity entityitem = this.spawnAtLocation((IMaterial) Sheep.ITEM_BY_DYE.get(this.getColor()), 1);
++ this.forceDrops = false; // CraftBukkit
++
++ if (entityitem != null) {
++ entityitem.setDeltaMovement(entityitem.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)));
+ }
+ }
+ }
+@@ -332,6 +373,12 @@
+
+ @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()) {
+@@ -374,10 +421,19 @@
+ public boolean stillValid(Player player) {
+ return false;
+ }
++
++ // CraftBukkit start
++ @Override
++ public InventoryView getBukkitView() {
++ return null; // TODO: O.O
++ }
++ // CraftBukkit end
+ }, 2, 1);
+- craftingContainer.setItem(0, new ItemStack(DyeItem.byColor(fatherColor)));
+- craftingContainer.setItem(1, new ItemStack(DyeItem.byColor(motherColor)));
+- return craftingContainer;
++
++ 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;
+ }
+
+ @Override
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/SnowGolem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/SnowGolem.java.patch
new file mode 100644
index 0000000000..d6c54722b1
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/SnowGolem.java.patch
@@ -0,0 +1,78 @@
+--- a/net/minecraft/world/entity/animal/SnowGolem.java
++++ b/net/minecraft/world/entity/animal/SnowGolem.java
+@@ -39,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 {
+ private static final EntityDataAccessor<Byte> DATA_PUMPKIN_ID = SynchedEntityData.defineId(SnowGolem.class, EntityDataSerializers.BYTE);
+@@ -92,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)) {
+@@ -101,14 +109,19 @@
+
+ BlockState blockState = Blocks.SNOW.defaultBlockState();
+
+- for (int i = 0; i < 4; i++) {
+- int floor = Mth.floor(this.getX() + (double)((float)(i % 2 * 2 - 1) * 0.25F));
+- int floor1 = Mth.floor(this.getY());
+- int floor2 = Mth.floor(this.getZ() + (double)((float)(i / 2 % 2 * 2 - 1) * 0.25F));
+- BlockPos blockPos = new BlockPos(floor, floor1, floor2);
+- 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));
++ for (int i = 0; i < 4; ++i) {
++ int j = Mth.floor(this.getX() + (double) ((float) (i % 2 * 2 - 1) * 0.25F));
++ int k = Mth.floor(this.getY());
++ int l = Mth.floor(this.getZ() + (double) ((float) (i / 2 % 2 * 2 - 1) * 0.25F));
++ BlockPos blockposition = new BlockPos(j, k, l);
++
++ 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));
+ }
+ }
+ }
+@@ -133,9 +148,15 @@
+ }
+
+ @Override
+- protected InteractionResult mobInteract(Player player, InteractionHand hand) {
+- ItemStack itemInHand = player.getItemInHand(hand);
+- if (itemInHand.is(Items.SHEARS) && this.readyForShearing()) {
++ protected InteractionResult mobInteract(Player player, EnumHand hand) {
++ ItemStack itemstack = player.getItemInHand(hand);
++
++ 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) {
+@@ -153,7 +176,9 @@
+ this.level().playSound(null, this, SoundEvents.SNOW_GOLEM_SHEAR, category, 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-vineflower-stripped/net/minecraft/world/entity/animal/Turtle.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Turtle.java.patch
new file mode 100644
index 0000000000..485d31894e
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Turtle.java.patch
@@ -0,0 +1,46 @@
+--- a/net/minecraft/world/entity/animal/Turtle.java
++++ b/net/minecraft/world/entity/animal/Turtle.java
+@@ -305,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
+ }
+ }
+
+@@ -330,7 +336,9 @@
+
+ @Override
+ 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
+@@ -431,13 +495,17 @@
+ posTowards = DefaultRandomPos.getPosTowards(this.turtle, 8, 7, vec3, (float) (Math.PI / 2));
+ }
+
+- if (posTowards != null && !flag && !this.turtle.level().getBlockState(BlockPos.containing(posTowards)).is(Blocks.WATER)) {
+- posTowards = DefaultRandomPos.getPosTowards(this.turtle, 16, 5, vec3, (float) (Math.PI / 2));
+- }
++ 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);
+
+- if (posTowards == null) {
+- this.stuck = true;
+- return;
++ 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);
+ }
+
+ this.turtle.getNavigation().moveTo(posTowards.x, posTowards.y, posTowards.z, this.speedModifier);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Wolf.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Wolf.java.patch
new file mode 100644
index 0000000000..797e233cff
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/Wolf.java.patch
@@ -0,0 +1,75 @@
+--- 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);
+ private static final EntityDataAccessor<Integer> DATA_COLLAR_COLOR = SynchedEntityData.defineId(Wolf.class, EntityDataSerializers.INT);
+@@ -310,11 +304,19 @@
+ this.setOrderedToSit(false);
+ }
+
++ // CraftBukkit - move diff down
++
+ if (entity != null && !(entity instanceof Player) && !(entity instanceof AbstractArrow)) {
+ amount = (amount + 1.0F) / 2.0F;
+ }
+
+- return super.hurt(source, amount);
++ // CraftBukkit start
++ boolean result = super.hurt(source, amount);
++ if (!this.level().isClientSide && result) {
++ this.setOrderedToSit(false);
++ }
++ return result;
++ // CraftBukkit end
+ }
+ }
+
+@@ -332,8 +335,8 @@
+ public void setTame(boolean tamed) {
+ super.setTame(tamed);
+ if (tamed) {
+- this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(20.0);
+- this.setHealth(20.0F);
++ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(20.0D);
++ this.setHealth(this.getMaxHealth()); // CraftBukkit - 20.0 -> getMaxHealth()
+ } else {
+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(8.0);
+ }
+@@ -354,7 +359,7 @@
+ itemInHand.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 dyeItem && this.isOwnedBy(player)) {
+@@ -376,7 +387,7 @@
+ this.setOrderedToSit(!this.isOrderedToSit());
+ this.jumping = false;
+ this.navigation.stop();
+- this.setTarget(null);
++ this.setTarget((LivingEntity) null, EntityTargetEvent.TargetReason.FORGOT_TARGET, true); // CraftBukkit - reason
+ return InteractionResult.SUCCESS;
+ } else {
+ return interactionResult;
+@@ -387,7 +398,8 @@
+ itemInHand.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(null);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/allay/Allay.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/allay/Allay.java.patch
new file mode 100644
index 0000000000..7cb091317b
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/allay/Allay.java.patch
@@ -0,0 +1,94 @@
+--- a/net/minecraft/world/entity/animal/allay/Allay.java
++++ b/net/minecraft/world/entity/animal/allay/Allay.java
+@@ -112,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);
+@@ -125,6 +111,12 @@
+ );
+ }
+
++ // CraftBukkit start
++ public void setCanDuplicate(boolean canDuplicate) {
++ this.entityData.set(Allay.DATA_CAN_DUPLICATE, canDuplicate);
++ }
++ // CraftBukkit end
++
+ @Override
+ protected Brain.Provider<Allay> brainProvider() {
+ return Brain.provider(MEMORY_TYPES, SENSOR_TYPES);
+@@ -246,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) {
+@@ -310,14 +301,20 @@
+ }
+
+ @Override
+- protected InteractionResult mobInteract(Player player, InteractionHand hand) {
+- ItemStack itemInHand = player.getItemInHand(hand);
+- ItemStack itemInHand1 = this.getItemInHand(InteractionHand.MAIN_HAND);
+- if (this.isDancing() && this.isDuplicationItem(itemInHand) && this.canDuplicate()) {
+- this.duplicateAllay();
+- this.level().broadcastEntityEvent(this, (byte)18);
+- this.level().playSound(player, this, SoundEvents.AMETHYST_BLOCK_CHIME, SoundSource.NEUTRAL, 2.0F, 1.0F);
+- this.removeInteractionItem(player, itemInHand);
++ protected InteractionResult mobInteract(Player player, EnumHand hand) {
++ ItemStack itemstack = player.getItemInHand(hand);
++ ItemStack itemstack1 = this.getItemInHand(EnumHand.MAIN_HAND);
++
++ if (this.isDancing() && this.isDuplicationItem(itemstack) && this.canDuplicate()) {
++ // 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);
+ return InteractionResult.SUCCESS;
+ } else if (itemInHand1.isEmpty() && !itemInHand.isEmpty()) {
+ ItemStack itemStack = itemInHand.copyWithCount(1);
+@@ -431,9 +439,8 @@
+ }
+
+ private boolean shouldStopDancing() {
+- return this.jukeboxPos == null
+- || !this.jukeboxPos.closerToCenterThan(this.position(), (double)GameEvent.JUKEBOX_PLAY.getNotificationRadius())
+- || !this.level().getBlockState(this.jukeboxPos).is(Blocks.JUKEBOX);
++ 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);
+ }
+
+ public float getHoldingItemAnimationProgress(float partialTick) {
+@@ -516,15 +533,17 @@
+ return DUPLICATION_ITEM.test(stack);
+ }
+
+- private void duplicateAllay() {
+- Allay allay = EntityType.ALLAY.create(this.level());
++ public Allay duplicateAllay() { // CraftBukkit - return allay
++ Allay allay = (Allay) EntityType.ALLAY.create(this.level());
++
+ if (allay != null) {
+ allay.moveTo(this.position());
+ 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-vineflower-stripped/net/minecraft/world/entity/animal/axolotl/Axolotl.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/axolotl/Axolotl.java.patch
new file mode 100644
index 0000000000..243c02b137
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/axolotl/Axolotl.java.patch
@@ -0,0 +1,44 @@
+--- a/net/minecraft/world/entity/animal/axolotl/Axolotl.java
++++ b/net/minecraft/world/entity/animal/axolotl/Axolotl.java
+@@ -67,6 +67,13 @@
+ import org.joml.Vector3f;
+
+ 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
+@@ -206,7 +195,7 @@
+
+ @Override
+ public int getMaxAirSupply() {
+- return 6000;
++ return maxAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
+ }
+
+ @Override
+@@ -414,11 +410,13 @@
+ }
+
+ public void applySupportingEffects(Player player) {
+- MobEffectInstance effect = player.getEffect(MobEffects.REGENERATION);
+- if (effect == null || effect.endsWithin(2399)) {
+- int i = effect != null ? effect.getDuration() : 0;
+- int min = Math.min(2400, 100 + i);
+- player.addEffect(new MobEffectInstance(MobEffects.REGENERATION, min, 0), this);
++ MobEffectInstance mobeffect = player.getEffect(MobEffects.REGENERATION);
++
++ if (mobeffect == null || mobeffect.endsWithin(2399)) {
++ int i = mobeffect != null ? mobeffect.getDuration() : 0;
++ int j = Math.min(2400, 100 + i);
++
++ 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-vineflower-stripped/net/minecraft/world/entity/animal/frog/Tadpole.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/frog/Tadpole.java.patch
new file mode 100644
index 0000000000..122d63658a
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -236,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-vineflower-stripped/net/minecraft/world/entity/animal/goat/Goat.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/goat/Goat.java.patch
new file mode 100644
index 0000000000..1f49245a0c
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/goat/Goat.java.patch
@@ -0,0 +1,42 @@
+--- a/net/minecraft/world/entity/animal/goat/Goat.java
++++ b/net/minecraft/world/entity/animal/goat/Goat.java
+@@ -54,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);
+ private static final int ADULT_ATTACK_DAMAGE = 2;
+@@ -210,12 +222,21 @@
+ }
+
+ @Override
+- public InteractionResult mobInteract(Player player, InteractionHand hand) {
+- ItemStack itemInHand = player.getItemInHand(hand);
+- if (itemInHand.is(Items.BUCKET) && !this.isBaby()) {
++ public InteractionResult mobInteract(Player player, EnumHand hand) {
++ ItemStack itemstack = player.getItemInHand(hand);
++
++ 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 itemStack = ItemUtils.createFilledResult(itemInHand, player, Items.MILK_BUCKET.getDefaultInstance());
+- player.setItemInHand(hand, itemStack);
++ ItemStack itemstack1 = ItemUtils.createFilledResult(itemstack, player, CraftItemStack.asNMSCopy(event.getItemStack())); // CraftBukkit
++
++ player.setItemInHand(hand, itemstack1);
+ return InteractionResult.sidedSuccess(this.level().isClientSide);
+ } else {
+ InteractionResult interactionResult = super.mobInteract(player, hand);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch
new file mode 100644
index 0000000000..58b38c01f5
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/horse/AbstractHorse.java.patch
@@ -0,0 +1,108 @@
+--- a/net/minecraft/world/entity/animal/horse/AbstractHorse.java
++++ b/net/minecraft/world/entity/animal/horse/AbstractHorse.java
+@@ -77,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;
+ public static final int CHEST_SLOT_OFFSET = 499;
+@@ -129,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);
+@@ -326,10 +341,16 @@
+ simpleContainer.removeListener(this);
+ int min = Math.min(simpleContainer.getContainerSize(), this.inventory.getContainerSize());
+
+- for (int i = 0; i < min; i++) {
+- ItemStack item = simpleContainer.getItem(i);
+- if (!item.isEmpty()) {
+- this.inventory.setItem(i, item.copy());
++ 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 = inventorysubcontainer.getItem(j);
++
++ if (!itemstack.isEmpty()) {
++ this.inventory.setItem(j, itemstack.copy());
+ }
+ }
+ }
+@@ -427,7 +449,7 @@
+ }
+
+ public int getMaxTemper() {
+- return 100;
++ return this.maxDomestication; // CraftBukkit - return stored max domestication instead of 100
+ }
+
+ @Override
+@@ -499,7 +520,7 @@
+ }
+
+ if (this.getHealth() < this.getMaxHealth() && f > 0.0F) {
+- this.heal(f);
++ this.heal(f, EntityRegainHealthEvent.RegainReason.EATING); // CraftBukkit
+ flag = true;
+ }
+
+@@ -570,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()) {
+@@ -826,6 +861,7 @@
+ if (this.getOwnerUUID() != null) {
+ compound.putUUID("Owner", this.getOwnerUUID());
+ }
++ compound.putInt("Bukkit.MaxDomestication", this.maxDomestication); // CraftBukkit
+
+ if (!this.inventory.getItem(0).isEmpty()) {
+ compound.put("SaddleItem", this.inventory.getItem(0).save(new CompoundTag()));
+@@ -850,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 (compound.contains("SaddleItem", 10)) {
+ ItemStack itemStack = ItemStack.of(compound.getCompound("SaddleItem"));
+@@ -947,6 +996,17 @@
+
+ @Override
+ 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-vineflower-stripped/net/minecraft/world/entity/animal/horse/Llama.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/horse/Llama.java.patch
new file mode 100644
index 0000000000..c9ec510516
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/horse/Llama.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/world/entity/animal/horse/Llama.java
++++ b/net/minecraft/world/entity/animal/horse/Llama.java
+@@ -81,6 +84,11 @@
+ return false;
+ }
+
++ // CraftBukkit start
++ public void setStrengthPublic(int i) {
++ this.setStrength(i);
++ }
++ // CraftBukkit end
+ private void setStrength(int strength) {
+ this.entityData.set(DATA_STRENGTH_ID, Math.max(1, Math.min(5, strength)));
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java.patch
new file mode 100644
index 0000000000..0c833c627f
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java.patch
@@ -0,0 +1,37 @@
+--- a/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java
++++ b/net/minecraft/world/entity/animal/horse/SkeletonTrapGoal.java
+@@ -42,14 +38,26 @@
+ skeleton.startRiding(this.horse);
+ serverLevel.addFreshEntityWithPassengers(skeleton);
+
+- for (int i = 0; i < 3; i++) {
+- AbstractHorse abstractHorse = this.createHorse(currentDifficultyAt);
+- if (abstractHorse != null) {
+- Skeleton skeleton1 = this.createSkeleton(currentDifficultyAt, abstractHorse);
+- if (skeleton1 != null) {
+- skeleton1.startRiding(abstractHorse);
+- abstractHorse.push(this.horse.getRandom().triangle(0.0, 1.1485), 0.0, this.horse.getRandom().triangle(0.0, 1.1485));
+- serverLevel.addFreshEntityWithPassengers(abstractHorse);
++ 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 (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 entityhorseabstract = this.createHorse(difficultydamagescaler);
++
++ if (entityhorseabstract != null) {
++ Skeleton entityskeleton1 = this.createSkeleton(difficultydamagescaler, entityhorseabstract);
++
++ 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-vineflower-stripped/net/minecraft/world/entity/animal/horse/TraderLlama.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/horse/TraderLlama.java.patch
new file mode 100644
index 0000000000..bdb92a4495
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/horse/TraderLlama.java.patch
@@ -0,0 +1,18 @@
+--- a/net/minecraft/world/entity/animal/horse/TraderLlama.java
++++ b/net/minecraft/world/entity/animal/horse/TraderLlama.java
+@@ -142,10 +153,11 @@
+
+ @Override
+ public void start() {
+- this.mob.setTarget(this.ownerLastHurtBy);
+- Entity leashHolder = this.llama.getLeashHolder();
+- if (leashHolder instanceof WanderingTrader) {
+- this.timestamp = ((WanderingTrader)leashHolder).getLastHurtByMobTimestamp();
++ 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) {
++ this.timestamp = ((WanderingTrader) entity).getLastHurtByMobTimestamp();
+ }
+
+ super.start();
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/sniffer/Sniffer.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/sniffer/Sniffer.java.patch
new file mode 100644
index 0000000000..597a35b403
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/animal/sniffer/Sniffer.java.patch
@@ -0,0 +1,55 @@
+--- a/net/minecraft/world/entity/animal/sniffer/Sniffer.java
++++ b/net/minecraft/world/entity/animal/sniffer/Sniffer.java
+@@ -81,16 +83,25 @@
+
+ public Sniffer(EntityType<? extends Animal> entityType, Level level) {
+ super(entityType, level);
+- this.entityData.define(DATA_STATE, Sniffer.State.IDLING);
+- this.entityData.define(DATA_DROP_SEED_AT_TICK, 0);
++ // 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 float getStandingEyeHeight(Pose pose, EntityDimensions dimensions) {
++ 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(EntityPose pose, EntityDimensions dimensions) {
+ return this.getDimensions(pose).height * 0.6F;
+ }
+
+@@ -271,10 +272,19 @@
+ List<ItemStack> randomItems = lootTable.getRandomItems(lootParams);
+ BlockPos headBlock = this.getHeadBlock();
+
+- for (ItemStack itemStack : randomItems) {
+- ItemEntity itemEntity = new ItemEntity(serverLevel, (double)headBlock.getX(), (double)headBlock.getY(), (double)headBlock.getZ(), itemStack);
+- itemEntity.setDefaultPickUpDelay();
+- serverLevel.addFreshEntity(itemEntity);
++ while (iterator.hasNext()) {
++ ItemStack itemstack = (ItemStack) iterator.next();
++ ItemEntity entityitem = new ItemEntity(worldserver, (double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ(), itemstack);
++
++ // 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-vineflower-stripped/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch
new file mode 100644
index 0000000000..9c1b232434
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java.patch
@@ -0,0 +1,57 @@
+--- a/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java
++++ b/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java
+@@ -18,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 {
+ private static final EntityDataAccessor<Optional<BlockPos>> DATA_BEAM_TARGET = SynchedEntityData.defineId(
+@@ -52,9 +56,14 @@
+ public void tick() {
+ this.time++;
+ 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));
++ BlockPos blockposition = this.blockPosition();
++
++ 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
+ }
+ }
+ }
+@@ -92,10 +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 (!source.is(DamageTypeTags.IS_EXPLOSION)) {
+- DamageSource damageSource = source.getEntity() != null ? this.damageSources().explosion(this, source.getEntity()) : null;
+- this.level().explode(this, damageSource, null, this.getX(), this.getY(), this.getZ(), 6.0F, false, Level.ExplosionInteraction.BLOCK);
++ DamageSource damagesource1 = source.getEntity() != null ? this.damageSources().explosion(this, source.getEntity()) : null;
++
++ // 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(source);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch
new file mode 100644
index 0000000000..15b1ccddaa
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java.patch
@@ -0,0 +1,258 @@
+--- a/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
++++ b/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
+@@ -52,7 +51,22 @@
+ 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
++
++public class EnderDragon extends Mob implements IMonster {
++
+ private static final Logger LOGGER = LogUtils.getLogger();
+ public static final EntityDataAccessor<Integer> DATA_PHASE = SynchedEntityData.defineId(EnderDragon.class, EntityDataSerializers.INT);
+ private static final TargetingConditions CRYSTAL_DESTROY_TARGETING = TargetingConditions.forCombat().range(64.0);
+@@ -85,9 +99,10 @@
+ private final EnderDragonPhaseManager phaseManager;
+ private int growlTime = 100;
+ private float sittingDamageReceived;
+- private final Node[] nodes = new Node[24];
+- private final int[] nodeAdjacency = new int[24];
+- private final BinaryHeap openSet = new BinaryHeap();
++ 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);
+@@ -104,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 dragonFight) {
+@@ -250,16 +270,18 @@
+ currentPhase.doServerTick();
+ }
+
+- Vec3 flyTargetLocation = currentPhase.getFlyTargetLocation();
+- if (flyTargetLocation != null) {
+- double d = flyTargetLocation.x - this.getX();
+- double d1 = flyTargetLocation.y - this.getY();
+- double d2 = flyTargetLocation.z - this.getZ();
+- double d3 = d * d + d1 * d1 + d2 * d2;
+- float flySpeed = currentPhase.getFlySpeed();
+- double squareRoot = Math.sqrt(d * d + d2 * d2);
+- if (squareRoot > 0.0) {
+- d1 = Mth.clamp(d1 / squareRoot, (double)(-flySpeed), (double)flySpeed);
++ Vec3 vec3d1 = idragoncontroller.getFlyTargetLocation();
++
++ 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 = idragoncontroller.getFlySpeed();
++ double d4 = Math.sqrt(d0 * d0 + d2 * d2);
++
++ if (d4 > 0.0D) {
++ d1 = Mth.clamp(d1 / d4, (double) (-f6), (double) f6);
+ }
+
+ this.setDeltaMovement(this.getDeltaMovement().add(0.0, d1 * 0.01, 0.0));
+@@ -398,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
+ }
+ }
+
+@@ -459,15 +495,23 @@
+ int floor5 = Mth.floor(area.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 i = floor; i <= floor3; i++) {
+- for (int i1 = floor1; i1 <= floor4; i1++) {
+- for (int i2 = floor2; i2 <= floor5; i2++) {
+- BlockPos blockPos = new BlockPos(i, i1, 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;
++ for (int k1 = i; k1 <= l; ++k1) {
++ for (int l1 = j; l1 <= i1; ++l1) {
++ for (int i2 = k; i2 <= j1; ++i2) {
++ BlockPos blockposition = new BlockPos(k1, l1, i2);
++ IBlockData iblockdata = this.level().getBlockState(blockposition);
++
++ 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;
+ }
+@@ -476,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(
+ floor + this.random.nextInt(floor3 - floor + 1),
+@@ -541,7 +629,22 @@
+ }
+ }
+
++ // 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) {
+ this.dragonFight.updateDragon(this);
+@@ -556,15 +659,21 @@
+ .addParticle(ParticleTypes.EXPLOSION_EMITTER, this.getX() + (double)f, this.getY() + 2.0 + (double)f1, this.getZ() + (double)f2, 0.0, 0.0, 0.0);
+ }
+
+- boolean _boolean = this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT);
+- int i = 500;
++ // 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()) {
+ i = 12000;
+ }
++ */
++ int short0 = expToDrop;
++ // CraftBukkit end
+
+ if (this.level() instanceof ServerLevel) {
+- if (this.dragonDeathTime > 150 && this.dragonDeathTime % 5 == 0 && _boolean) {
+- ExperienceOrb.award((ServerLevel)this.level(), this.position(), Mth.floor((float)i * 0.08F));
++ 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));
+ }
+
+ if (this.dragonDeathTime == 1 && !this.isSilent()) {
+@@ -574,8 +683,8 @@
+
+ this.move(MoverType.SELF, new Vec3(0.0, 0.1F, 0.0));
+ if (this.dragonDeathTime == 200 && this.level() instanceof ServerLevel) {
+- if (_boolean) {
+- ExperienceOrb.award((ServerLevel)this.level(), this.position(), Mth.floor((float)i * 0.2F));
++ 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));
+ }
+
+ if (this.dragonFight != null) {
+@@ -767,6 +904,7 @@
+ 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
+@@ -779,6 +917,12 @@
+ if (compound.contains("DragonDeathTime")) {
+ this.dragonDeathTime = compound.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-vineflower-stripped/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/boss/enderdragon/phases/EnderDragonPhaseManager.java.patch
new file mode 100644
index 0000000000..aed8d587b5
--- /dev/null
+++ b/patch-remap/mache-vineflower-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();
+ private final EnderDragon dragon;
+@@ -23,7 +29,20 @@
+ this.currentPhase.end();
+ }
+
+- this.currentPhase = this.getPhase((EnderDragonPhase<DragonPhaseInstance>)phase);
++ // 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, phase.getId());
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch
new file mode 100644
index 0000000000..d0328a9f3e
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/boss/wither/WitherBoss.java.patch
@@ -0,0 +1,134 @@
+--- a/net/minecraft/world/entity/boss/wither/WitherBoss.java
++++ b/net/minecraft/world/entity/boss/wither/WitherBoss.java
+@@ -53,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 {
+ private static final EntityDataAccessor<Integer> DATA_TARGET_A = SynchedEntityData.defineId(WitherBoss.class, EntityDataSerializers.INT);
+@@ -256,15 +257,40 @@
+ int 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 {
+ super.customServerAiStep();
+@@ -297,11 +330,13 @@
+ this.setAlternativeTarget(ix, 0);
+ }
+ } else {
+- List<LivingEntity> nearbyEntities = this.level()
+- .getNearbyEntities(LivingEntity.class, TARGETING_CONDITIONS, this, this.getBoundingBox().inflate(20.0, 8.0, 20.0));
+- if (!nearbyEntities.isEmpty()) {
+- LivingEntity livingEntity1 = nearbyEntities.get(this.random.nextInt(nearbyEntities.size()));
+- this.setAlternativeTarget(ix, livingEntity1.getId());
++ List<LivingEntity> list = this.level().getNearbyEntities(LivingEntity.class, WitherBoss.TARGETING_CONDITIONS, this, this.getBoundingBox().inflate(20.0D, 8.0D, 20.0D));
++
++ if (!list.isEmpty()) {
++ LivingEntity entityliving1 = (LivingEntity) list.get(this.random.nextInt(list.size()));
++
++ if (CraftEventFactory.callEntityTargetLivingEvent(this, entityliving1, EntityTargetEvent.TargetReason.CLOSEST_ENTITY).isCancelled()) continue; // CraftBukkit
++ this.setAlternativeTarget(i, entityliving1.getId());
+ }
+ }
+ }
+@@ -321,16 +356,22 @@
+ int floor = Mth.floor(this.getZ());
+ boolean flag = false;
+
+- for (int i1 = -1; i1 <= 1; i1++) {
+- for (int i2 = -1; i2 <= 1; i2++) {
+- for (int i3 = 0; i3 <= 3; i3++) {
+- int i4 = alternativeTarget + i1;
+- int i5 = ixx + i3;
+- int i6 = floor + i2;
+- BlockPos blockPos = new BlockPos(i4, i5, i6);
+- BlockState blockState = this.level().getBlockState(blockPos);
+- if (canDestroy(blockState)) {
+- flag = this.level().destroyBlock(blockPos, true, this) || flag;
++ for (int j1 = -1; j1 <= 1; ++j1) {
++ for (int k1 = -1; k1 <= 1; ++k1) {
++ for (int l1 = 0; l1 <= 3; ++l1) {
++ int i2 = j + j1;
++ int j2 = i + l1;
++ int k2 = i1 + k1;
++ BlockPos blockposition = new BlockPos(i2, j2, k2);
++ IBlockData iblockdata = this.level().getBlockState(blockposition);
++
++ if (canDestroy(iblockdata)) {
++ // CraftBukkit start
++ if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, Blocks.AIR.defaultBlockState())) {
++ continue;
++ }
++ // CraftBukkit end
++ flag = this.level().destroyBlock(blockposition, true, this) || flag;
+ }
+ }
+ }
+@@ -343,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-vineflower-stripped/net/minecraft/world/entity/decoration/ArmorStand.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/decoration/ArmorStand.java.patch
new file mode 100644
index 0000000000..24f5e342ed
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/decoration/ArmorStand.java.patch
@@ -0,0 +1,259 @@
+--- a/net/minecraft/world/entity/decoration/ArmorStand.java
++++ b/net/minecraft/world/entity/decoration/ArmorStand.java
+@@ -41,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 {
+ public static final int WOBBLE_TIME = 5;
+@@ -94,7 +111,14 @@
+ this.setPos(x, y, z);
+ }
+
++ // CraftBukkit start - SPIGOT-3607, SPIGOT-3637
+ @Override
++ public float getBukkitYaw() {
++ return this.getYRot();
++ }
++ // CraftBukkit end
++
++ @Override
+ public void refreshDimensions() {
+ double x = this.getX();
+ double y = this.getY();
+@@ -147,14 +172,21 @@
+ }
+
+ @Override
+- public void setItemSlot(EquipmentSlot slot, ItemStack stack) {
+- this.verifyEquippedItem(stack);
+- switch (slot.getType()) {
++ public void setItemSlot(net.minecraft.world.entity.EquipmentSlot slot, ItemStack stack) {
++ // CraftBukkit start
++ this.setItemSlot(slot, stack, false);
++ }
++
++ @Override
++ public void setItemSlot(net.minecraft.world.entity.EquipmentSlot enumitemslot, ItemStack itemstack, boolean silent) {
++ // CraftBukkit end
++ this.verifyEquippedItem(itemstack);
++ switch (enumitemslot.getType()) {
+ case HAND:
+- this.onEquipItem(slot, this.handItems.set(slot.getIndex(), stack), stack);
++ this.onEquipItem(enumitemslot, (ItemStack) this.handItems.set(enumitemslot.getIndex(), itemstack), itemstack, silent); // CraftBukkit
+ break;
+ case ARMOR:
+- this.onEquipItem(slot, this.armorItems.set(slot.getIndex(), stack), stack);
++ this.onEquipItem(enumitemslot, (ItemStack) this.armorItems.set(enumitemslot.getIndex(), itemstack), itemstack, silent); // CraftBukkit
+ }
+ }
+
+@@ -360,7 +421,25 @@
+ return false;
+ } else if (itemBySlot.isEmpty() && (this.disabledSlots & 1 << slot.getFilterFlag() + 16) != 0) {
+ return false;
+- } else if (player.getAbilities().instabuild && itemBySlot.isEmpty() && !stack.isEmpty()) {
++ // 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 (stack.isEmpty() || stack.getCount() <= 1) {
+@@ -373,45 +454,28 @@
+ this.setItemSlot(slot, stack.split(1));
+ return true;
+ }
++ } // CraftBukkit
+ }
+
+ @Override
+ public boolean hurt(DamageSource source, float amount) {
+- if (this.level().isClientSide || this.isRemoved()) {
+- return false;
+- } else if (source.is(DamageTypeTags.BYPASSES_INVULNERABILITY)) {
+- this.kill();
+- return false;
+- } else if (this.isInvulnerableTo(source) || this.invisible || this.isMarker()) {
+- return false;
+- } else if (source.is(DamageTypeTags.IS_EXPLOSION)) {
+- this.brokenByAnything(source);
+- this.kill();
+- return false;
+- } else if (source.is(DamageTypeTags.IGNITES_ARMOR_STANDS)) {
+- if (this.isOnFire()) {
+- this.causeDamage(source, 0.15F);
+- } else {
+- this.setSecondsOnFire(5);
+- }
+-
+- return false;
+- } else if (source.is(DamageTypeTags.BURNS_ARMOR_STANDS) && this.getHealth() > 0.5F) {
+- this.causeDamage(source, 4.0F);
+- return false;
+- } else {
+- boolean isCanBreakArmorStand = source.is(DamageTypeTags.CAN_BREAK_ARMOR_STAND);
+- boolean isAlwaysKillsArmorStands = source.is(DamageTypeTags.ALWAYS_KILLS_ARMOR_STANDS);
+- if (!isCanBreakArmorStand && !isAlwaysKillsArmorStands) {
++ if (!this.level().isClientSide && !this.isRemoved()) {
++ 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 (source.getEntity() instanceof Player player && !player.getAbilities().mayBuild) {
++ } 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;
+ }
+-
+- if (source.isCreativePlayer()) {
+- this.playBrokenSound();
+- this.showBreakingParticles();
++ // CraftBukkit end
++ if (source.is(DamageTypeTags.IS_EXPLOSION)) {
++ this.brokenByAnything(source);
+ this.kill();
+ return true;
+ } else {
+@@ -426,7 +485,48 @@
+ this.kill();
+ }
+
+- return true;
++ return false;
++ } else if (source.is(DamageTypeTags.BURNS_ARMOR_STANDS) && this.getHealth() > 0.5F) {
++ this.causeDamage(source, 4.0F);
++ return false;
++ } else {
++ boolean flag = source.is(DamageTypeTags.CAN_BREAK_ARMOR_STAND);
++ boolean flag1 = source.is(DamageTypeTags.ALWAYS_KILLS_ARMOR_STANDS);
++
++ if (!flag && !flag1) {
++ return false;
++ } else {
++ Entity entity = source.getEntity();
++
++ if (entity instanceof net.minecraft.world.entity.player.Player) {
++ net.minecraft.world.entity.player.Player entityhuman = (net.minecraft.world.entity.player.Player) entity;
++
++ if (!entityhuman.getAbilities().mayBuild) {
++ return false;
++ }
++ }
++
++ if (source.isCreativePlayer()) {
++ this.playBrokenSound();
++ this.showBreakingParticles();
++ this.kill();
++ return true;
++ } else {
++ long i = this.level().getGameTime();
++
++ if (i - this.lastHit > 5L && !flag1) {
++ this.level().broadcastEntityEvent(this, (byte) 32);
++ this.gameEvent(GameEvent.ENTITY_DAMAGE, source.getEntity());
++ this.lastHit = i;
++ } else {
++ this.brokenByPlayer(source);
++ this.showBreakingParticles();
++ this.discard(); // CraftBukkit - SPIGOT-4890: remain as this.discard() since above damagesource method will call death event
++ }
++
++ return true;
++ }
++ }
+ }
+ }
+ }
+@@ -490,29 +589,34 @@
+ itemStack.setHoverName(this.getCustomName());
+ }
+
+- Block.popResource(this.level(), this.blockPosition(), itemStack);
++ 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
+
+- for (int i = 0; i < this.handItems.size(); i++) {
+- ItemStack itemStack = this.handItems.get(i);
+- if (!itemStack.isEmpty()) {
+- Block.popResource(this.level(), this.blockPosition().above(), itemStack);
++ ItemStack itemstack;
++ int i;
++
++ for (i = 0; i < this.handItems.size(); ++i) {
++ itemstack = (ItemStack) this.handItems.get(i);
++ if (!itemstack.isEmpty()) {
++ drops.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(itemstack)); // CraftBukkit - add to drops
+ this.handItems.set(i, ItemStack.EMPTY);
+ }
+ }
+
+- for (int ix = 0; ix < this.armorItems.size(); ix++) {
+- ItemStack itemStack = this.armorItems.get(ix);
+- if (!itemStack.isEmpty()) {
+- Block.popResource(this.level(), this.blockPosition().above(), itemStack);
+- this.armorItems.set(ix, ItemStack.EMPTY);
++ for (i = 0; i < this.armorItems.size(); ++i) {
++ itemstack = (ItemStack) this.armorItems.get(i);
++ if (!itemstack.isEmpty()) {
++ 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
++
+ }
+
+ private void playBrokenSound() {
+@@ -600,8 +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-vineflower-stripped/net/minecraft/world/entity/decoration/HangingEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/decoration/HangingEntity.java.patch
new file mode 100644
index 0000000000..d25b03fe7f
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/decoration/HangingEntity.java.patch
@@ -0,0 +1,186 @@
+--- 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 {
+ private static final Logger LOGGER = LogUtils.getLogger();
+@@ -57,24 +66,38 @@
+
+ protected void recalculateBoundingBox() {
+ if (this.direction != null) {
+- double d = (double)this.pos.getX() + 0.5;
+- double d1 = (double)this.pos.getY() + 0.5;
+- double d2 = (double)this.pos.getZ() + 0.5;
+- double d3 = 0.46875;
+- double d4 = this.offs(this.getWidth());
+- double d5 = this.offs(this.getHeight());
+- d -= (double)this.direction.getStepX() * 0.46875;
+- d2 -= (double)this.direction.getStepZ() * 0.46875;
+- double var22 = d1 + d5;
+- Direction counterClockWise = this.direction.getCounterClockWise();
+- double var21 = d + d4 * (double)counterClockWise.getStepX();
+- double var24 = d2 + d4 * (double)counterClockWise.getStepZ();
+- this.setPosRaw(var21, var22, var24);
+- double d6 = (double)this.getWidth();
+- double d7 = (double)this.getHeight();
+- double d8 = (double)this.getWidth();
+- if (this.direction.getAxis() == Direction.Axis.Z) {
+- d8 = 1.0;
++ // 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 = offs(width);
++ double d5 = offs(height);
++
++ d0 -= (double) direction.getStepX() * 0.46875D;
++ d2 -= (double) direction.getStepZ() * 0.46875D;
++ d1 += d5;
++ Direction enumdirection = direction.getCounterClockWise();
++
++ d0 += d4 * (double) enumdirection.getStepX();
++ d2 += d4 * (double) enumdirection.getStepZ();
++ if (entity != null) {
++ entity.setPosRaw(d0, d1, d2);
++ }
++ double d6 = (double) width;
++ double d7 = (double) height;
++ double d8 = (double) width;
++
++ if (direction.getAxis() == Direction.Axis.Z) {
++ d8 = 1.0D;
+ } else {
+ d6 = 1.0;
+ }
+@@ -85,9 +108,10 @@
+ this.setBoundingBox(new AABB(var21 - d6, var22 - d7, var24 - d8, var21 + d6, var22 + d7, var24 + d8));
+ }
+ }
++ // CraftBukkit end
+
+- private double offs(int offset) {
+- return offset % 32 == 0 ? 0.5 : 0.0;
++ private static double offs(int i) { // CraftBukkit - static
++ return i % 32 == 0 ? 0.5D : 0.0D;
+ }
+
+ @Override
+@@ -97,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(null);
+ }
+@@ -151,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(source.getEntity());
+@@ -161,22 +228,43 @@
+ }
+
+ @Override
+- public void move(MoverType type, Vec3 pos) {
+- if (!this.level().isClientSide && !this.isRemoved() && pos.lengthSqr() > 0.0) {
++ 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(null);
+ }
+ }
+
+ @Override
+- public void push(double x, double y, double z) {
+- if (!this.level().isClientSide && !this.isRemoved() && x * x + y * y + z * z > 0.0) {
++ 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(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 compound) {
+ BlockPos pos = this.getPos();
+ compound.putInt("TileX", pos.getX());
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/decoration/ItemFrame.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/decoration/ItemFrame.java.patch
new file mode 100644
index 0000000000..3382e654fc
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/decoration/ItemFrame.java.patch
@@ -0,0 +1,87 @@
+--- a/net/minecraft/world/entity/decoration/ItemFrame.java
++++ b/net/minecraft/world/entity/decoration/ItemFrame.java
+@@ -93,16 +96,29 @@
+ @Override
+ protected void recalculateBoundingBox() {
+ if (this.direction != null) {
+- double d = 0.46875;
+- double d1 = (double)this.pos.getX() + 0.5 - (double)this.direction.getStepX() * 0.46875;
+- double d2 = (double)this.pos.getY() + 0.5 - (double)this.direction.getStepY() * 0.46875;
+- double d3 = (double)this.pos.getZ() + 0.5 - (double)this.direction.getStepZ() * 0.46875;
+- this.setPosRaw(d1, d2, d3);
+- double d4 = (double)this.getWidth();
+- double d5 = (double)this.getHeight();
+- double d6 = (double)this.getWidth();
+- Direction.Axis axis = this.direction.getAxis();
+- switch (axis) {
++ // 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) blockPosition.getX() + 0.5D - (double) direction.getStepX() * 0.46875D;
++ double d2 = (double) blockPosition.getY() + 0.5D - (double) direction.getStepY() * 0.46875D;
++ double d3 = (double) blockPosition.getZ() + 0.5D - (double) direction.getStepZ() * 0.46875D;
++
++ if (entity != null) {
++ entity.setPosRaw(d1, d2, d3);
++ }
++ double d4 = (double) width;
++ double d5 = (double) height;
++ double d6 = (double) width;
++ Direction.Axis enumdirection_enumaxis = direction.getAxis();
++
++ switch (enumdirection_enumaxis) {
+ case X:
+ d4 = 1.0;
+ break;
+@@ -119,6 +135,7 @@
+ this.setBoundingBox(new AABB(d1 - d4, d2 - d5, d3 - d6, d1 + d4, d2 + d5, d3 + d6));
+ }
+ }
++ // CraftBukkit end
+
+ @Override
+ public boolean survives() {
+@@ -161,6 +180,11 @@
+ return false;
+ } else if (!source.is(DamageTypeTags.IS_EXPLOSION) && !this.getItem().isEmpty()) {
+ if (!this.level().isClientSide) {
++ // 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);
+@@ -278,13 +314,19 @@
+ }
+
+ public void setItem(ItemStack stack, boolean updateNeighbours) {
+- if (!stack.isEmpty()) {
+- stack = stack.copyWithCount(1);
++ // 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(stack);
+- this.getEntityData().set(DATA_ITEM, stack);
+- if (!stack.isEmpty()) {
++ this.onItemChanged(itemstack);
++ this.getEntityData().set(ItemFrame.DATA_ITEM, itemstack);
++ if (!itemstack.isEmpty() && playSound) { // CraftBukkit
+ this.playSound(this.getAddItemSound(), 1.0F, 1.0F);
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java.patch
new file mode 100644
index 0000000000..144137fc8b
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java.patch
@@ -0,0 +1,69 @@
+--- a/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java
++++ b/net/minecraft/world/entity/decoration/LeashFenceKnotEntity.java
+@@ -24,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 {
+ public static final double OFFSET_Y = 0.375;
+@@ -94,23 +96,49 @@
+ Mob.class, new AABB(this.getX() - 7.0, this.getY() - 7.0, this.getZ() - 7.0, this.getX() + 7.0, this.getY() + 7.0, this.getZ() + 7.0)
+ );
+
+- for (Mob mob : entitiesOfClass) {
+- if (mob.getLeashHolder() == player) {
+- mob.setLeashedTo(this, true);
++ while (iterator.hasNext()) {
++ Mob entityinsentient = (Mob) iterator.next();
++
++ 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;
+ }
+ }
+
+ boolean flag1 = false;
+ if (!flag) {
+- this.discard();
+- if (player.getAbilities().instabuild) {
+- for (Mob mob1 : entitiesOfClass) {
+- if (mob1.isLeashed() && mob1.getLeashHolder() == this) {
+- mob1.dropLeash(true, false);
++ // 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 entityinsentient1 = (Mob) iterator1.next();
++
++ 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-vineflower-stripped/net/minecraft/world/entity/item/FallingBlockEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/item/FallingBlockEntity.java.patch
new file mode 100644
index 0000000000..f6e260e8ea
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/item/FallingBlockEntity.java.patch
@@ -0,0 +1,109 @@
+--- a/net/minecraft/world/entity/item/FallingBlockEntity.java
++++ b/net/minecraft/world/entity/item/FallingBlockEntity.java
+@@ -46,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();
+ private BlockState blockState = Blocks.SAND.defaultBlockState();
+@@ -75,21 +86,21 @@
+ this.setStartPos(this.blockPosition());
+ }
+
+- public static FallingBlockEntity fall(Level level, BlockPos pos, BlockState blockState) {
+- FallingBlockEntity fallingBlockEntity = new FallingBlockEntity(
+- level,
+- (double)pos.getX() + 0.5,
+- (double)pos.getY(),
+- (double)pos.getZ() + 0.5,
+- blockState.hasProperty(BlockStateProperties.WATERLOGGED)
+- ? blockState.setValue(BlockStateProperties.WATERLOGGED, Boolean.valueOf(false))
+- : blockState
+- );
+- level.setBlock(pos, blockState.getFluidState().createLegacyBlock(), 3);
+- level.addFreshEntity(fallingBlockEntity);
+- return fallingBlockEntity;
++ public static FallingBlockEntity fall(Level level, BlockPos pos, IBlockData blockState) {
++ // CraftBukkit start
++ return fall(level, pos, blockState, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
+ }
+
++ 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
+ public boolean isAttackable() {
+ return false;
+@@ -164,11 +180,14 @@
+ this.blockState = this.blockState.setValue(BlockStateProperties.WATERLOGGED, Boolean.valueOf(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);
+@@ -242,15 +257,31 @@
+ return false;
+ } else {
+ Predicate<Entity> predicate = EntitySelector.NO_CREATIVE_OR_SPECTATOR.and(EntitySelector.LIVING_ENTITY_STILL_ALIVE);
+- DamageSource damageSource = this.blockState.getBlock() instanceof Fallable fallable
+- ? fallable.getFallDamageSource(this)
+- : this.damageSources().fallingBlock(this);
+- float f = (float)Math.min(Mth.floor((float)ceil * this.fallDamagePerDistance), this.fallDamageMax);
+- this.level().getEntities(this, this.getBoundingBox(), predicate).forEach(entity -> entity.hurt(damageSource, f));
+- boolean isAnvil = this.blockState.is(BlockTags.ANVIL);
+- if (isAnvil && f > 0.0F && this.random.nextFloat() < 0.05F + (float)ceil * 0.05F) {
+- BlockState blockState = AnvilBlock.damage(this.blockState);
+- if (blockState == null) {
++ Block block = this.blockState.getBlock();
++ DamageSource damagesource1;
++
++ if (block instanceof Fallable) {
++ Fallable fallable = (Fallable) block;
++
++ damagesource1 = fallable.getFallDamageSource(this);
++ } else {
++ damagesource1 = this.damageSources().fallingBlock(this);
++ }
++
++ DamageSource damagesource2 = damagesource1;
++ 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);
++
++ if (flag && f2 > 0.0F && this.random.nextFloat() < 0.05F + (float) i * 0.05F) {
++ IBlockData iblockdata = AnvilBlock.damage(this.blockState);
++
++ if (iblockdata == null) {
+ this.cancelDrop = true;
+ } else {
+ this.blockState = blockState;
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/item/ItemEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/item/ItemEntity.java.patch
new file mode 100644
index 0000000000..fd93ebc427
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/item/ItemEntity.java.patch
@@ -0,0 +1,158 @@
+--- a/net/minecraft/world/entity/item/ItemEntity.java
++++ b/net/minecraft/world/entity/item/ItemEntity.java
+@@ -28,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 {
+ private static final EntityDataAccessor<ItemStack> DATA_ITEM = SynchedEntityData.defineId(ItemEntity.class, EntityDataSerializers.ITEM_STACK);
+@@ -44,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);
+@@ -112,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();
+@@ -162,9 +190,11 @@
+ this.mergeWithNeighbours();
+ }
+
++ /* CraftBukkit start - moved up
+ if (this.age != -32768) {
+ this.age++;
+ }
++ // CraftBukkit end */
+
+ this.hasImpulse = this.hasImpulse | this.updateInWaterStateAndDoFluidPushing();
+ if (!this.level().isClientSide) {
+@@ -175,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();
+ }
+ }
+@@ -246,6 +295,11 @@
+ }
+
+ 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);
+@@ -270,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 - amount);
+ this.gameEvent(GameEvent.ENTITY_DAMAGE, source.getEntity());
+@@ -327,12 +390,53 @@
+ @Override
+ public void playerTouch(Player entity) {
+ if (!this.level().isClientSide) {
+- ItemStack item = this.getItem();
+- Item item1 = item.getItem();
+- int count = item.getCount();
+- if (this.pickupDelay == 0 && (this.target == null || this.target.equals(entity.getUUID())) && entity.getInventory().add(item)) {
+- entity.take(this, count);
+- if (item.isEmpty()) {
++ ItemStack itemstack = this.getItem();
++ Item item = itemstack.getItem();
++ int i = itemstack.getCount();
++
++ // 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();
+ item.setCount(count);
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/item/PrimedTnt.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/item/PrimedTnt.java.patch
new file mode 100644
index 0000000000..d4574d5308
--- /dev/null
+++ b/patch-remap/mache-vineflower-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);
+ private static final EntityDataAccessor<BlockState> DATA_BLOCK_STATE_ID = SynchedEntityData.defineId(PrimedTnt.class, EntityDataSerializers.BLOCK_STATE);
+@@ -26,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);
+@@ -76,10 +86,13 @@
+ int i = this.getFuse() - 1;
+ 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) {
+@@ -89,8 +103,14 @@
+ }
+
+ private void explode() {
+- float f = 4.0F;
+- this.level().explode(this, this.getX(), this.getY(0.0625), this.getZ(), 4.0F, Level.ExplosionInteraction.TNT);
++ // CraftBukkit start
++ // float f = 4.0F;
++ ExplosionPrimeEvent event = CraftEventFactory.callExplosionPrimeEvent((org.bukkit.entity.Explosive)this.getBukkitEntity());
++
++ 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-vineflower-stripped/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch
new file mode 100644
index 0000000000..cd9b888c27
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/AbstractSkeleton.java.patch
@@ -0,0 +1,28 @@
+--- a/net/minecraft/world/entity/monster/AbstractSkeleton.java
++++ b/net/minecraft/world/entity/monster/AbstractSkeleton.java
+@@ -186,10 +197,22 @@
+ double d = target.getX() - this.getX();
+ double d1 = target.getY(0.3333333333333333) - arrow.getY();
+ double d2 = target.getZ() - this.getZ();
+- double squareRoot = Math.sqrt(d * d + d2 * d2);
+- arrow.shoot(d, d1 + squareRoot * 0.2F, d2, 1.6F, (float)(14 - this.level().getDifficulty().getId() * 4));
++ double d3 = Math.sqrt(d0 * d0 + d2 * d2);
++
++ entityarrow.shoot(d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, (float) (14 - this.level().getDifficulty().getId() * 4));
++ // 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(arrow);
++ // this.level().addFreshEntity(entityarrow); // CraftBukkit - moved up
+ }
+
+ protected AbstractArrow getArrow(ItemStack arrowStack, float velocity) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/CaveSpider.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/CaveSpider.java.patch
new file mode 100644
index 0000000000..92ed453b51
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/CaveSpider.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/world/entity/monster/CaveSpider.java
++++ b/net/minecraft/world/entity/monster/CaveSpider.java
+@@ -39,8 +41,8 @@
+ i = 15;
+ }
+
+- if (i > 0) {
+- ((LivingEntity)entity).addEffect(new MobEffectInstance(MobEffects.POISON, i * 20, 0), this);
++ if (b0 > 0) {
++ ((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-vineflower-stripped/net/minecraft/world/entity/monster/Creeper.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Creeper.java.patch
new file mode 100644
index 0000000000..6a51fae160
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -41,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);
+ private static final EntityDataAccessor<Boolean> DATA_IS_POWERED = SynchedEntityData.defineId(Creeper.class, EntityDataSerializers.BOOLEAN);
+@@ -206,9 +224,20 @@
+ @Override
+ public void thunderHit(ServerLevel level, LightningBolt lightning) {
+ super.thunderHit(level, lightning);
+- this.entityData.set(DATA_IS_POWERED, true);
++ // 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
+ protected InteractionResult mobInteract(Player player, InteractionHand hand) {
+ ItemStack itemInHand = player.getItemInHand(hand);
+@@ -218,8 +248,8 @@
+ .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 (!itemInHand.isDamageableItem()) {
+- itemInHand.shrink(1);
++ if (itemstack.getItem().getMaxDamage() == 0) { // CraftBukkit - fix MC-264285: unbreakable flint and steels are completely consumed when igniting a creeper
++ itemstack.shrink(1);
+ } else {
+ itemInHand.hurtAndBreak(1, player, entity -> entity.broadcastBreakEvent(hand));
+ }
+@@ -234,10 +266,20 @@
+ private void explodeCreeper() {
+ 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
+ }
+ }
+
+@@ -251,11 +287,24 @@
+ areaEffectCloud.setDuration(areaEffectCloud.getDuration() / 2);
+ areaEffectCloud.setRadiusPerTick(-areaEffectCloud.getRadius() / (float)areaEffectCloud.getDuration());
+
+- for (MobEffectInstance mobEffectInstance : activeEffects) {
+- areaEffectCloud.addEffect(new MobEffectInstance(mobEffectInstance));
++ if (!collection.isEmpty()) {
++ AreaEffectCloud entityareaeffectcloud = new AreaEffectCloud(this.level(), this.getX(), this.getY(), this.getZ());
++
++ 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()) {
++ MobEffectInstance mobeffect = (MobEffectInstance) iterator.next();
++
++ entityareaeffectcloud.addEffect(new MobEffectInstance(mobeffect));
+ }
+
+- this.level().addFreshEntity(areaEffectCloud);
++ this.level().addFreshEntity(entityareaeffectcloud, CreatureSpawnEvent.SpawnReason.EXPLOSION); // CraftBukkit
+ }
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Drowned.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Drowned.java.patch
new file mode 100644
index 0000000000..41a99efcc4
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Drowned.java.patch
@@ -0,0 +1,15 @@
+--- a/net/minecraft/world/entity/monster/Drowned.java
++++ b/net/minecraft/world/entity/monster/Drowned.java
+@@ -252,9 +244,9 @@
+
+ @Override
+ public void performRangedAttack(LivingEntity target, float distanceFactor) {
+- ThrownTrident thrownTrident = new ThrownTrident(this.level(), this, new ItemStack(Items.TRIDENT));
+- double d = target.getX() - this.getX();
+- double d1 = target.getY(0.3333333333333333) - thrownTrident.getY();
++ 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 squareRoot = Math.sqrt(d * d + d2 * d2);
+ thrownTrident.shoot(d, d1 + squareRoot * 0.2F, d2, 1.6F, (float)(14 - this.level().getDifficulty().getId() * 4));
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/ElderGuardian.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/ElderGuardian.java.patch
new file mode 100644
index 0000000000..7c4840c414
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/ElderGuardian.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/entity/monster/ElderGuardian.java
++++ b/net/minecraft/world/entity/monster/ElderGuardian.java
+@@ -67,12 +69,12 @@
+ 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.0, mobEffectInstance, 1200);
+- list.forEach(
+- serverPlayer -> serverPlayer.connection
+- .send(new ClientboundGameEventPacket(ClientboundGameEventPacket.GUARDIAN_ELDER_EFFECT, this.isSilent() ? 0.0F : 1.0F))
+- );
++ 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((entityplayer) -> {
++ entityplayer.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.GUARDIAN_ELDER_EFFECT, this.isSilent() ? 0.0F : 1.0F));
++ });
+ }
+
+ if (!this.hasRestriction()) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/EnderMan.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/EnderMan.java.patch
new file mode 100644
index 0000000000..9507bf06a9
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/EnderMan.java.patch
@@ -0,0 +1,125 @@
+--- a/net/minecraft/world/entity/monster/EnderMan.java
++++ b/net/minecraft/world/entity/monster/EnderMan.java
+@@ -70,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");
+ private static final AttributeModifier SPEED_MODIFIER_ATTACKING = new AttributeModifier(
+@@ -121,9 +120,20 @@
+
+ @Override
+ public void setTarget(@Nullable LivingEntity livingEntity) {
+- super.setTarget(livingEntity);
+- AttributeInstance attribute = this.getAttribute(Attributes.MOVEMENT_SPEED);
+- if (livingEntity == null) {
++ // CraftBukkit start - fire event
++ setTarget(livingEntity, EntityTargetEvent.TargetReason.UNKNOWN, true);
++ }
++
++ @Override
++ 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 (entityliving == null) {
+ this.targetChangeTime = 0;
+ this.entityData.set(DATA_CREEPY, false);
+ this.entityData.set(DATA_STARED_AT, false);
+@@ -474,22 +495,25 @@
+
+ @Override
+ public void tick() {
+- RandomSource random = this.enderman.getRandom();
+- Level level = this.enderman.level();
+- int floor = Mth.floor(this.enderman.getX() - 1.0 + random.nextDouble() * 2.0);
+- int floor1 = Mth.floor(this.enderman.getY() + random.nextDouble() * 2.0);
+- int floor2 = Mth.floor(this.enderman.getZ() - 1.0 + random.nextDouble() * 2.0);
+- BlockPos blockPos = new BlockPos(floor, floor1, floor2);
+- BlockState blockState = level.getBlockState(blockPos);
+- BlockPos blockPos1 = blockPos.below();
+- BlockState blockState1 = level.getBlockState(blockPos1);
+- BlockState carriedBlock = this.enderman.getCarriedBlock();
+- if (carriedBlock != null) {
+- BlockState var11 = Block.updateFromNeighbourShapes(carriedBlock, this.enderman.level(), blockPos);
+- if (this.canPlaceBlock(level, blockPos, var11, blockState, blockState1, blockPos1)) {
+- level.setBlock(blockPos, var11, 3);
+- level.gameEvent(GameEvent.BLOCK_PLACE, blockPos, GameEvent.Context.of(this.enderman, var11));
+- this.enderman.setCarriedBlock(null);
++ RandomSource randomsource = this.enderman.getRandom();
++ Level world = this.enderman.level();
++ int i = Mth.floor(this.enderman.getX() - 1.0D + randomsource.nextDouble() * 2.0D);
++ int j = Mth.floor(this.enderman.getY() + randomsource.nextDouble() * 2.0D);
++ int k = Mth.floor(this.enderman.getZ() - 1.0D + randomsource.nextDouble() * 2.0D);
++ BlockPos blockposition = new BlockPos(i, j, k);
++ IBlockData iblockdata = world.getBlockState(blockposition);
++ BlockPos blockposition1 = blockposition.below();
++ IBlockData iblockdata1 = world.getBlockState(blockposition1);
++ IBlockData iblockdata2 = this.enderman.getCarriedBlock();
++
++ 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
+ }
+ }
+ }
+@@ -513,6 +527,44 @@
+
+ static class EndermanLookForPlayerGoal extends NearestAttackableTargetGoal<Player> {
+ private final EnderMan enderman;
++
++ public EndermanTakeBlockGoal(EnderMan enderman) {
++ this.enderman = enderman;
++ }
++
++ @Override
++ public boolean canUse() {
++ return this.enderman.getCarriedBlock() != null ? false : (!this.enderman.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? false : this.enderman.getRandom().nextInt(reducedTickDelay(20)) == 0);
++ }
++
++ @Override
++ public void tick() {
++ RandomSource randomsource = this.enderman.getRandom();
++ Level world = this.enderman.level();
++ int i = Mth.floor(this.enderman.getX() - 2.0D + randomsource.nextDouble() * 4.0D);
++ int j = Mth.floor(this.enderman.getY() + randomsource.nextDouble() * 3.0D);
++ int k = Mth.floor(this.enderman.getZ() - 2.0D + randomsource.nextDouble() * 4.0D);
++ BlockPos blockposition = new BlockPos(i, j, k);
++ IBlockData iblockdata = world.getBlockState(blockposition);
++ Vec3 vec3d = new Vec3((double) this.enderman.getBlockX() + 0.5D, (double) j + 0.5D, (double) this.enderman.getBlockZ() + 0.5D);
++ Vec3 vec3d1 = new Vec3((double) i + 0.5D, (double) j + 0.5D, (double) k + 0.5D);
++ BlockHitResult movingobjectpositionblock = world.clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, this.enderman));
++ boolean flag = movingobjectpositionblock.getBlockPos().equals(blockposition);
++
++ if (iblockdata.is(BlockTags.ENDERMAN_HOLDABLE) && flag) {
++ if (CraftEventFactory.callEntityChangeBlockEvent(this.enderman, blockposition, Blocks.AIR.defaultBlockState())) { // CraftBukkit - Place event
++ world.removeBlock(blockposition, false);
++ world.gameEvent(GameEvent.BLOCK_DESTROY, blockposition, GameEvent.Context.of(this.enderman, iblockdata));
++ this.enderman.setCarriedBlock(iblockdata.getBlock().defaultBlockState());
++ } // CraftBukkit
++ }
++
++ }
++ }
++
++ private static class EndermanLookForPlayerGoal extends NearestAttackableTargetGoal<Player> {
++
++ private final EnderMan enderman;
+ @Nullable
+ private Player pendingTarget;
+ private int aggroTime;
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Evoker.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Evoker.java.patch
new file mode 100644
index 0000000000..51d989fab0
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -200,8 +200,8 @@
+ }
+ }
+
+- flag = true;
+- break;
++ 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));
+ }
+
+ blockPos = blockPos.below();
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Ghast.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Ghast.java.patch
new file mode 100644
index 0000000000..894e9c848c
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Ghast.java.patch
@@ -0,0 +1,18 @@
+--- a/net/minecraft/world/entity/monster/Ghast.java
++++ b/net/minecraft/world/entity/monster/Ghast.java
+@@ -291,9 +346,12 @@
+ level.levelEvent(null, 1016, this.ghast.blockPosition(), 0);
+ }
+
+- LargeFireball largeFireball = new LargeFireball(level, this.ghast, d2, d3, d4, this.ghast.getExplosionPower());
+- largeFireball.setPos(this.ghast.getX() + viewVector.x * 4.0, this.ghast.getY(0.5) + 0.5, largeFireball.getZ() + viewVector.z * 4.0);
+- level.addFreshEntity(largeFireball);
++ LargeFireball entitylargefireball = new LargeFireball(world, this.ghast, d2, d3, d4, this.ghast.getExplosionPower());
++
++ // 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-vineflower-stripped/net/minecraft/world/entity/monster/Guardian.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Guardian.java.patch
new file mode 100644
index 0000000000..49115ccb40
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Guardian.java.patch
@@ -0,0 +1,28 @@
+--- a/net/minecraft/world/entity/monster/Guardian.java
++++ b/net/minecraft/world/entity/monster/Guardian.java
+@@ -61,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);
+@@ -74,10 +76,11 @@
+
+ @Override
+ protected void registerGoals() {
+- MoveTowardsRestrictionGoal moveTowardsRestrictionGoal = new MoveTowardsRestrictionGoal(this, 1.0);
+- this.randomStrollGoal = new RandomStrollGoal(this, 1.0, 80);
+- this.goalSelector.addGoal(4, new Guardian.GuardianAttackGoal(this));
+- this.goalSelector.addGoal(5, moveTowardsRestrictionGoal);
++ MoveTowardsRestrictionGoal pathfindergoalmovetowardsrestriction = new MoveTowardsRestrictionGoal(this, 1.0D);
++
++ this.randomStrollGoal = new RandomStrollGoal(this, 1.0D, 80);
++ 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-vineflower-stripped/net/minecraft/world/entity/monster/Husk.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Husk.java.patch
new file mode 100644
index 0000000000..5909521e20
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Husk.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/world/entity/monster/Husk.java
++++ b/net/minecraft/world/entity/monster/Husk.java
+@@ -55,8 +58,9 @@
+ public boolean doHurtTarget(Entity entity) {
+ boolean flag = super.doHurtTarget(entity);
+ if (flag && this.getMainHandItem().isEmpty() && entity instanceof LivingEntity) {
+- float effectiveDifficulty = this.level().getCurrentDifficultyAt(this.blockPosition()).getEffectiveDifficulty();
+- ((LivingEntity)entity).addEffect(new MobEffectInstance(MobEffects.HUNGER, 140 * (int)effectiveDifficulty), this);
++ float f = this.level().getCurrentDifficultyAt(this.blockPosition()).getEffectiveDifficulty();
++
++ ((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-vineflower-stripped/net/minecraft/world/entity/monster/Illusioner.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Illusioner.java.patch
new file mode 100644
index 0000000000..22e9fe5629
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -241,7 +228,7 @@
+
+ @Override
+ protected void performSpellCasting() {
+- Illusioner.this.getTarget().addEffect(new MobEffectInstance(MobEffects.BLINDNESS, 400), Illusioner.this);
++ Illusioner.this.addEffect(new MobEffectInstance(MobEffects.INVISIBILITY, 1200), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ILLUSION); // CraftBukkit
+ }
+
+ @Override
+@@ -273,7 +279,7 @@
+
+ @Override
+ protected void performSpellCasting() {
+- Illusioner.this.addEffect(new MobEffectInstance(MobEffects.INVISIBILITY, 1200));
++ Illusioner.this.getTarget().addEffect(new MobEffectInstance(MobEffects.BLINDNESS, 400), Illusioner.this, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+ }
+
+ @Nullable
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Phantom.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Phantom.java.patch
new file mode 100644
index 0000000000..85732351ac
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Phantom.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/world/entity/monster/Phantom.java
++++ b/net/minecraft/world/entity/monster/Phantom.java
+@@ -508,7 +549,10 @@
+ cat.hiss();
+ }
+
+- this.isScaredOfCat = !entitiesOfClass.isEmpty();
++ if (Phantom.this.canAttack(entityhuman, TargetingConditions.DEFAULT)) {
++ Phantom.this.setTarget(entityhuman, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER, true); // CraftBukkit - reason
++ return true;
++ }
+ }
+
+ return !this.isScaredOfCat;
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Ravager.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Ravager.java.patch
new file mode 100644
index 0000000000..052e734c64
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -41,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 -> entity.isAlive() && !(entity instanceof Ravager);
+ private static final double BASE_MOVEMENT_SPEED = 0.3;
+@@ -149,7 +157,12 @@
+ BlockState blockState = this.level().getBlockState(blockPos);
+ 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-vineflower-stripped/net/minecraft/world/entity/monster/Shulker.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Shulker.java.patch
new file mode 100644
index 0000000000..fa79916f85
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Shulker.java.patch
@@ -0,0 +1,77 @@
+--- a/net/minecraft/world/entity/monster/Shulker.java
++++ b/net/minecraft/world/entity/monster/Shulker.java
+@@ -56,7 +59,14 @@
+ 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
++
++public class Shulker extends AbstractGolem implements VariantHolder<Optional<DyeColor>>, IMonster {
++
+ private static final UUID COVERED_ARMOR_MODIFIER_UUID = UUID.fromString("7E0292F2-9434-48D5-A29F-9583AF7DF27F");
+ private static final AttributeModifier COVERED_ARMOR_MODIFIER = new AttributeModifier(
+ COVERED_ARMOR_MODIFIER_UUID, "Covered armor bonus", 20.0, AttributeModifier.Operation.ADDITION
+@@ -373,18 +399,21 @@
+ if (!this.isNoAi() && this.isAlive()) {
+ BlockPos blockPos = this.blockPosition();
+
+- for (int i = 0; i < 5; i++) {
+- BlockPos blockPos1 = blockPos.offset(
+- Mth.randomBetweenInclusive(this.random, -8, 8),
+- Mth.randomBetweenInclusive(this.random, -8, 8),
+- Mth.randomBetweenInclusive(this.random, -8, 8)
+- );
+- 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-6))) {
+- Direction direction = this.findAttachableSurface(blockPos1);
+- if (direction != null) {
++ for (int i = 0; i < 5; ++i) {
++ BlockPos blockposition1 = blockposition.offset(Mth.randomBetweenInclusive(this.random, -8, 8), Mth.randomBetweenInclusive(this.random, -8, 8), Mth.randomBetweenInclusive(this.random, -8, 8));
++
++ if (blockposition1.getY() > this.level().getMinBuildHeight() && this.level().isEmptyBlock(blockposition1) && this.level().getWorldBorder().isWithinBounds(blockposition1) && this.level().noCollision(this, (new AABB(blockposition1)).deflate(1.0E-6D))) {
++ Direction enumdirection = this.findAttachableSurface(blockposition1);
++
++ 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);
+@@ -443,14 +475,16 @@
+ Vec3 vec3 = this.position();
+ AABB boundingBox = this.getBoundingBox();
+ if (!this.isClosed() && this.teleportSomewhere()) {
+- int size = this.level().getEntities(EntityType.SHULKER, boundingBox.inflate(8.0), Entity::isAlive).size();
+- float f = (float)(size - 1) / 5.0F;
+- if (!(this.level().random.nextFloat() < f)) {
+- Shulker shulker = EntityType.SHULKER.create(this.level());
+- if (shulker != null) {
+- shulker.setVariant(this.getVariant());
+- shulker.moveTo(vec3);
+- this.level().addFreshEntity(shulker);
++ int i = this.level().getEntities((EntityTypeTest) EntityType.SHULKER, axisalignedbb.inflate(8.0D), Entity::isAlive).size();
++ float f = (float) (i - 1) / 5.0F;
++
++ if (this.level().random.nextFloat() >= f) {
++ Shulker entityshulker = (Shulker) EntityType.SHULKER.create(this.level());
++
++ 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-vineflower-stripped/net/minecraft/world/entity/monster/Silverfish.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Silverfish.java.patch
new file mode 100644
index 0000000000..bde77f90a3
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Silverfish.java.patch
@@ -0,0 +1,106 @@
+--- 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
+ private Silverfish.SilverfishWakeUpFriendsGoal friendsGoal;
+@@ -139,7 +143,68 @@
+ return new Vector3f(0.0F, entityDimensions.height - 0.0625F * f, 0.0F);
+ }
+
+- static class SilverfishMergeWithStoneGoal extends RandomStrollGoal {
++ private static class SilverfishWakeUpFriendsGoal extends Goal {
++
++ private final Silverfish silverfish;
++ private int lookForFriends;
++
++ public SilverfishWakeUpFriendsGoal(Silverfish silverfish) {
++ this.silverfish = silverfish;
++ }
++
++ public void notifyHurt() {
++ if (this.lookForFriends == 0) {
++ this.lookForFriends = this.adjustedTickDelay(20);
++ }
++
++ }
++
++ @Override
++ public boolean canUse() {
++ return this.lookForFriends > 0;
++ }
++
++ @Override
++ public void tick() {
++ --this.lookForFriends;
++ if (this.lookForFriends <= 0) {
++ Level world = this.silverfish.level();
++ RandomSource randomsource = this.silverfish.getRandom();
++ BlockPos blockposition = this.silverfish.blockPosition();
++
++ for (int i = 0; i <= 5 && i >= -5; i = (i <= 0 ? 1 : 0) - i) {
++ for (int j = 0; j <= 10 && j >= -10; j = (j <= 0 ? 1 : 0) - j) {
++ for (int k = 0; k <= 10 && k >= -10; k = (k <= 0 ? 1 : 0) - k) {
++ BlockPos blockposition1 = blockposition.offset(j, i, k);
++ IBlockData iblockdata = world.getBlockState(blockposition1);
++ Block block = iblockdata.getBlock();
++
++ if (block instanceof InfestedBlock) {
++ // 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 {
++ world.setBlock(blockposition1, ((InfestedBlock) block).hostStateByInfested(world.getBlockState(blockposition1)), 3);
++ }
++
++ if (randomsource.nextBoolean()) {
++ return;
++ }
++ }
++ }
++ }
++ }
++ }
++
++ }
++ }
++
++ private static class SilverfishMergeWithStoneGoal extends RandomStrollGoal {
++
+ @Nullable
+ private Direction selectedDirection;
+ private boolean doMerge;
+@@ -182,11 +249,17 @@
+ if (!this.doMerge) {
+ super.start();
+ } else {
+- LevelAccessor levelAccessor = this.mob.level();
+- BlockPos blockPos = BlockPos.containing(this.mob.getX(), this.mob.getY() + 0.5, this.mob.getZ()).relative(this.selectedDirection);
+- BlockState blockState = levelAccessor.getBlockState(blockPos);
+- if (InfestedBlock.isCompatibleHostBlock(blockState)) {
+- levelAccessor.setBlock(blockPos, InfestedBlock.infestedStateByHost(blockState), 3);
++ Level world = this.mob.level();
++ BlockPos blockposition = BlockPos.containing(this.mob.getX(), this.mob.getY() + 0.5D, this.mob.getZ()).relative(this.selectedDirection);
++ IBlockData iblockdata = world.getBlockState(blockposition);
++
++ 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-vineflower-stripped/net/minecraft/world/entity/monster/Skeleton.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Skeleton.java.patch
new file mode 100644
index 0000000000..a4bf214045
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -85,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(null, 1048, this.blockPosition(), 0);
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Slime.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Slime.java.patch
new file mode 100644
index 0000000000..e937d8eaea
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Slime.java.patch
@@ -0,0 +1,85 @@
+--- a/net/minecraft/world/entity/monster/Slime.java
++++ b/net/minecraft/world/entity/monster/Slime.java
+@@ -44,7 +44,16 @@
+ 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
++
++public class Slime extends Mob implements IMonster {
++
+ private static final EntityDataAccessor<Integer> ID_SIZE = SynchedEntityData.defineId(Slime.class, EntityDataSerializers.INT);
+ public static final int MIN_SIZE = 1;
+ public static final int MAX_SIZE = 127;
+@@ -202,23 +207,53 @@
+ int i = size / 2;
+ int i1 = 2 + this.random.nextInt(3);
+
+- for (int i2 = 0; i2 < i1; i2++) {
+- float f1 = ((float)(i2 % 2) - 0.5F) * f;
+- float f2 = ((float)(i2 / 2) - 0.5F) * f;
+- Slime slime = this.getType().create(this.level());
+- if (slime != null) {
++ if (!this.level().isClientSide && i > 1 && this.isDeadOrDying()) {
++ Component ichatbasecomponent = this.getCustomName();
++ boolean flag = this.isNoAi();
++ float f = (float) i / 4.0F;
++ 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;
++ Slime entityslime = (Slime) this.getType().create(this.level());
++
++ if (entityslime != null) {
+ if (this.isPersistenceRequired()) {
+ slime.setPersistenceRequired();
+ }
+
+- slime.setCustomName(customName);
+- slime.setNoAi(isNoAi);
+- slime.setInvulnerable(this.isInvulnerable());
+- slime.setSize(i, true);
+- slime.moveTo(this.getX() + (double)f1, this.getY() + 0.5, 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(reason);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/SpellcasterIllager.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/SpellcasterIllager.java.patch
new file mode 100644
index 0000000000..a456cdba28
--- /dev/null
+++ b/patch-remap/mache-vineflower-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 {
+ private static final EntityDataAccessor<Byte> DATA_SPELL_CASTING_ID = SynchedEntityData.defineId(SpellcasterIllager.class, EntityDataSerializers.BYTE);
+@@ -190,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-vineflower-stripped/net/minecraft/world/entity/monster/Spider.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Spider.java.patch
new file mode 100644
index 0000000000..036f0e8e36
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Spider.java.patch
@@ -0,0 +1,19 @@
+--- a/net/minecraft/world/entity/monster/Spider.java
++++ b/net/minecraft/world/entity/monster/Spider.java
+@@ -173,10 +177,12 @@
+ }
+ }
+
+- if (var9 instanceof Spider.SpiderEffectsGroupData spiderEffectsGroupData) {
+- MobEffect mobEffect = spiderEffectsGroupData.effect;
+- if (mobEffect != null) {
+- this.addEffect(new MobEffectInstance(mobEffect, -1));
++ if (object instanceof Spider.SpiderEffectsGroupData) {
++ Spider.SpiderEffectsGroupData entityspider_groupdataspider = (Spider.SpiderEffectsGroupData) object;
++ MobEffect mobeffectlist = entityspider_groupdataspider.effect;
++
++ if (mobeffectlist != null) {
++ this.addEffect(new MobEffectInstance(mobeffectlist, -1), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.SPIDER_SPAWN); // CraftBukkit
+ }
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Strider.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Strider.java.patch
new file mode 100644
index 0000000000..7db33003e2
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Strider.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/entity/monster/Strider.java
++++ b/net/minecraft/world/entity/monster/Strider.java
+@@ -320,8 +349,16 @@
+ var10000 = false;
+ }
+
+- boolean flag1 = var10000;
+- this.setSuffocating(!flag || flag1);
++ boolean flag2 = flag1;
++
++ // 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-vineflower-stripped/net/minecraft/world/entity/monster/Vex.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Vex.java.patch
new file mode 100644
index 0000000000..a7c7978c7a
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Vex.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/world/entity/monster/Vex.java
++++ b/net/minecraft/world/entity/monster/Vex.java
+@@ -362,8 +403,9 @@
+ }
+
+ @Override
+- public boolean canContinueToUse() {
+- return false;
++ public void start() {
++ Vex.this.setTarget(Vex.this.owner.getTarget(), org.bukkit.event.entity.EntityTargetEvent.TargetReason.OWNER_ATTACKED_TARGET, true); // CraftBukkit
++ super.start();
+ }
+
+ @Override
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Witch.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Witch.java.patch
new file mode 100644
index 0000000000..e518e8dd84
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Witch.java.patch
@@ -0,0 +1,24 @@
+--- a/net/minecraft/world/entity/monster/Witch.java
++++ b/net/minecraft/world/entity/monster/Witch.java
+@@ -123,11 +125,16 @@
+ this.setUsingItem(false);
+ ItemStack mainHandItem = this.getMainHandItem();
+ this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY);
+- if (mainHandItem.is(Items.POTION)) {
+- List<MobEffectInstance> mobEffects = PotionUtils.getMobEffects(mainHandItem);
+- if (mobEffects != null) {
+- for (MobEffectInstance mobEffectInstance : mobEffects) {
+- this.addEffect(new MobEffectInstance(mobEffectInstance));
++ if (itemstack.is(Items.POTION)) {
++ List<MobEffectInstance> list = PotionUtils.getMobEffects(itemstack);
++
++ if (list != null) {
++ Iterator iterator = list.iterator();
++
++ while (iterator.hasNext()) {
++ MobEffectInstance mobeffect = (MobEffectInstance) iterator.next();
++
++ this.addEffect(new MobEffectInstance(mobeffect), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+ }
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/WitherSkeleton.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/WitherSkeleton.java.patch
new file mode 100644
index 0000000000..af3413f316
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -104,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-vineflower-stripped/net/minecraft/world/entity/monster/Zombie.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Zombie.java.patch
new file mode 100644
index 0000000000..385c5f7f80
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/Zombie.java.patch
@@ -0,0 +1,228 @@
+--- a/net/minecraft/world/entity/monster/Zombie.java
++++ b/net/minecraft/world/entity/monster/Zombie.java
+@@ -65,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 {
+ private static final UUID SPEED_MODIFIER_BABY_UUID = UUID.fromString("B9766B59-9566-4402-BC1F-2EE2A276D836");
+@@ -84,7 +93,8 @@
+ private final BreakDoorGoal breakDoorGoal = new BreakDoorGoal(this, DOOR_BREAKING_PREDICATE);
+ 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);
+@@ -202,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();
+ }
+@@ -219,6 +231,7 @@
+ }
+
+ super.tick();
++ this.lastTick = MinecraftServer.currentTick; // CraftBukkit
+ }
+
+ @Override
+@@ -248,7 +263,8 @@
+ super.aiStep();
+ }
+
+- private void startUnderWaterConversion(int conversionTime) {
++ public void startUnderWaterConversion(int conversionTime) {
++ this.lastTick = MinecraftServer.currentTick; // CraftBukkit
+ this.conversionTime = conversionTime;
+ this.getEntityData().set(DATA_DROWNED_CONVERSION_ID, true);
+ }
+@@ -261,10 +278,15 @@
+ }
+
+ protected void convertToZombieType(EntityType<? extends Zombie> entityType) {
+- Zombie zombie = this.convertTo(entityType, true);
+- if (zombie != null) {
+- zombie.handleAttributes(zombie.level().getCurrentDifficultyAt(zombie.blockPosition()).getSpecialMultiplier());
+- zombie.setCanBreakDoors(zombie.supportsBreakDoorGoal() && this.canBreakDoors());
++ Zombie entityzombie = (Zombie) this.convertTo(entityType, true, EntityTransformEvent.TransformReason.DROWNED, CreatureSpawnEvent.SpawnReason.DROWNED);
++
++ 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
+ }
+ }
+
+@@ -294,29 +315,22 @@
+ int floor2 = Mth.floor(this.getZ());
+ Zombie zombie = new Zombie(this.level());
+
+- for (int i = 0; i < 50; i++) {
+- int i1 = floor + Mth.nextInt(this.random, 7, 40) * Mth.nextInt(this.random, -1, 1);
+- int i2 = floor1 + Mth.nextInt(this.random, 7, 40) * Mth.nextInt(this.random, -1, 1);
+- int i3 = floor2 + Mth.nextInt(this.random, 7, 40) * Mth.nextInt(this.random, -1, 1);
+- BlockPos blockPos = new BlockPos(i1, i2, i3);
+- EntityType<?> type = zombie.getType();
+- SpawnPlacements.Type placementType = SpawnPlacements.getPlacementType(type);
+- if (NaturalSpawner.isSpawnPositionOk(placementType, this.level(), blockPos, type)
+- && SpawnPlacements.checkSpawnRules(type, serverLevel, MobSpawnType.REINFORCEMENT, blockPos, this.level().random)) {
+- zombie.setPos((double)i1, (double)i2, (double)i3);
+- if (!this.level().hasNearbyAlivePlayer((double)i1, (double)i2, (double)i3, 7.0)
+- && this.level().isUnobstructed(zombie)
+- && this.level().noCollision(zombie)
+- && !this.level().containsAnyLiquid(zombie.getBoundingBox())) {
+- zombie.setTarget(target);
+- zombie.finalizeSpawn(
+- serverLevel, this.level().getCurrentDifficultyAt(zombie.blockPosition()), MobSpawnType.REINFORCEMENT, null, null
+- );
+- serverLevel.addFreshEntityWithPassengers(zombie);
+- this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE)
+- .addPermanentModifier(new AttributeModifier("Zombie reinforcement caller charge", -0.05F, AttributeModifier.Operation.ADDITION));
+- zombie.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE)
+- .addPermanentModifier(new AttributeModifier("Zombie reinforcement callee charge", -0.05F, AttributeModifier.Operation.ADDITION));
++ for (int l = 0; l < 50; ++l) {
++ int i1 = i + Mth.nextInt(this.random, 7, 40) * Mth.nextInt(this.random, -1, 1);
++ int j1 = j + Mth.nextInt(this.random, 7, 40) * Mth.nextInt(this.random, -1, 1);
++ int k1 = k + Mth.nextInt(this.random, 7, 40) * Mth.nextInt(this.random, -1, 1);
++ BlockPos blockposition = new BlockPos(i1, j1, k1);
++ EntityType<?> entitytypes = entityzombie.getType();
++ SpawnPlacements.Surface entitypositiontypes_surface = SpawnPlacements.getPlacementType(entitytypes);
++
++ 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));
++ entityzombie.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).addPermanentModifier(new AttributeModifier("Zombie reinforcement callee charge", -0.05000000074505806D, AttributeModifier.Operation.ADDITION));
+ break;
+ }
+ }
+@@ -331,9 +346,17 @@
+ public boolean doHurtTarget(Entity entity) {
+ boolean flag = super.doHurtTarget(entity);
+ if (flag) {
+- float effectiveDifficulty = this.level().getCurrentDifficultyAt(this.blockPosition()).getEffectiveDifficulty();
+- if (this.getMainHandItem().isEmpty() && this.isOnFire() && this.random.nextFloat() < effectiveDifficulty * 0.3F) {
+- entity.setSecondsOnFire(2 * (int)effectiveDifficulty);
++ float f = this.level().getCurrentDifficultyAt(this.blockPosition()).getEffectiveDifficulty();
++
++ if (this.getMainHandItem().isEmpty() && this.isOnFire() && this.random.nextFloat() < f * 0.3F) {
++ // 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
+ }
+ }
+
+@@ -409,25 +438,36 @@
+ if (level.getDifficulty() != Difficulty.HARD && this.random.nextBoolean()) {
+ return flag;
+ }
++ // CraftBukkit start
++ flag = zombifyVillager(level, entityvillager, this.blockPosition(), this.isSilent(), CreatureSpawnEvent.SpawnReason.INFECTION) == null;
++ }
+
+- ZombieVillager zombieVillager = villager.convertTo(EntityType.ZOMBIE_VILLAGER, false);
+- if (zombieVillager != null) {
+- zombieVillager.finalizeSpawn(
+- level, level.getCurrentDifficultyAt(zombieVillager.blockPosition()), MobSpawnType.CONVERSION, new Zombie.ZombieGroupData(false, true), null
+- );
+- zombieVillager.setVillagerData(villager.getVillagerData());
+- zombieVillager.setGossips(villager.getGossips().store(NbtOps.INSTANCE));
+- zombieVillager.setTradeOffers(villager.getOffers().createTag());
+- zombieVillager.setVillagerXp(villager.getVillagerXp());
+- if (!this.isSilent()) {
+- level.levelEvent(null, 1026, this.blockPosition(), 0);
++ return flag;
++ }
++
++ 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
+@@ -471,14 +511,15 @@
+ chicken.setChickenJockey(true);
+ this.startRiding(chicken);
+ }
+- } else if ((double)random.nextFloat() < 0.05) {
+- Chicken chicken1 = EntityType.CHICKEN.create(this.level());
+- if (chicken1 != null) {
+- chicken1.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), 0.0F);
+- chicken1.finalizeSpawn(level, difficulty, MobSpawnType.JOCKEY, null, null);
+- chicken1.setChickenJockey(true);
+- this.startRiding(chicken1);
+- level.addFreshEntity(chicken1);
++ } else if ((double) randomsource.nextFloat() < 0.05D) {
++ Chicken entitychicken1 = (Chicken) EntityType.CHICKEN.create(this.level());
++
++ 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-vineflower-stripped/net/minecraft/world/entity/monster/ZombieVillager.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/ZombieVillager.java.patch
new file mode 100644
index 0000000000..f18b0413fb
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/ZombieVillager.java.patch
@@ -0,0 +1,109 @@
+--- 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();
+ private static final EntityDataAccessor<Boolean> DATA_CONVERTING_ID = SynchedEntityData.defineId(ZombieVillager.class, EntityDataSerializers.BOOLEAN);
+@@ -68,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);
+@@ -134,14 +147,20 @@
+ @Override
+ public void tick() {
+ if (!this.level().isClientSide && this.isAlive() && this.isConverting()) {
+- int conversionProgress = this.getConversionProgress();
+- this.villagerConversionTime -= conversionProgress;
++ 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) {
+ this.finishConversion((ServerLevel)this.level());
+ }
+ }
+
+ super.tick();
++ this.lastTick = MinecraftServer.currentTick; // CraftBukkit
+ }
+
+ @Override
+@@ -183,10 +203,12 @@
+ private void startConverting(@Nullable UUID conversionStarter, int villagerConversionTime) {
+ this.conversionStarter = conversionStarter;
+ this.villagerConversionTime = villagerConversionTime;
+- this.getEntityData().set(DATA_CONVERTING_ID, true);
+- this.removeEffect(MobEffects.WEAKNESS);
+- this.addEffect(new MobEffectInstance(MobEffects.DAMAGE_BOOST, villagerConversionTime, Math.min(this.level().getDifficulty().getId() - 1, 0)));
+- this.level().broadcastEntityEvent(this, (byte)16);
++ this.getEntityData().set(ZombieVillager.DATA_CONVERTING_ID, true);
++ // 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);
+ }
+
+ @Override
+@@ -211,7 +224,15 @@
+ }
+
+ private void finishConversion(ServerLevel serverLevel) {
+- Villager villager = this.convertTo(EntityType.VILLAGER, false);
++ // 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 (EquipmentSlot equipmentSlot : EquipmentSlot.values()) {
+ ItemStack itemBySlot = this.getItemBySlot(equipmentSlot);
+@@ -219,9 +242,12 @@
+ if (EnchantmentHelper.hasBindingCurse(itemBySlot)) {
+ villager.getSlot(equipmentSlot.getIndex() + 300).set(itemBySlot);
+ } else {
+- double d = (double)this.getEquipmentDropChance(equipmentSlot);
+- if (d > 1.0) {
+- this.spawnAtLocation(itemBySlot);
++ double d0 = (double) this.getEquipmentDropChance(enumitemslot);
++
++ if (d0 > 1.0D) {
++ this.forceDrops = true; // CraftBukkit
++ this.spawnAtLocation(itemstack);
++ this.forceDrops = false; // CraftBukkit
+ }
+ }
+ }
+@@ -247,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(null, 1027, this.blockPosition(), 0);
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/ZombifiedPiglin.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/ZombifiedPiglin.java.patch
new file mode 100644
index 0000000000..ca4080c2ed
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/ZombifiedPiglin.java.patch
@@ -0,0 +1,68 @@
+--- a/net/minecraft/world/entity/monster/ZombifiedPiglin.java
++++ b/net/minecraft/world/entity/monster/ZombifiedPiglin.java
+@@ -142,15 +140,18 @@
+ }
+
+ private void alertOthers() {
+- double attributeValue = this.getAttributeValue(Attributes.FOLLOW_RANGE);
+- AABB aABB = AABB.unitCubeFromLowerCorner(this.position()).inflate(attributeValue, 10.0, attributeValue);
+- this.level()
+- .getEntitiesOfClass(ZombifiedPiglin.class, aABB, EntitySelector.NO_SPECTATORS)
+- .stream()
+- .filter(zombifiedPiglin -> zombifiedPiglin != this)
+- .filter(zombifiedPiglin -> zombifiedPiglin.getTarget() == null)
+- .filter(zombifiedPiglin -> !zombifiedPiglin.isAlliedTo(this.getTarget()))
+- .forEach(zombifiedPiglin -> zombifiedPiglin.setTarget(this.getTarget()));
++ double d0 = this.getAttributeValue(Attributes.FOLLOW_RANGE);
++ AABB axisalignedbb = AABB.unitCubeFromLowerCorner(this.position()).inflate(d0, 10.0D, d0);
++
++ 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
++ });
+ }
+
+ private void playAngerSound() {
+@@ -158,22 +159,31 @@
+ }
+
+ @Override
+- public void setTarget(@Nullable LivingEntity livingEntity) {
+- if (this.getTarget() == null && livingEntity != null) {
+- this.playFirstAngerSoundIn = FIRST_ANGER_SOUND_DELAY.sample(this.random);
+- this.ticksUntilNextAlert = ALERT_INTERVAL.sample(this.random);
++ 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);
+ }
+
+ if (livingEntity instanceof Player) {
+ this.setLastHurtByPlayer((Player)livingEntity);
+ }
+
+- super.setTarget(livingEntity);
++ return super.setTarget(entityliving, reason, fireEvent); // CraftBukkit
+ }
+
+ @Override
+ public void startPersistentAngerTimer() {
+- this.setRemainingPersistentAngerTime(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(
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java.patch
new file mode 100644
index 0000000000..589d7f3bb9
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java
++++ b/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java
+@@ -114,9 +115,10 @@
+ }
+
+ protected void finishConversion(ServerLevel serverLevel) {
+- ZombifiedPiglin zombifiedPiglin = this.convertTo(EntityType.ZOMBIFIED_PIGLIN, true);
+- if (zombifiedPiglin != null) {
+- zombifiedPiglin.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 200, 0));
++ 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 (entitypigzombie != null) {
++ entitypigzombie.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 200, 0));
+ }
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/piglin/Piglin.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/piglin/Piglin.java.patch
new file mode 100644
index 0000000000..d34be168ba
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/piglin/Piglin.java.patch
@@ -0,0 +1,162 @@
+--- 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);
+ private static final EntityDataAccessor<Boolean> DATA_IS_CHARGING_CROSSBOW = SynchedEntityData.defineId(Piglin.class, EntityDataSerializers.BOOLEAN);
+@@ -71,53 +82,15 @@
+ private static final int MAX_PASSENGERS_ON_ONE_HOGLIN = 3;
+ private static final float PROBABILITY_OF_SPAWNING_AS_BABY = 0.2F;
+ private static final float BABY_EYE_HEIGHT_ADJUSTMENT = 0.82F;
+- private static final double PROBABILITY_OF_SPAWNING_WITH_CROSSBOW_INSTEAD_OF_SWORD = 0.5;
+- private final SimpleContainer inventory = new SimpleContainer(8);
+- 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,
+- 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
+- );
++ private static final double PROBABILITY_OF_SPAWNING_WITH_CROSSBOW_INSTEAD_OF_SWORD = 0.5D;
++ public final SimpleContainer inventory = new SimpleContainer(8);
++ public 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);
+@@ -136,6 +109,14 @@
+ }
+
+ 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
+@@ -144,6 +125,10 @@
+ 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
+@@ -254,7 +248,7 @@
+
+ @Override
+ public Brain<Piglin> getBrain() {
+- return (Brain<Piglin>)super.getBrain();
++ return (Brain<Piglin>) super.getBrain(); // CraftBukkit - Decompile error
+ }
+
+ @Override
+@@ -398,7 +388,7 @@
+ }
+
+ protected void holdInOffHand(ItemStack stack) {
+- if (stack.is(PiglinAi.BARTERING_ITEM)) {
++ 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 {
+@@ -422,12 +414,10 @@
+ if (EnchantmentHelper.hasBindingCurse(existing)) {
+ return false;
+ } else {
+- boolean flag = PiglinAi.isLovedItem(candidate) || candidate.is(Items.CROSSBOW);
+- boolean flag1 = PiglinAi.isLovedItem(existing) || existing.is(Items.CROSSBOW);
+- return flag && !flag1
+- || (flag || !flag1)
+- && (!this.isAdult() || candidate.is(Items.CROSSBOW) || !existing.is(Items.CROSSBOW))
+- && super.canReplaceCurrentItem(candidate, existing);
++ 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() && !candidate.is(Items.CROSSBOW) && existing.is(Items.CROSSBOW) ? false : super.canReplaceCurrentItem(candidate, existing)));
+ }
+ }
+
+@@ -453,7 +444,7 @@
+
+ @Override
+ protected SoundEvent getAmbientSound() {
+- return this.level().isClientSide ? null : PiglinAi.getSoundForCurrentActivity(this).orElse(null);
++ return this.level().isClientSide ? null : (SoundEvent) PiglinAi.getSoundForCurrentActivity(this).orElse(null); // CraftBukkit - Decompile error
+ }
+
+ @Override
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/piglin/PiglinAi.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/piglin/PiglinAi.java.patch
new file mode 100644
index 0000000000..1f9d3de4ad
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/piglin/PiglinAi.java.patch
@@ -0,0 +1,142 @@
+--- a/net/minecraft/world/entity/monster/piglin/PiglinAi.java
++++ b/net/minecraft/world/entity/monster/piglin/PiglinAi.java
+@@ -72,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 {
+ public static final int REPELLENT_DETECTION_RANGE_HORIZONTAL = 8;
+@@ -329,8 +238,10 @@
+
+ protected static void pickUpItem(Piglin piglin, ItemEntity itemEntity) {
+ stopWalking(piglin);
+- ItemStack item;
+- if (itemEntity.getItem().is(Items.GOLD_NUGGET)) {
++ ItemStack itemstack;
++
++ // 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());
+ item = itemEntity.getItem();
+ itemEntity.discard();
+@@ -338,15 +251,17 @@
+ piglin.take(itemEntity, 1);
+ item = removeOneItemFromItemEntity(itemEntity);
+ }
++ // CraftBukkit end
+
+- if (isLovedItem(item)) {
++ 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, item);
+ admireGoldItem(piglin);
+ } else if (isFood(item) && !hasEatenRecently(piglin)) {
+ eat(piglin);
+ } else {
+- boolean flag = !piglin.equipItemIfPossible(item).equals(ItemStack.EMPTY);
++ boolean flag = !piglin.equipItemIfPossible(itemstack, itemEntity).equals(ItemStack.EMPTY); // CraftBukkit
++
+ if (!flag) {
+ putInInventory(piglin, item);
+ }
+@@ -377,21 +296,28 @@
+ ItemStack itemInHand = piglin.getItemInHand(InteractionHand.OFF_HAND);
+ piglin.setItemInHand(InteractionHand.OFF_HAND, ItemStack.EMPTY);
+ if (piglin.isAdult()) {
+- boolean isBarterCurrency = isBarterCurrency(itemInHand);
+- if (shouldBarter && isBarterCurrency) {
+- throwItems(piglin, getBarterResponseItems(piglin));
+- } else if (!isBarterCurrency) {
+- boolean flag = !piglin.equipItemIfPossible(itemInHand).isEmpty();
+- if (!flag) {
+- putInInventory(piglin, itemInHand);
++ 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();
++
++ if (!flag2) {
++ putInInventory(piglin, itemstack);
++ }
+ }
+ } else {
+- boolean isBarterCurrency = !piglin.equipItemIfPossible(itemInHand).isEmpty();
+- if (!isBarterCurrency) {
+- ItemStack mainHandItem = piglin.getMainHandItem();
+- if (isLovedItem(mainHandItem)) {
+- putInInventory(piglin, mainHandItem);
++ flag1 = !piglin.equipItemIfPossible(itemstack).isEmpty();
++ if (!flag1) {
++ ItemStack itemstack1 = piglin.getMainHandItem();
++
++ if (isLovedItem(itemstack1, piglin)) { // CraftBukkit - Changes to allow for custom payment in bartering
++ putInInventory(piglin, itemstack1);
+ } else {
+ throwItems(piglin, Collections.singletonList(mainHandItem));
+ }
+@@ -459,7 +393,7 @@
+ return false;
+ } else if (isAdmiringDisabled(piglin) && piglin.getBrain().hasMemoryValue(MemoryModuleType.ATTACK_TARGET)) {
+ return false;
+- } else if (isBarterCurrency(stack)) {
++ } else if (isBarterCurrency(stack, piglin)) { // CraftBukkit
+ return isNotHoldingLovedItemInOffHand(piglin);
+ } else {
+ boolean canAddToInventory = piglin.canAddToInventory(stack);
+@@ -473,6 +402,12 @@
+ }
+ }
+
++ // 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
++
+ protected static boolean isLovedItem(ItemStack item) {
+ return item.is(ItemTags.PIGLIN_LOVED);
+ }
+@@ -548,7 +503,7 @@
+ }
+
+ protected static boolean canAdmire(Piglin piglin, ItemStack stack) {
+- return !isAdmiringDisabled(piglin) && !isAdmiringItem(piglin) && piglin.isAdult() && isBarterCurrency(stack);
++ return !isAdmiringDisabled(piglin) && !isAdmiringItem(piglin) && piglin.isAdult() && isBarterCurrency(stack, piglin); // CraftBukkit
+ }
+
+ protected static void wasHurtBy(Piglin piglin, LivingEntity target) {
+@@ -796,6 +760,12 @@
+ return piglin.getBrain().hasMemoryValue(MemoryModuleType.ADMIRING_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 isBarterCurrency(ItemStack stack) {
+ return stack.is(BARTERING_ITEM);
+ }
+@@ -833,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-vineflower-stripped/net/minecraft/world/entity/monster/warden/Warden.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/warden/Warden.java.patch
new file mode 100644
index 0000000000..23a489b117
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/monster/warden/Warden.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/world/entity/monster/warden/Warden.java
++++ b/net/minecraft/world/entity/monster/warden/Warden.java
+@@ -404,8 +412,9 @@
+ }
+
+ public static void applyDarknessAround(ServerLevel level, Vec3 pos, @Nullable Entity source, int radius) {
+- MobEffectInstance mobEffectInstance = new MobEffectInstance(MobEffects.DARKNESS, 260, 0, false, false);
+- MobEffectUtil.addEffectToPlayersAround(level, source, pos, (double)radius, mobEffectInstance, 200);
++ MobEffectInstance mobeffect = new MobEffectInstance(MobEffects.DARKNESS, 260, 0, false, false);
++
++ 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-vineflower-stripped/net/minecraft/world/entity/npc/AbstractVillager.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/npc/AbstractVillager.java.patch
new file mode 100644
index 0000000000..f8fffcf3b4
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/npc/AbstractVillager.java.patch
@@ -0,0 +1,63 @@
+--- a/net/minecraft/world/entity/npc/AbstractVillager.java
++++ b/net/minecraft/world/entity/npc/AbstractVillager.java
+@@ -34,8 +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 {
++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;
+@@ -43,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);
+@@ -226,11 +243,21 @@
+ ArrayList<VillagerTrades.ItemListing> list = Lists.newArrayList(newTrades);
+ int i = 0;
+
+- while (i < maxNumbers && !list.isEmpty()) {
+- MerchantOffer offer = list.remove(this.random.nextInt(list.size())).getOffer(this, this.random);
+- if (offer != null) {
+- givenMerchantOffers.add(offer);
+- i++;
++ while (j < maxNumbers && !arraylist.isEmpty()) {
++ MerchantOffer merchantrecipe = ((VillagerTrades.ItemListing) arraylist.remove(this.random.nextInt(arraylist.size()))).getOffer(this, this.random);
++
++ 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-vineflower-stripped/net/minecraft/world/entity/npc/InventoryCarrier.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/npc/InventoryCarrier.java.patch
new file mode 100644
index 0000000000..e3a2262da9
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/npc/InventoryCarrier.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/world/entity/npc/InventoryCarrier.java
++++ b/net/minecraft/world/entity/npc/InventoryCarrier.java
+@@ -20,6 +23,13 @@
+ return;
+ }
+
++ // 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 count = item.getCount();
+ ItemStack itemStack = inventory.addItem(item);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/npc/Villager.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/npc/Villager.java.patch
new file mode 100644
index 0000000000..eab608f375
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/npc/Villager.java.patch
@@ -0,0 +1,106 @@
+--- a/net/minecraft/world/entity/npc/Villager.java
++++ b/net/minecraft/world/entity/npc/Villager.java
+@@ -91,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();
+ private static final EntityDataAccessor<VillagerData> DATA_VILLAGER_DATA = SynchedEntityData.defineId(Villager.class, EntityDataSerializers.VILLAGER_DATA);
+@@ -286,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
+ }
+ }
+
+@@ -397,8 +365,16 @@
+ public void restock() {
+ this.updateDemand();
+
+- for (MerchantOffer merchantOffer : this.getOffers()) {
+- merchantOffer.resetUses();
++ while (iterator.hasNext()) {
++ MerchantOffer merchantrecipe = (MerchantOffer) iterator.next();
++
++ // 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();
+@@ -458,8 +438,18 @@
+ private void catchUpDemand() {
+ int i = 2 - this.numberOfRestocksToday;
+ if (i > 0) {
+- for (MerchantOffer merchantOffer : this.getOffers()) {
+- merchantOffer.resetUses();
++ Iterator iterator = this.getOffers().iterator();
++
++ while (iterator.hasNext()) {
++ MerchantOffer merchantrecipe = (MerchantOffer) iterator.next();
++
++ // 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
+ }
+ }
+
+@@ -818,8 +852,13 @@
+ witch.setCustomNameVisible(this.isCustomNameVisible());
+ }
+
+- witch.setPersistenceRequired();
+- level.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 {
+@@ -900,15 +950,15 @@
+
+ public void spawnGolemIfNeeded(ServerLevel serverLevel, long gameTime, int minVillagerAmount) {
+ if (this.wantsToSpawnGolem(gameTime)) {
+- AABB aABB = this.getBoundingBox().inflate(10.0, 10.0, 10.0);
+- List<Villager> entitiesOfClass = serverLevel.getEntitiesOfClass(Villager.class, aABB);
+- List<Villager> list = entitiesOfClass.stream().filter(villager -> villager.wantsToSpawnGolem(gameTime)).limit(5L).collect(Collectors.toList());
+- if (list.size() >= minVillagerAmount) {
+- if (!SpawnUtil.trySpawnMob(
+- EntityType.IRON_GOLEM, MobSpawnType.MOB_SUMMONED, serverLevel, this.blockPosition(), 10, 8, 6, SpawnUtil.Strategy.LEGACY_IRON_GOLEM
+- )
+- .isEmpty()) {
+- entitiesOfClass.forEach(GolemSensor::golemDetected);
++ AABB axisalignedbb = this.getBoundingBox().inflate(10.0D, 10.0D, 10.0D);
++ List<Villager> list = serverLevel.getEntitiesOfClass(Villager.class, axisalignedbb);
++ List<Villager> list1 = (List) list.stream().filter((entityvillager) -> {
++ return entityvillager.wantsToSpawnGolem(gameTime);
++ }).limit(5L).collect(Collectors.toList());
++
++ if (list1.size() >= j) {
++ 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-vineflower-stripped/net/minecraft/world/entity/npc/WanderingTrader.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/npc/WanderingTrader.java.patch
new file mode 100644
index 0000000000..8894c943c2
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/npc/WanderingTrader.java.patch
@@ -0,0 +1,66 @@
+--- a/net/minecraft/world/entity/npc/WanderingTrader.java
++++ b/net/minecraft/world/entity/npc/WanderingTrader.java
+@@ -46,7 +47,15 @@
+ 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
++
++public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVillager {
++
+ private static final int NUMBER_OF_TRADE_OFFERS = 5;
+ @Nullable
+ private BlockPos wanderTarget;
+@@ -54,6 +63,7 @@
+
+ public WanderingTrader(EntityType<? extends WanderingTrader> entityType, Level level) {
+ super(entityType, level);
++ this.setDespawnDelay(48000); // CraftBukkit - set default from MobSpawnerTrader
+ }
+
+ @Override
+@@ -135,16 +132,28 @@
+ if (this.level().enabledFeatures().contains(FeatureFlags.TRADE_REBALANCE)) {
+ this.experimentalUpdateTrades();
+ } else {
+- VillagerTrades.ItemListing[] itemListings = VillagerTrades.WANDERING_TRADER_TRADES.get(1);
+- VillagerTrades.ItemListing[] itemListings1 = VillagerTrades.WANDERING_TRADER_TRADES.get(2);
+- if (itemListings != null && itemListings1 != null) {
+- MerchantOffers offers = this.getOffers();
+- this.addOffersFromItemListings(offers, itemListings, 5);
+- int randomInt = this.random.nextInt(itemListings1.length);
+- VillagerTrades.ItemListing itemListing = itemListings1[randomInt];
+- MerchantOffer offer = itemListing.getOffer(this, this.random);
+- if (offer != null) {
+- offers.add(offer);
++ VillagerTrades.ItemListing[] avillagertrades_imerchantrecipeoption = (VillagerTrades.ItemListing[]) VillagerTrades.WANDERING_TRADER_TRADES.get(1);
++ VillagerTrades.ItemListing[] avillagertrades_imerchantrecipeoption1 = (VillagerTrades.ItemListing[]) VillagerTrades.WANDERING_TRADER_TRADES.get(2);
++
++ if (avillagertrades_imerchantrecipeoption != null && avillagertrades_imerchantrecipeoption1 != null) {
++ MerchantOffers merchantrecipelist = this.getOffers();
++
++ this.addOffersFromItemListings(merchantrecipelist, avillagertrades_imerchantrecipeoption, 5);
++ int i = this.random.nextInt(avillagertrades_imerchantrecipeoption1.length);
++ VillagerTrades.ItemListing villagertrades_imerchantrecipeoption = avillagertrades_imerchantrecipeoption1[i];
++ MerchantOffer merchantrecipe = villagertrades_imerchantrecipeoption.getOffer(this, this.random);
++
++ 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-vineflower-stripped/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch
new file mode 100644
index 0000000000..7be38ff45b
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/npc/WanderingTraderSpawner.java.patch
@@ -0,0 +1,47 @@
+--- a/net/minecraft/world/entity/npc/WanderingTraderSpawner.java
++++ b/net/minecraft/world/entity/npc/WanderingTraderSpawner.java
+@@ -101,16 +110,17 @@
+ return false;
+ }
+
+- WanderingTrader wanderingTrader = EntityType.WANDERING_TRADER.spawn(serverLevel, blockPos2, MobSpawnType.EVENT);
+- if (wanderingTrader != null) {
+- for (int i1 = 0; i1 < 2; i1++) {
+- this.tryToSpawnLlamaFor(serverLevel, wanderingTrader, 4);
++ WanderingTrader entityvillagertrader = (WanderingTrader) EntityType.WANDERING_TRADER.spawn(serverLevel, blockposition2, EnumMobSpawn.EVENT, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit
++
++ if (entityvillagertrader != null) {
++ for (int i = 0; i < 2; ++i) {
++ this.tryToSpawnLlamaFor(serverLevel, entityvillagertrader, 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;
+ }
+ }
+@@ -120,11 +130,13 @@
+ }
+
+ private void tryToSpawnLlamaFor(ServerLevel serverLevel, WanderingTrader trader, int maxDistance) {
+- BlockPos blockPos = this.findSpawnPositionNear(serverLevel, trader.blockPosition(), maxDistance);
+- if (blockPos != null) {
+- TraderLlama traderLlama = EntityType.TRADER_LLAMA.spawn(serverLevel, blockPos, MobSpawnType.EVENT);
+- if (traderLlama != null) {
+- traderLlama.setLeashedTo(trader, true);
++ BlockPos blockposition = this.findSpawnPositionNear(serverLevel, trader.blockPosition(), maxDistance);
++
++ if (blockposition != null) {
++ TraderLlama entityllamatrader = (TraderLlama) EntityType.TRADER_LLAMA.spawn(serverLevel, blockposition, EnumMobSpawn.EVENT, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.NATURAL); // CraftBukkit
++
++ if (entityllamatrader != null) {
++ entityllamatrader.setLeashedTo(trader, true);
+ }
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/player/Inventory.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/player/Inventory.java.patch
new file mode 100644
index 0000000000..78b7c418e9
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/player/Inventory.java.patch
@@ -0,0 +1,103 @@
+--- a/net/minecraft/world/entity/player/Inventory.java
++++ b/net/minecraft/world/entity/player/Inventory.java
+@@ -24,7 +26,15 @@
+ 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 class Inventory implements Container, INamableTileEntity {
++
+ public static final int POP_TIME_DURATION = 5;
+ public static final int INVENTORY_SIZE = 36;
+ private static final int SELECTION_SIZE = 9;
+@@ -40,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.player = player;
+ }
+@@ -60,6 +118,28 @@
+ && destination.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 (this.items.get(i).isEmpty()) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/player/Player.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/player/Player.java.patch
new file mode 100644
index 0000000000..0a462b50bf
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/player/Player.java.patch
@@ -0,0 +1,560 @@
+--- 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.Scoreboard;
+ import net.minecraft.world.scores.Team;
+ 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 {
+ private static final Logger LOGGER = LogUtils.getLogger();
+@@ -143,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;
+@@ -175,6 +180,16 @@
+ public FishingHook fishing;
+ protected float hurtDir;
+
++ // 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.setUUID(gameProfile.getId());
+@@ -315,9 +331,10 @@
+ }
+
+ private void turtleHelmetTick() {
+- ItemStack itemBySlot = this.getItemBySlot(EquipmentSlot.HEAD);
+- if (itemBySlot.is(Items.TURTLE_HELMET) && !this.isEyeInFluid(FluidTags.WATER)) {
+- this.addEffect(new MobEffectInstance(MobEffects.WATER_BREATHING, 200, 0, false, false, true));
++ 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), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.TURTLE_HELMET); // CraftBukkit
+ }
+ }
+
+@@ -485,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;
+@@ -508,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) {
+@@ -670,7 +694,14 @@
+
+ @Nullable
+ public ItemEntity drop(ItemStack droppedItem, boolean dropAround, boolean includeThrowerName) {
+- if (droppedItem.isEmpty()) {
++ // 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 {
+ if (this.level().isClientSide) {
+@@ -703,7 +735,34 @@
+ );
+ }
+
+- 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;
+ }
+ }
+
+@@ -837,12 +912,12 @@
+ return false;
+ } else {
+ if (!this.level().isClientSide) {
+- this.removeEntitiesOnShoulder();
++ // this.removeEntitiesOnShoulder(); // CraftBukkit - moved down
+ }
+
+ if (source.scalesWithDifficulty()) {
+ if (this.level().getDifficulty() == Difficulty.PEACEFUL) {
+- amount = 0.0F;
++ return false; // CraftBukkit - f = 0.0f -> return false
+ }
+
+ if (this.level().getDifficulty() == Difficulty.EASY) {
+@@ -854,7 +929,13 @@
+ }
+ }
+
+- return amount != 0.0F && super.hurt(source, amount);
++ // CraftBukkit start - Don't filter out 0 damage
++ boolean damaged = super.hurt(source, amount);
++ if (damaged) {
++ this.removeEntitiesOnShoulder();
++ }
++ return damaged;
++ // CraftBukkit end
+ }
+ }
+ }
+@@ -873,9 +955,29 @@
+ }
+
+ public boolean canHarmPlayer(Player other) {
+- Team team = this.getTeam();
+- Team team1 = other.getTeam();
+- return team == null || !team.isAlliedTo(team1) || team.isAllowFriendlyFire();
++ // 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;
++ }
++ }
++
++ if (this instanceof ServerPlayer) {
++ return !team.hasPlayer(((ServerPlayer) this).getBukkitEntity());
++ }
++ return !team.hasPlayer(this.level().getCraftServer().getOfflinePlayer(this.getScoreboardName()));
++ // CraftBukkit end
+ }
+
+ @Override
+@@ -913,29 +1019,38 @@
+ }
+ }
+
++ // CraftBukkit start
+ @Override
+- protected void actuallyHurt(DamageSource damageSrc, float damageAmount) {
+- if (!this.isInvulnerableTo(damageSrc)) {
+- damageAmount = this.getDamageAfterArmorAbsorb(damageSrc, damageAmount);
+- damageAmount = this.getDamageAfterMagicAbsorb(damageSrc, damageAmount);
+- float var7 = Math.max(damageAmount - this.getAbsorptionAmount(), 0.0F);
+- this.setAbsorptionAmount(this.getAbsorptionAmount() - (damageAmount - var7));
+- float f1 = damageAmount - var7;
+- if (f1 > 0.0F && f1 < 3.4028235E37F) {
+- this.awardStat(Stats.DAMAGE_ABSORBED, Math.round(f1 * 10.0F));
++ 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);
++ float f1 = f;
++
++ f = Math.max(f - this.getAbsorptionAmount(), 0.0F);
++ this.setAbsorptionAmount(this.getAbsorptionAmount() - (f1 - f));
++ float f2 = f1 - f;
++
++ if (f2 > 0.0F && f2 < 3.4028235E37F) {
++ this.awardStat(Stats.DAMAGE_ABSORBED, Math.round(f2 * 10.0F));
+ }
+
+- if (var7 != 0.0F) {
+- this.causeFoodExhaustion(damageSrc.getFoodExhaustion());
+- this.getCombatTracker().recordDamage(damageSrc, var7);
+- this.setHealth(this.getHealth() - var7);
+- if (var7 < 3.4028235E37F) {
+- this.awardStat(Stats.DAMAGE_TAKEN, Math.round(var7 * 10.0F));
++ if (f != 0.0F) {
++ this.causeFoodExhaustion(damagesource.getFoodExhaustion(), EntityExhaustionEvent.ExhaustionReason.DAMAGED); // CraftBukkit - EntityExhaustionEvent
++ this.getCombatTracker().recordDamage(damagesource, f);
++ this.setHealth(this.getHealth() - f);
++ if (f < 3.4028235E37F) {
++ this.awardStat(Stats.DAMAGE_TAKEN, Math.round(f * 10.0F));
+ }
+
+ this.gameEvent(GameEvent.ENTITY_DAMAGE);
+ }
+ }
++ return false; // CraftBukkit
+ }
+
+ @Override
+@@ -1107,12 +1211,13 @@
+ damageBonus = EnchantmentHelper.getDamageBonus(this.getMainHandItem(), MobType.UNDEFINED);
+ }
+
+- float attackStrengthScale = this.getAttackStrengthScale(0.5F);
+- float var21 = f * (0.2F + attackStrengthScale * attackStrengthScale * 0.8F);
+- float var23 = damageBonus * attackStrengthScale;
+- this.resetAttackStrengthTicker();
+- if (var21 > 0.0F || var23 > 0.0F) {
+- boolean flag = attackStrengthScale > 0.9F;
++ float f2 = this.getAttackStrengthScale(0.5F);
++
++ f *= 0.2F + f2 * f2 * 0.8F;
++ f1 *= f2;
++ // 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;
+ int i = 0;
+ int var24 = i + EnchantmentHelper.getKnockbackBonus(this);
+@@ -1150,10 +1252,17 @@
+ boolean flag4 = false;
+ int fireAspect = EnchantmentHelper.getFireAspect(this);
+ if (target instanceof LivingEntity) {
+- f1 = ((LivingEntity)target).getHealth();
+- if (fireAspect > 0 && !target.isOnFire()) {
+- flag4 = true;
+- target.setSecondsOnFire(1);
++ 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
+ }
+ }
+
+@@ -1183,19 +1286,15 @@
+ if (flag3) {
+ float f2 = 1.0F + EnchantmentHelper.getSweepingDamageRatio(this) * f;
+
+- for (LivingEntity livingEntity : this.level()
+- .getEntitiesOfClass(LivingEntity.class, target.getBoundingBox().inflate(1.0, 0.25, 1.0))) {
+- if (livingEntity != this
+- && livingEntity != target
+- && !this.isAlliedTo(livingEntity)
+- && (!(livingEntity instanceof ArmorStand) || !((ArmorStand)livingEntity).isMarker())
+- && this.distanceToSqr(livingEntity) < 9.0) {
+- livingEntity.knockback(
+- 0.4F,
+- (double)Mth.sin(this.getYRot() * (float) (Math.PI / 180.0)),
+- (double)(-Mth.cos(this.getYRot() * (float) (Math.PI / 180.0)))
+- );
+- livingEntity.hurt(this.damageSources().playerAttack(this), f2);
++ while (iterator.hasNext()) {
++ LivingEntity entityliving = (LivingEntity) iterator.next();
++
++ 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
+ }
+ }
+
+@@ -1205,9 +1303,26 @@
+ }
+
+ if (target instanceof ServerPlayer && target.hurtMarked) {
+- ((ServerPlayer)target).connection.send(new ClientboundSetEntityMotionPacket(target));
++ // 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(deltaMovement);
++ target.setDeltaMovement(vec3d);
++ }
++ // CraftBukkit end
+ }
+
+ if (flag2) {
+@@ -1250,10 +1363,18 @@
+ }
+
+ if (target instanceof LivingEntity) {
+- float f3 = f1 - ((LivingEntity)target).getHealth();
+- this.awardStat(Stats.DAMAGE_DEALT, Math.round(f3 * 10.0F));
+- if (fireAspect > 0) {
+- target.setSecondsOnFire(fireAspect * 4);
++ float f5 = f3 - ((LivingEntity) target).getHealth();
++
++ this.awardStat(Stats.DAMAGE_DEALT, Math.round(f5 * 10.0F));
++ if (j > 0) {
++ // 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 && f3 > 2.0F) {
+@@ -1263,13 +1384,18 @@
+ }
+ }
+
+- this.causeFoodExhaustion(0.1F);
++ this.causeFoodExhaustion(0.1F, EntityExhaustionEvent.ExhaustionReason.ATTACK); // CraftBukkit - EntityExhaustionEvent
+ } else {
+ this.level()
+ .playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_ATTACK_NODAMAGE, this.getSoundSource(), 1.0F, 1.0F);
+ if (flag4) {
+ target.clearFire();
+ }
++ // CraftBukkit start - resync on cancelled event
++ if (this instanceof ServerPlayer) {
++ ((ServerPlayer) this).getBukkitEntity().updateInventory();
++ }
++ // CraftBukkit end
+ }
+ }
+ }
+@@ -1344,7 +1471,13 @@
+ }
+
+ public Either<Player.BedSleepingProblem, Unit> startSleepInBed(BlockPos bedPos) {
+- this.startSleeping(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);
+ }
+@@ -1441,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
+ }
+ }
+
+@@ -1466,7 +1593,11 @@
+ Vec3 deltaMovement1 = this.getDeltaMovement();
+ this.setDeltaMovement(deltaMovement1.x, d * 0.6, deltaMovement1.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(travelVector);
+ }
+@@ -1516,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
+@@ -1630,10 +1781,21 @@
+ }
+ }
+
++ // 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(exhaustion);
++ // CraftBukkit start
++ EntityExhaustionEvent event = CraftEventFactory.callPlayerExhaustionEvent(this, reason, f);
++ if (!event.isCancelled()) {
++ this.foodData.addExhaustion(event.getExhaustion());
++ }
++ // CraftBukkit end
+ }
+ }
+ }
+@@ -1723,13 +1881,20 @@
+
+ @Override
+ public void setItemSlot(EquipmentSlot slot, ItemStack stack) {
+- this.verifyEquippedItem(stack);
+- if (slot == EquipmentSlot.MAINHAND) {
+- this.onEquipItem(slot, this.inventory.items.set(this.inventory.selected, stack), stack);
+- } else if (slot == EquipmentSlot.OFFHAND) {
+- this.onEquipItem(slot, this.inventory.offhand.set(0, stack), stack);
+- } else if (slot.getType() == EquipmentSlot.Type.ARMOR) {
+- this.onEquipItem(slot, this.inventory.armor.set(slot.getIndex(), stack), stack);
++ // CraftBukkit start
++ setItemSlot(slot, stack, false);
++ }
++
++ @Override
++ public void setItemSlot(EquipmentSlot enumitemslot, ItemStack itemstack, boolean silent) {
++ // CraftBukkit end
++ this.verifyEquippedItem(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
+ }
+ }
+
+@@ -1765,24 +1933,30 @@
+
+ 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 entityCompound) {
+- if (!this.level().isClientSide && !entityCompound.isEmpty()) {
+- EntityType.create(entityCompound, 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.7F, this.getZ());
+- ((ServerLevel)this.level()).addWithUUID(entity);
+- });
++ entity.setPos(this.getX(), this.getY() + 0.699999988079071D, this.getZ());
++ return ((ServerLevel) this.level()).addWithUUID(entity, CreatureSpawnEvent.SpawnReason.SHOULDER_ENTITY); // CraftBukkit
++ }).orElse(true); // CraftBukkit
+ }
++
++ return true; // CraftBukkit
+ }
+
+ @Override
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/AbstractArrow.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/AbstractArrow.java.patch
new file mode 100644
index 0000000000..f87c49e800
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/AbstractArrow.java.patch
@@ -0,0 +1,69 @@
+--- a/net/minecraft/world/entity/projectile/AbstractArrow.java
++++ b/net/minecraft/world/entity/projectile/AbstractArrow.java
+@@ -44,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 {
+ private static final double ARROW_BASE_DAMAGE = 2.0;
+@@ -197,8 +222,8 @@
+ }
+ }
+
+- if (hitResult != null && !isNoPhysics) {
+- this.onHit(hitResult);
++ if (object != null && !flag) {
++ this.preOnHit((HitResult) object); // CraftBukkit - projectile hit event
+ this.hasImpulse = true;
+ }
+
+@@ -341,10 +367,17 @@
+ }
+
+ boolean flag = entity.getType() == EntityType.ENDERMAN;
+- int remainingFireTicks = entity.getRemainingFireTicks();
+- boolean isDeflectsArrows = entity.getType().is(EntityTypeTags.DEFLECTS_ARROWS);
+- if (this.isOnFire() && !flag && !isDeflectsArrows) {
+- entity.setSecondsOnFire(5);
++ int k = entity.getRemainingFireTicks();
++ boolean flag1 = entity.getType().is(EntityTypeTags.DEFLECTS_ARROWS);
++
++ if (this.isOnFire() && !flag && !flag1) {
++ // 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)ceil)) {
+@@ -516,7 +555,22 @@
+ @Override
+ public void playerTouch(Player entity) {
+ if (!this.level().isClientSide && (this.inGround || this.isNoPhysics()) && this.shakeTime <= 0) {
+- if (this.tryPickup(entity)) {
++ // 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-vineflower-stripped/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java.patch
new file mode 100644
index 0000000000..0d280ee620
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java.patch
@@ -0,0 +1,84 @@
+--- a/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java
++++ b/net/minecraft/world/entity/projectile/AbstractHurtingProjectile.java
+@@ -16,11 +16,14 @@
+ 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);
+@@ -37,11 +39,18 @@
+ this(entityType, level);
+ this.moveTo(x, y, z, this.getYRot(), this.getXRot());
+ this.reapplyPosition();
+- double squareRoot = Math.sqrt(offsetX * offsetX + offsetY * offsetY + offsetZ * offsetZ);
+- if (squareRoot != 0.0) {
+- this.xPower = offsetX / squareRoot * 0.1;
+- this.yPower = offsetY / squareRoot * 0.1;
+- this.zPower = offsetZ / squareRoot * 0.1;
++ // 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) {
++ this.xPower = d3 / d6 * 0.1D;
++ this.yPower = d4 / d6 * 0.1D;
++ this.zPower = d5 / d6 * 0.1D;
+ }
+ }
+
+@@ -81,9 +92,16 @@
+ this.setSecondsOnFire(1);
+ }
+
+- HitResult hitResultOnMoveVector = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity, this.getClipType());
+- if (hitResultOnMoveVector.getType() != HitResult.Type.MISS) {
+- this.onHit(hitResultOnMoveVector);
++ HitResult movingobjectposition = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity, this.getClipType());
++
++ 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();
+@@ -185,11 +199,17 @@
+ Entity entity = source.getEntity();
+ if (entity != null) {
+ if (!this.level().isClientSide) {
+- Vec3 lookAngle = entity.getLookAngle();
+- this.setDeltaMovement(lookAngle);
+- this.xPower = lookAngle.x * 0.1;
+- this.yPower = lookAngle.y * 0.1;
+- this.zPower = lookAngle.z * 0.1;
++ // CraftBukkit start
++ if (CraftEventFactory.handleNonLivingEntityDamageEvent(this, source, amount, false)) {
++ return false;
++ }
++ // CraftBukkit end
++ Vec3 vec3d = entity.getLookAngle();
++
++ this.setDeltaMovement(vec3d);
++ this.xPower = vec3d.x * 0.1D;
++ this.yPower = vec3d.y * 0.1D;
++ this.zPower = vec3d.z * 0.1D;
+ this.setOwner(entity);
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/Arrow.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/Arrow.java.patch
new file mode 100644
index 0000000000..6c7f584823
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/Arrow.java.patch
@@ -0,0 +1,37 @@
+--- a/net/minecraft/world/entity/projectile/Arrow.java
++++ b/net/minecraft/world/entity/projectile/Arrow.java
+@@ -177,22 +207,21 @@
+ super.doPostHurtEffects(living);
+ Entity effectSource = this.getEffectSource();
+
+- for (MobEffectInstance mobEffectInstance : this.potion.getEffects()) {
+- living.addEffect(
+- new MobEffectInstance(
+- mobEffectInstance.getEffect(),
+- Math.max(mobEffectInstance.mapDuration(i -> i / 8), 1),
+- mobEffectInstance.getAmplifier(),
+- mobEffectInstance.isAmbient(),
+- mobEffectInstance.isVisible()
+- ),
+- effectSource
+- );
++ MobEffectInstance mobeffect;
++
++ while (iterator.hasNext()) {
++ mobeffect = (MobEffectInstance) iterator.next();
++ living.addEffect(new MobEffectInstance(mobeffect.getEffect(), Math.max(mobeffect.mapDuration((i) -> {
++ return i / 8;
++ }), 1), mobeffect.getAmplifier(), mobeffect.isAmbient(), mobeffect.isVisible()), entity, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ARROW); // CraftBukkit
+ }
+
+ if (!this.effects.isEmpty()) {
+- for (MobEffectInstance mobEffectInstance : this.effects) {
+- living.addEffect(mobEffectInstance, effectSource);
++ iterator = this.effects.iterator();
++
++ while (iterator.hasNext()) {
++ mobeffect = (MobEffectInstance) iterator.next();
++ living.addEffect(mobeffect, entity, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ARROW); // CraftBukkit
+ }
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/EvokerFangs.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/EvokerFangs.java.patch
new file mode 100644
index 0000000000..582caa9bf3
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/EvokerFangs.java.patch
@@ -0,0 +1,19 @@
+--- a/net/minecraft/world/entity/projectile/EvokerFangs.java
++++ b/net/minecraft/world/entity/projectile/EvokerFangs.java
+@@ -112,10 +125,13 @@
+ }
+
+ private void dealDamageTo(LivingEntity target) {
+- LivingEntity owner = this.getOwner();
+- if (target.isAlive() && !target.isInvulnerable() && target != owner) {
+- if (owner == null) {
++ LivingEntity entityliving1 = this.getOwner();
++
++ 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 (owner.isAlliedTo(target)) {
+ return;
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/EyeOfEnder.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/EyeOfEnder.java.patch
new file mode 100644
index 0000000000..b3e85b40a3
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/EyeOfEnder.java.patch
@@ -0,0 +1,25 @@
+--- a/net/minecraft/world/entity/projectile/EyeOfEnder.java
++++ b/net/minecraft/world/entity/projectile/EyeOfEnder.java
+@@ -34,8 +35,8 @@
+ }
+
+ public void setItem(ItemStack stack) {
+- if (!stack.is(Items.ENDER_EYE) || stack.hasTag()) {
+- this.getEntityData().set(DATA_ITEM_STACK, stack.copyWithCount(1));
++ if (true || !stack.is(Items.ENDER_EYE) || stack.hasTag()) { // CraftBukkit - always allow item changing
++ this.getEntityData().set(EyeOfEnder.DATA_ITEM_STACK, stack.copyWithCount(1));
+ }
+ }
+
+@@ -179,8 +175,9 @@
+
+ @Override
+ public void readAdditionalSaveData(CompoundTag compound) {
+- ItemStack itemStack = ItemStack.of(compound.getCompound("Item"));
+- this.setItem(itemStack);
++ ItemStack itemstack = ItemStack.of(compound.getCompound("Item"));
++
++ if (!itemstack.isEmpty()) this.setItem(itemstack); // CraftBukkit - SPIGOT-6103 summon, see also SPIGOT-5474
+ }
+
+ @Override
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/Fireball.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/Fireball.java.patch
new file mode 100644
index 0000000000..1965d9c38c
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/Fireball.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/world/entity/projectile/Fireball.java
++++ b/net/minecraft/world/entity/projectile/Fireball.java
+@@ -58,7 +63,8 @@
+ @Override
+ public void readAdditionalSaveData(CompoundTag compound) {
+ super.readAdditionalSaveData(compound);
+- ItemStack itemStack = ItemStack.of(compound.getCompound("Item"));
+- this.setItem(itemStack);
++ ItemStack itemstack = ItemStack.of(compound.getCompound("Item"));
++
++ if (!itemstack.isEmpty()) this.setItem(itemstack); // CraftBukkit - SPIGOT-5474 probably came from bugged earlier versions
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch
new file mode 100644
index 0000000000..8927e9a414
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/FireworkRocketEntity.java.patch
@@ -0,0 +1,83 @@
+--- a/net/minecraft/world/entity/projectile/FireworkRocketEntity.java
++++ b/net/minecraft/world/entity/projectile/FireworkRocketEntity.java
+@@ -24,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 {
+ private static final EntityDataAccessor<ItemStack> DATA_ID_FIREWORKS_ITEM = SynchedEntityData.defineId(
+@@ -147,7 +144,7 @@
+
+ HitResult hitResultOnMoveVector = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity);
+ if (!this.noPhysics) {
+- this.onHit(hitResultOnMoveVector);
++ this.preOnHit(movingobjectposition); // CraftBukkit - projectile hit event
+ this.hasImpulse = true;
+ }
+
+@@ -171,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
+ }
+ }
+
+@@ -186,7 +179,11 @@
+ protected void onHitEntity(EntityHitResult result) {
+ super.onHitEntity(result);
+ if (!this.level().isClientSide) {
+- this.explode();
++ // CraftBukkit start
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callFireworkExplodeEvent(this).isCancelled()) {
++ this.explode();
++ }
++ // CraftBukkit end
+ }
+ }
+
+@@ -195,7 +193,11 @@
+ BlockPos blockPos = new BlockPos(result.getBlockPos());
+ 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(result);
+@@ -219,7 +223,9 @@
+
+ if (f > 0.0F) {
+ if (this.attachedToEntity != null) {
+- this.attachedToEntity.hurt(this.damageSources().fireworks(this, this.getOwner()), 5.0F + (float)(list.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 d = 5.0;
+@@ -239,8 +250,11 @@
+ }
+
+ if (flag) {
+- float f1 = f * (float)Math.sqrt((5.0 - (double)this.distanceTo(livingEntity)) / 5.0);
+- livingEntity.hurt(this.damageSources().fireworks(this, this.getOwner()), f1);
++ float f1 = f * (float) Math.sqrt((5.0D - (double) this.distanceTo(entityliving)) / 5.0D);
++
++ CraftEventFactory.entityDamage = this; // CraftBukkit
++ entityliving.hurt(this.damageSources().fireworks(this, this.getOwner()), f1);
++ CraftEventFactory.entityDamage = null; // CraftBukkit
+ }
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/FishingHook.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/FishingHook.java.patch
new file mode 100644
index 0000000000..01e178dd07
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/FishingHook.java.patch
@@ -0,0 +1,210 @@
+--- a/net/minecraft/world/entity/projectile/FishingHook.java
++++ b/net/minecraft/world/entity/projectile/FishingHook.java
+@@ -45,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();
+ private final RandomSource syncronizedRandom = RandomSource.create();
+@@ -65,6 +72,18 @@
+ private final int luck;
+ private final int lureSpeed;
+
++ // 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.noCulling = true;
+@@ -252,8 +276,9 @@
+ }
+
+ private void checkCollision() {
+- HitResult hitResultOnMoveVector = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity);
+- this.onHit(hitResultOnMoveVector);
++ HitResult movingobjectposition = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity);
++
++ this.preOnHit(movingobjectposition); // CraftBukkit - projectile hit event
+ }
+
+ @Override
+@@ -283,13 +309,14 @@
+ private void catchingFish(BlockPos pos) {
+ ServerLevel serverLevel = (ServerLevel)this.level();
+ int i = 1;
+- BlockPos blockPos = pos.above();
+- if (this.random.nextFloat() < 0.25F && this.level().isRainingAt(blockPos)) {
+- i++;
++ BlockPos blockposition1 = pos.above();
++
++ if (this.rainInfluenced && this.random.nextFloat() < 0.25F && this.level().isRainingAt(blockposition1)) { // CraftBukkit
++ ++i;
+ }
+
+- if (this.random.nextFloat() < 0.5F && !this.level().canSeeSky(blockPos)) {
+- i--;
++ if (this.skyInfluenced && this.random.nextFloat() < 0.5F && !this.level().canSeeSky(blockposition1)) { // CraftBukkit
++ --i;
+ }
+
+ if (this.nibble > 0) {
+@@ -297,7 +324,11 @@
+ if (this.nibble <= 0) {
+ this.timeUntilLured = 0;
+ this.timeUntilHooked = 0;
+- this.getEntityData().set(DATA_BITING, false);
++ 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 if (this.timeUntilHooked > 0) {
+ this.timeUntilHooked -= i;
+@@ -314,6 +361,16 @@
+ if (this.random.nextFloat() < 0.15F) {
+ serverLevel.sendParticles(ParticleTypes.BUBBLE, d, d1 - 0.1F, d2, 1, (double)sin, 0.1, (double)cos, 0.0);
+ }
++ } 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;
+
+ float f1 = sin * 0.04F;
+ float f2 = cos * 0.04F;
+@@ -371,9 +400,17 @@
+ }
+ }
+
+- if (this.timeUntilLured <= 0) {
+- this.fishAngle = Mth.nextFloat(this.random, 0.0F, 360.0F);
+- this.timeUntilHooked = Mth.nextInt(this.random, 20, 80);
++ if (this.timeUntilLured <= 0) {
++ // 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 {
++ // 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
+ }
+ } else {
+ this.timeUntilLured = Mth.nextInt(this.random, 100, 600);
+@@ -442,6 +477,14 @@
+ if (!this.level().isClientSide && playerOwner != null && !this.shouldStopFishing(playerOwner)) {
+ 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)playerOwner, stack, this, Collections.emptyList());
+ this.level().broadcastEntityEvent(this, (byte)31);
+@@ -457,31 +494,61 @@
+ List<ItemStack> randomItems = lootTable.getRandomItems(lootParams);
+ CriteriaTriggers.FISHING_ROD_HOOKED.trigger((ServerPlayer)playerOwner, stack, this, randomItems);
+
+- for (ItemStack itemStack : randomItems) {
+- ItemEntity itemEntity = new ItemEntity(this.level(), this.getX(), this.getY(), this.getZ(), itemStack);
+- double d = playerOwner.getX() - this.getX();
+- double d1 = playerOwner.getY() - this.getY();
+- double d2 = playerOwner.getZ() - this.getZ();
+- double d3 = 0.1;
+- itemEntity.setDeltaMovement(d * 0.1, d1 * 0.1 + Math.sqrt(Math.sqrt(d * d + d1 * d1 + d2 * d2)) * 0.08, d2 * 0.1);
+- this.level().addFreshEntity(itemEntity);
+- playerOwner.level()
+- .addFreshEntity(
+- new ExperienceOrb(
+- playerOwner.level(), playerOwner.getX(), playerOwner.getY() + 0.5, playerOwner.getZ() + 0.5, this.random.nextInt(6) + 1
+- )
+- );
+- if (itemStack.is(ItemTags.FISHES)) {
+- playerOwner.awardStat(Stats.FISH_CAUGHT, 1);
++ CriteriaTriggers.FISHING_ROD_HOOKED.trigger((ServerPlayer) entityhuman, stack, this, list);
++ Iterator iterator = list.iterator();
++
++ while (iterator.hasNext()) {
++ ItemStack itemstack1 = (ItemStack) iterator.next();
++ 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;
++
++ 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)) {
++ entityhuman.awardStat(Stats.FISH_CAUGHT, 1);
++ }
+ }
+
+ i = 1;
+ }
+
+ 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-vineflower-stripped/net/minecraft/world/entity/projectile/LargeFireball.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/LargeFireball.java.patch
new file mode 100644
index 0000000000..dce2bb426e
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/LargeFireball.java.patch
@@ -0,0 +1,55 @@
+--- a/net/minecraft/world/entity/projectile/LargeFireball.java
++++ b/net/minecraft/world/entity/projectile/LargeFireball.java
+@@ -8,25 +8,37 @@
+ 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);
++ isIncendiary = this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit
+ }
+
+- public LargeFireball(Level level, LivingEntity shooter, double offsetX, double offsetY, double offsetZ, int explosionPower) {
+- super(EntityType.FIREBALL, shooter, offsetX, offsetY, offsetZ, level);
+- this.explosionPower = explosionPower;
++ public LargeFireball(Level level, LivingEntity shooter, double offsetX, double d1, double offsetY, int i) {
++ super(EntityType.FIREBALL, shooter, offsetX, d1, offsetY, level);
++ this.explosionPower = i;
++ isIncendiary = this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit
+ }
+
+ @Override
+ protected void onHit(HitResult result) {
+ super.onHit(result);
+ if (!this.level().isClientSide) {
+- boolean _boolean = this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING);
+- this.level().explode(this, this.getX(), this.getY(), this.getZ(), (float)this.explosionPower, _boolean, Level.ExplosionInteraction.MOB);
++ boolean flag = this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING);
++
++ // 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();
+ }
+ }
+@@ -54,7 +70,8 @@
+ public void readAdditionalSaveData(CompoundTag compound) {
+ super.readAdditionalSaveData(compound);
+ if (compound.contains("ExplosionPower", 99)) {
+- this.explosionPower = compound.getByte("ExplosionPower");
++ // CraftBukkit - set bukkitYield when setting explosionpower
++ bukkitYield = this.explosionPower = compound.getByte("ExplosionPower");
+ }
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/LlamaSpit.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/LlamaSpit.java.patch
new file mode 100644
index 0000000000..5886254a3a
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/LlamaSpit.java.patch
@@ -0,0 +1,23 @@
+--- a/net/minecraft/world/entity/projectile/LlamaSpit.java
++++ b/net/minecraft/world/entity/projectile/LlamaSpit.java
+@@ -31,12 +29,14 @@
+ @Override
+ public void tick() {
+ super.tick();
+- Vec3 deltaMovement = this.getDeltaMovement();
+- HitResult hitResultOnMoveVector = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity);
+- this.onHit(hitResultOnMoveVector);
+- double d = this.getX() + deltaMovement.x;
+- double d1 = this.getY() + deltaMovement.y;
+- double d2 = this.getZ() + deltaMovement.z;
++ Vec3 vec3d = this.getDeltaMovement();
++ HitResult movingobjectposition = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity);
++
++ 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;
+ float f1 = 0.06F;
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/Projectile.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/Projectile.java.patch
new file mode 100644
index 0000000000..30c39d21e6
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/Projectile.java.patch
@@ -0,0 +1,73 @@
+--- a/net/minecraft/world/entity/projectile/Projectile.java
++++ b/net/minecraft/world/entity/projectile/Projectile.java
+@@ -23,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 {
+ @Nullable
+@@ -32,6 +37,10 @@
+ private boolean leftOwner;
+ private boolean hasBeenShot;
+
++ // CraftBukkit start
++ private boolean hitCancelled = false;
++ // CraftBukkit end
++
+ Projectile(EntityType<? extends Projectile> entityType, Level level) {
+ super(entityType, level);
+ }
+@@ -41,6 +50,8 @@
+ this.ownerUUID = owner.getUUID();
+ this.cachedOwner = owner;
+ }
++ this.projectileSource = (owner != null && owner.getBukkitEntity() instanceof ProjectileSource) ? (ProjectileSource) owner.getBukkitEntity() : null; // CraftBukkit
++
+ }
+
+ @Nullable
+@@ -152,25 +175,28 @@
+ this.setDeltaMovement(this.getDeltaMovement().add(deltaMovement.x, shooter.onGround() ? 0.0 : deltaMovement.y, deltaMovement.z));
+ }
+
+- protected void onHit(HitResult result) {
+- HitResult.Type type = result.getType();
+- if (type == HitResult.Type.ENTITY) {
+- this.onHitEntity((EntityHitResult)result);
+- this.level().gameEvent(GameEvent.PROJECTILE_LAND, result.getLocation(), GameEvent.Context.of(this, null));
+- } else if (type == HitResult.Type.BLOCK) {
+- BlockHitResult blockHitResult = (BlockHitResult)result;
+- this.onHitBlock(blockHitResult);
+- BlockPos blockPos = blockHitResult.getBlockPos();
+- this.level().gameEvent(GameEvent.PROJECTILE_LAND, blockPos, GameEvent.Context.of(this, this.level().getBlockState(blockPos)));
++ // 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
+
+ protected void onHitEntity(EntityHitResult result) {
+ }
+
+ protected void onHitBlock(BlockHitResult result) {
+- BlockState blockState = this.level().getBlockState(result.getBlockPos());
+- blockState.onProjectileHit(this.level(), blockState, result, this);
++ // CraftBukkit start - cancellable hit event
++ if (hitCancelled) {
++ return;
++ }
++ // CraftBukkit end
++ IBlockData iblockdata = this.level().getBlockState(result.getBlockPos());
++
++ iblockdata.onProjectileHit(this.level(), iblockdata, result, this);
+ }
+
+ @Override
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ShulkerBullet.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ShulkerBullet.java.patch
new file mode 100644
index 0000000000..5c3c112607
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ShulkerBullet.java.patch
@@ -0,0 +1,65 @@
+--- a/net/minecraft/world/entity/projectile/ShulkerBullet.java
++++ b/net/minecraft/world/entity/projectile/ShulkerBullet.java
+@@ -58,8 +60,21 @@
+ this.finalTarget = finalTarget;
+ this.currentMoveDirection = Direction.UP;
+ 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
+ public SoundSource getSoundSource() {
+ return SoundSource.HOSTILE;
+@@ -219,9 +235,10 @@
+ );
+ }
+
+- HitResult hitResultOnMoveVector = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity);
+- if (hitResultOnMoveVector.getType() != HitResult.Type.MISS) {
+- this.onHit(hitResultOnMoveVector);
++ HitResult movingobjectposition = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity);
++
++ if (movingobjectposition.getType() != HitResult.EnumMovingObjectType.MISS) {
++ this.preOnHit(movingobjectposition); // CraftBukkit - projectile hit event
+ }
+ }
+
+@@ -287,9 +303,11 @@
+ LivingEntity livingEntity = owner instanceof LivingEntity ? (LivingEntity)owner : null;
+ boolean flag = entity.hurt(this.damageSources().mobProjectile(this, livingEntity), 4.0F);
+ if (flag) {
+- this.doEnchantDamageEffects(livingEntity, entity);
+- if (entity instanceof LivingEntity livingEntity1) {
+- livingEntity1.addEffect(new MobEffectInstance(MobEffects.LEVITATION, 200), MoreObjects.firstNonNull(owner, this));
++ this.doEnchantDamageEffects(entityliving, entity);
++ if (entity instanceof LivingEntity) {
++ LivingEntity entityliving1 = (LivingEntity) entity;
++
++ entityliving1.addEffect(new MobEffectInstance(MobEffects.LEVITATION, 200), (Entity) MoreObjects.firstNonNull(entity1, this), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+ }
+ }
+ }
+@@ -319,6 +338,11 @@
+
+ @Override
+ 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.2, 0.2, 0.2, 0.0);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/SmallFireball.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/SmallFireball.java.patch
new file mode 100644
index 0000000000..ad12c17e8a
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/SmallFireball.java.patch
@@ -0,0 +1,75 @@
+--- a/net/minecraft/world/entity/projectile/SmallFireball.java
++++ b/net/minecraft/world/entity/projectile/SmallFireball.java
+@@ -12,14 +12,20 @@
+ 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 {
+ public SmallFireball(EntityType<? extends SmallFireball> entityType, Level level) {
+ super(entityType, level);
+ }
+
+- public SmallFireball(Level level, LivingEntity shooter, double offsetX, double offsetY, double offsetZ) {
+- super(EntityType.SMALL_FIREBALL, shooter, offsetX, offsetY, offsetZ, 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 x, double y, double z, double offsetX, double offsetY, double offsetZ) {
+@@ -31,14 +38,23 @@
+ super.onHitEntity(result);
+ if (!this.level().isClientSide) {
+ Entity entity = result.getEntity();
+- Entity owner = this.getOwner();
+- int remainingFireTicks = entity.getRemainingFireTicks();
+- entity.setSecondsOnFire(5);
+- if (!entity.hurt(this.damageSources().fireball(this, owner), 5.0F)) {
+- entity.setRemainingFireTicks(remainingFireTicks);
+- } else if (owner instanceof LivingEntity) {
+- this.doEnchantDamageEffects((LivingEntity)owner, entity);
++ Entity entity1 = this.getOwner();
++ int i = entity.getRemainingFireTicks();
++
++ // 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) {
++ this.doEnchantDamageEffects((LivingEntity) entity1, entity);
++ }
++
+ }
+ }
+
+@@ -46,11 +62,13 @@
+ protected void onHitBlock(BlockHitResult result) {
+ super.onHitBlock(result);
+ if (!this.level().isClientSide) {
+- Entity owner = this.getOwner();
+- if (!(owner instanceof Mob) || this.level().getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+- BlockPos blockPos = result.getBlockPos().relative(result.getDirection());
+- if (this.level().isEmptyBlock(blockPos)) {
+- this.level().setBlockAndUpdate(blockPos, BaseFireBlock.getState(this.level(), blockPos));
++ Entity entity = this.getOwner();
++
++ if (isIncendiary) { // CraftBukkit
++ BlockPos blockposition = result.getBlockPos().relative(result.getDirection());
++
++ 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-vineflower-stripped/net/minecraft/world/entity/projectile/SpectralArrow.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/SpectralArrow.java.patch
new file mode 100644
index 0000000000..ac1ab70833
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/SpectralArrow.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/world/entity/projectile/SpectralArrow.java
++++ b/net/minecraft/world/entity/projectile/SpectralArrow.java
+@@ -37,8 +39,9 @@
+ @Override
+ protected void doPostHurtEffects(LivingEntity living) {
+ super.doPostHurtEffects(living);
+- MobEffectInstance mobEffectInstance = new MobEffectInstance(MobEffects.GLOWING, this.duration, 0);
+- living.addEffect(mobEffectInstance, this.getEffectSource());
++ MobEffectInstance mobeffect = new MobEffectInstance(MobEffects.GLOWING, this.duration, 0);
++
++ living.addEffect(mobeffect, this.getEffectSource(), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ARROW); // CraftBukkit
+ }
+
+ @Override
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ThrowableItemProjectile.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ThrowableItemProjectile.java.patch
new file mode 100644
index 0000000000..ac288da8e9
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ThrowableItemProjectile.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/world/entity/projectile/ThrowableItemProjectile.java
++++ b/net/minecraft/world/entity/projectile/ThrowableItemProjectile.java
+@@ -35,9 +35,11 @@
+
+ protected abstract Item getDefaultItem();
+
+- protected ItemStack getItemRaw() {
+- return this.getEntityData().get(DATA_ITEM_STACK);
++ // CraftBukkit start
++ public Item getDefaultItemPublic() {
++ return getDefaultItem();
+ }
++ // CraftBukkit end
+
+ @Override
+ public ItemStack getItem() {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ThrowableProjectile.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ThrowableProjectile.java.patch
new file mode 100644
index 0000000000..21a6b48b1b
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -60,8 +65,8 @@
+ }
+ }
+
+- if (hitResultOnMoveVector.getType() != HitResult.Type.MISS && !flag) {
+- this.onHit(hitResultOnMoveVector);
++ if (movingobjectposition.getType() != HitResult.EnumMovingObjectType.MISS && !flag) {
++ this.preOnHit(movingobjectposition); // CraftBukkit - projectile hit event
+ }
+
+ this.checkInsideBlocks();
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ThrownEgg.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ThrownEgg.java.patch
new file mode 100644
index 0000000000..00e2f8f468
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ThrownEgg.java.patch
@@ -0,0 +1,77 @@
+--- 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 {
+ public ThrownEgg(EntityType<? extends ThrownEgg> entityType, Level level) {
+@@ -54,20 +52,49 @@
+ protected void onHit(HitResult result) {
+ super.onHit(result);
+ if (!this.level().isClientSide) {
+- if (this.random.nextInt(8) == 0) {
+- int i = 1;
++ // CraftBukkit start
++ boolean hatching = this.random.nextInt(8) == 0;
++ if (true) {
++ // CraftBukkit end
++ byte b0 = 1;
++
+ if (this.random.nextInt(32) == 0) {
+ i = 4;
+ }
+
+- for (int i1 = 0; i1 < i; i1++) {
+- Chicken chicken = EntityType.CHICKEN.create(this.level());
+- if (chicken != null) {
+- chicken.setAge(-24000);
+- chicken.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), 0.0F);
+- this.level().addFreshEntity(chicken);
++ // 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) {
++ 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 (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
++ }
++ }
+ }
+
+ this.level().broadcastEntityEvent(this, (byte)3);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch
new file mode 100644
index 0000000000..47973145b3
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ThrownEnderpearl.java.patch
@@ -0,0 +1,85 @@
+--- a/net/minecraft/world/entity/projectile/ThrownEnderpearl.java
++++ b/net/minecraft/world/entity/projectile/ThrownEnderpearl.java
+@@ -16,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 {
+ public ThrownEnderpearl(EntityType<? extends ThrownEnderpearl> entityType, Level level) {
+@@ -55,14 +54,29 @@
+ }
+
+ if (!this.level().isClientSide && !this.isRemoved()) {
+- Entity owner = this.getOwner();
+- if (owner instanceof ServerPlayer serverPlayer) {
+- 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 = EntityType.ENDERMITE.create(this.level());
+- if (endermite != null) {
+- endermite.moveTo(owner.getX(), owner.getY(), owner.getZ(), owner.getYRot(), owner.getXRot());
+- this.level().addFreshEntity(endermite);
++ Entity entity = this.getOwner();
++
++ if (entity instanceof ServerPlayer) {
++ ServerPlayer entityplayer = (ServerPlayer) entity;
++
++ 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());
++
++ PlayerTeleportEvent teleEvent = new PlayerTeleportEvent(player, player.getLocation(), location, PlayerTeleportEvent.TeleportCause.ENDER_PEARL);
++ Bukkit.getPluginManager().callEvent(teleEvent);
++
++ if (!teleEvent.isCancelled() && entityplayer.connection.isAcceptingMessages()) {
++ if (this.random.nextFloat() < 0.05F && this.level().getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) {
++ Endermite entityendermite = (Endermite) EntityType.ENDERMITE.create(this.level());
++
++ if (entityendermite != null) {
++ entityendermite.moveTo(entity.getX(), entity.getY(), entity.getZ(), entity.getYRot(), entity.getXRot());
++ this.level().addFreshEntity(entityendermite, CreatureSpawnEvent.SpawnReason.ENDER_PEARL);
++ }
+ }
+ }
+
+@@ -72,9 +83,14 @@
+ owner.teleportTo(this.getX(), this.getY(), this.getZ());
+ }
+
+- owner.resetFallDistance();
+- owner.hurt(this.damageSources().fall(), 5.0F);
+- this.level().playSound(null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_TELEPORT, SoundSource.PLAYERS);
++ 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 (owner != null) {
+ owner.teleportTo(this.getX(), this.getY(), this.getZ());
+@@ -98,9 +117,10 @@
+ @Nullable
+ @Override
+ public Entity changeDimension(ServerLevel server) {
+- Entity owner = this.getOwner();
+- if (owner != null && owner.level().dimension() != server.dimension()) {
+- this.setOwner(null);
++ Entity entity = this.getOwner();
++
++ if (entity != null && server != null && entity.level().dimension() != server.dimension()) { // CraftBukkit - SPIGOT-6113
++ this.setOwner((Entity) null);
+ }
+
+ return super.changeDimension(server);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java.patch
new file mode 100644
index 0000000000..d3ab8c972a
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java.patch
@@ -0,0 +1,24 @@
+--- a/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java
++++ b/net/minecraft/world/entity/projectile/ThrownExperienceBottle.java
+@@ -38,9 +39,19 @@
+ protected void onHit(HitResult result) {
+ super.onHit(result);
+ 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);
+- ExperienceOrb.award((ServerLevel)this.level(), this.position(), i);
++
++ // 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-vineflower-stripped/net/minecraft/world/entity/projectile/ThrownPotion.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ThrownPotion.java.patch
new file mode 100644
index 0000000000..430cfe9c4f
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ThrownPotion.java.patch
@@ -0,0 +1,185 @@
+--- a/net/minecraft/world/entity/projectile/ThrownPotion.java
++++ b/net/minecraft/world/entity/projectile/ThrownPotion.java
+@@ -28,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 {
+ public static final double SPLASH_RANGE = 4.0;
+@@ -88,11 +108,11 @@
+ boolean flag = potion == Potions.WATER && mobEffects.isEmpty();
+ if (flag) {
+ this.applyWater();
+- } else if (!mobEffects.isEmpty()) {
++ } else if (true || !list.isEmpty()) { // CraftBukkit - Call event even if no effects to apply
+ if (this.isLingering()) {
+- this.makeAreaOfEffectCloud(item, potion);
++ this.makeAreaOfEffectCloud(itemstack, potionregistry, result); // CraftBukkit - Pass MovingObjectPosition
+ } else {
+- this.applySplash(mobEffects, result.getType() == HitResult.Type.ENTITY ? ((EntityHitResult)result).getEntity() : null);
++ this.applySplash(list, result.getType() == HitResult.EnumMovingObjectType.ENTITY ? ((EntityHitResult) result).getEntity() : null, result); // CraftBukkit - Pass MovingObjectPosition
+ }
+ }
+
+@@ -123,11 +154,10 @@
+ }
+ }
+
+- private void applySplash(List<MobEffectInstance> effectInstances, @Nullable Entity target) {
+- AABB aABB = this.getBoundingBox().inflate(4.0, 2.0, 4.0);
+- List<LivingEntity> entitiesOfClass = this.level().getEntitiesOfClass(LivingEntity.class, aABB);
+- if (!entitiesOfClass.isEmpty()) {
+- Entity effectSource = this.getEffectSource();
++ 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
+
+ for (LivingEntity livingEntity : entitiesOfClass) {
+ if (livingEntity.isAffectedByPotions()) {
+@@ -140,31 +178,62 @@
+ d1 = 1.0 - Math.sqrt(d) / 4.0;
+ }
+
+- for (MobEffectInstance mobEffectInstance : effectInstances) {
+- MobEffect effect = mobEffectInstance.getEffect();
+- if (effect.isInstantenous()) {
+- effect.applyInstantenousEffect(this, this.getOwner(), livingEntity, mobEffectInstance.getAmplifier(), d1);
+- } else {
+- int i = mobEffectInstance.mapDuration(i1 -> (int)(d1 * (double)i1 + 0.5));
+- MobEffectInstance mobEffectInstance1 = new MobEffectInstance(
+- effect, i, mobEffectInstance.getAmplifier(), mobEffectInstance.isAmbient(), mobEffectInstance.isVisible()
+- );
+- if (!mobEffectInstance1.endsWithin(20)) {
+- livingEntity.addEffect(mobEffectInstance1, effectSource);
+- }
+- }
++ // CraftBukkit start
++ affected.put((LivingEntity) entityliving.getBukkitEntity(), d1);
++ }
++ }
++ }
++ }
++
++ org.bukkit.event.entity.PotionSplashEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callPotionSplashEvent(this, position, affected);
++ if (!event.isCancelled() && list != null && !list.isEmpty()) { // do not process effects if there are no effects to process
++ Entity entity1 = this.getEffectSource();
++ for (LivingEntity victim : event.getAffectedEntities()) {
++ if (!(victim instanceof CraftLivingEntity)) {
++ continue;
++ }
++
++ net.minecraft.world.entity.LivingEntity entityliving = ((CraftLivingEntity) victim).getHandle();
++ double d1 = event.getIntensity(victim);
++ // CraftBukkit end
++
++ 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 stack, Potion potion) {
+- AreaEffectCloud areaEffectCloud = new AreaEffectCloud(this.level(), this.getX(), this.getY(), this.getZ());
+- Entity owner = this.getOwner();
+- if (owner instanceof LivingEntity) {
+- areaEffectCloud.setOwner((LivingEntity)owner);
++ 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 net.minecraft.world.entity.LivingEntity) {
++ entityareaeffectcloud.setOwner((net.minecraft.world.entity.LivingEntity) entity);
+ }
+
+ areaEffectCloud.setRadius(3.0F);
+@@ -182,7 +256,14 @@
+ areaEffectCloud.setFixedColor(tag.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() {
+@@ -190,15 +271,28 @@
+ }
+
+ private void dowseFire(BlockPos pos) {
+- BlockState blockState = this.level().getBlockState(pos);
+- if (blockState.is(BlockTags.FIRE)) {
+- this.level().destroyBlock(pos, false, this);
+- } else if (AbstractCandleBlock.isLit(blockState)) {
+- AbstractCandleBlock.extinguish(null, blockState, this.level(), pos);
+- } else if (CampfireBlock.isLitCampfire(blockState)) {
+- this.level().levelEvent(null, 1009, pos, 0);
+- CampfireBlock.dowse(this.getOwner(), this.level(), pos, blockState);
+- this.level().setBlockAndUpdate(pos, blockState.setValue(CampfireBlock.LIT, Boolean.valueOf(false)));
++ IBlockData iblockdata = this.level().getBlockState(pos);
++
++ 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-vineflower-stripped/net/minecraft/world/entity/projectile/ThrownTrident.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ThrownTrident.java.patch
new file mode 100644
index 0000000000..6fc1b65499
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/ThrownTrident.java.patch
@@ -0,0 +1,27 @@
+--- a/net/minecraft/world/entity/projectile/ThrownTrident.java
++++ b/net/minecraft/world/entity/projectile/ThrownTrident.java
+@@ -132,14 +145,16 @@
+ this.setDeltaMovement(this.getDeltaMovement().multiply(-0.01, -0.1, -0.01));
+ float f1 = 1.0F;
+ if (this.level() instanceof ServerLevel && this.level().isThundering() && this.isChanneling()) {
+- BlockPos blockPos = entity.blockPosition();
+- if (this.level().canSeeSky(blockPos)) {
+- LightningBolt lightningBolt = EntityType.LIGHTNING_BOLT.create(this.level());
+- if (lightningBolt != null) {
+- lightningBolt.moveTo(Vec3.atBottomCenterOf(blockPos));
+- lightningBolt.setCause(owner instanceof ServerPlayer ? (ServerPlayer)owner : null);
+- this.level().addFreshEntity(lightningBolt);
+- soundEvent = SoundEvents.TRIDENT_THUNDER;
++ BlockPos blockposition = entity.blockPosition();
++
++ if (this.level().canSeeSky(blockposition)) {
++ LightningBolt entitylightning = (LightningBolt) EntityType.LIGHTNING_BOLT.create(this.level());
++
++ 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-vineflower-stripped/net/minecraft/world/entity/projectile/WindCharge.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/WindCharge.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/WindCharge.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/WitherSkull.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/WitherSkull.java.patch
new file mode 100644
index 0000000000..1037b4002e
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/projectile/WitherSkull.java.patch
@@ -0,0 +1,49 @@
+--- 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 {
+ private static final EntityDataAccessor<Boolean> DATA_DANGEROUS = SynchedEntityData.defineId(WitherSkull.class, EntityDataSerializers.BOOLEAN);
+@@ -61,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 {
+@@ -76,8 +84,8 @@
+ i = 40;
+ }
+
+- if (i > 0) {
+- livingEntityx.addEffect(new MobEffectInstance(MobEffects.WITHER, 20 * i, 1), this.getEffectSource());
++ if (b0 > 0) {
++ entityliving.addEffect(new MobEffectInstance(MobEffects.WITHER, 20 * b0, 1), this.getEffectSource(), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
+ }
+ }
+ }
+@@ -87,7 +96,15 @@
+ protected void onHit(HitResult result) {
+ super.onHit(result);
+ 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-vineflower-stripped/net/minecraft/world/entity/raid/Raid.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/raid/Raid.java.patch
new file mode 100644
index 0000000000..8cd48b035c
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/raid/Raid.java.patch
@@ -0,0 +1,199 @@
+--- a/net/minecraft/world/entity/raid/Raid.java
++++ b/net/minecraft/world/entity/raid/Raid.java
+@@ -160,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;
+ }
+@@ -243,6 +278,7 @@
+ boolean flag = this.active;
+ 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;
+ }
+@@ -262,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;
+ }
+@@ -343,7 +380,8 @@
+ i1++;
+ }
+
+- if (i1 > 3) {
++ if (j > 3) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.callRaidStopEvent(this, org.bukkit.event.raid.RaidStopEvent.Reason.UNSPAWNABLE); // CraftBukkit
+ this.stop();
+ break;
+ }
+@@ -355,21 +394,26 @@
+ } else {
+ this.status = Raid.RaidStatus.VICTORY;
+
+- for (UUID uUID : this.heroesOfTheVillage) {
+- Entity entity = this.level.getEntity(uUID);
++ 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);
++
+ if (entity instanceof LivingEntity) {
+ LivingEntity livingEntity = (LivingEntity)entity;
+ if (!entity.isSpectator()) {
+- livingEntity.addEffect(
+- new MobEffectInstance(MobEffects.HERO_OF_THE_VILLAGE, 48000, this.badOmenLevel - 1, false, false, true)
+- );
+- if (livingEntity instanceof ServerPlayer serverPlayer) {
+- serverPlayer.awardStat(Stats.RAID_WIN);
+- CriteriaTriggers.RAID_WIN.trigger(serverPlayer);
++ entityliving.addEffect(new MobEffectInstance(MobEffects.HERO_OF_THE_VILLAGE, 48000, this.badOmenLevel - 1, false, false, true));
++ if (entityliving instanceof ServerPlayer) {
++ ServerPlayer entityplayer = (ServerPlayer) entityliving;
++
++ entityplayer.awardStat(Stats.RAID_WIN);
++ CriteriaTriggers.RAID_WIN.trigger(entityplayer);
++ winners.add(entityplayer.getBukkitEntity()); // CraftBukkit
+ }
+ }
+ }
+ }
++ org.bukkit.craftbukkit.event.CraftEventFactory.callRaidFinishEvent(this, winners); // CraftBukkit
+ }
+ }
+
+@@ -377,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;
+ }
+@@ -492,10 +559,15 @@
+ DifficultyInstance currentDifficultyAt = this.level.getCurrentDifficultyAt(pos);
+ boolean shouldSpawnBonusGroup = this.shouldSpawnBonusGroup();
+
+- for (Raid.RaiderType raiderType : Raid.RaiderType.VALUES) {
+- int i1 = this.getDefaultNumSpawns(raiderType, i, shouldSpawnBonusGroup)
+- + this.getPotentialBonusSpawns(raiderType, this.random, i, currentDifficultyAt, shouldSpawnBonusGroup);
+- int i2 = 0;
++ // CraftBukkit start
++ Raider leader = null;
++ List<Raider> raiders = new java.util.ArrayList<>();
++ // CraftBukkit end
++ while (k < j) {
++ Raid.RaiderType raid_wave = araid_wave[k];
++ int l = this.getDefaultNumSpawns(raid_wave, i, flag1) + this.getPotentialBonusSpawns(raid_wave, this.random, i, difficultydamagescaler, flag1);
++ int i1 = 0;
++ int j1 = 0;
+
+ for (int i3 = 0; i3 < i1; i3++) {
+ Raider raider = raiderType.entityType.create(this.level);
+@@ -503,22 +573,36 @@
+ break;
+ }
+
+- if (!flag && raider.canBeLeader()) {
+- raider.setPatrolLeader(true);
+- this.setLeader(i, raider);
+- flag = true;
+- }
++ if (entityraider != null) {
++ if (!flag && entityraider.canBeLeader()) {
++ entityraider.setPatrolLeader(true);
++ this.setLeader(i, entityraider);
++ flag = true;
++ leader = entityraider; // CraftBukkit
++ }
+
+- this.joinRaid(i, raider, pos, false);
+- if (raiderType.entityType == EntityType.RAVAGER) {
+- Raider raider1 = null;
+- if (i == this.getNumGroups(Difficulty.NORMAL)) {
+- raider1 = EntityType.PILLAGER.create(this.level);
+- } else if (i >= this.getNumGroups(Difficulty.HARD)) {
+- if (i2 == 0) {
+- raider1 = EntityType.EVOKER.create(this.level);
+- } else {
+- raider1 = EntityType.VINDICATOR.create(this.level);
++ 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)) {
++ entityraider1 = (Raider) EntityType.PILLAGER.create(this.level);
++ } else if (i >= this.getNumGroups(Difficulty.HARD)) {
++ if (i1 == 0) {
++ entityraider1 = (Raider) EntityType.EVOKER.create(this.level);
++ } else {
++ entityraider1 = (Raider) EntityType.VINDICATOR.create(this.level);
++ }
++ }
++
++ ++i1;
++ if (entityraider1 != null) {
++ this.joinRaid(i, entityraider1, pos, false);
++ entityraider1.moveTo(pos, 0.0F, 0.0F);
++ entityraider1.startRiding(entityraider);
++ raiders.add(entityraider); // CraftBukkit
++ }
+ }
+ }
+
+@@ -536,6 +619,7 @@
+ this.groupsSpawned++;
+ this.updateBossbar();
+ this.setDirty();
++ org.bukkit.craftbukkit.event.CraftEventFactory.callRaidSpawnWaveEvent(this, leader, raiders); // CraftBukkit
+ }
+
+ public void joinRaid(int wave, Raider raider, @Nullable BlockPos pos, boolean isRecruited) {
+@@ -550,7 +635,7 @@
+ raider.finalizeSpawn(this.level, this.level.getCurrentDifficultyAt(pos), MobSpawnType.EVENT, null, null);
+ raider.applyRaidBuffs(wave, false);
+ raider.setOnGround(true);
+- this.level.addFreshEntityWithPassengers(raider);
++ this.level.addFreshEntityWithPassengers(raider, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.RAID); // CraftBukkit
+ }
+ }
+ }
+@@ -800,11 +885,11 @@
+ this.heroesOfTheVillage.add(player.getUUID());
+ }
+
+- static enum RaidStatus {
+- ONGOING,
+- VICTORY,
+- LOSS,
+- STOPPED;
++ // 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 final Raid.RaidStatus[] VALUES = values();
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/raid/Raider.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/raid/Raider.java.patch
new file mode 100644
index 0000000000..01720c3ef4
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/raid/Raider.java.patch
@@ -0,0 +1,63 @@
+--- a/net/minecraft/world/entity/raid/Raider.java
++++ b/net/minecraft/world/entity/raid/Raider.java
+@@ -154,7 +165,7 @@
+ i = Mth.clamp(i, 0, 4);
+ MobEffectInstance mobEffectInstance = new MobEffectInstance(MobEffects.BAD_OMEN, 120000, i, false, false, true);
+ if (!this.level().getGameRules().getBoolean(GameRules.RULE_DISABLE_RAIDS)) {
+- player.addEffect(mobEffectInstance);
++ entityhuman.addEffect(mobeffect1, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.PATROL_CAPTAIN); // CraftBukkit
+ }
+ }
+ }
+@@ -468,16 +511,17 @@
+ return this.raider.hasActiveRaid() && !this.raider.getCurrentRaid().isOver();
+ }
+
+- private boolean hasSuitablePoi() {
+- ServerLevel serverLevel = (ServerLevel)this.raider.level();
+- BlockPos blockPos = this.raider.blockPosition();
+- Optional<BlockPos> random = serverLevel.getPoiManager()
+- .getRandom(holder -> holder.is(PoiTypes.HOME), this::hasNotVisited, PoiManager.Occupancy.ANY, blockPos, 48, this.raider.random);
+- if (random.isEmpty()) {
+- return false;
+- } else {
+- this.poiPos = random.get().immutable();
+- return true;
++ @Override
++ public void start() {
++ super.start();
++ this.mob.getNavigation().stop();
++ List<Raider> list = this.mob.level().getNearbyEntities(Raider.class, this.shoutTargeting, this.mob, this.mob.getBoundingBox().inflate(8.0D, 8.0D, 8.0D));
++ Iterator iterator = list.iterator();
++
++ while (iterator.hasNext()) {
++ Raider entityraider = (Raider) iterator.next();
++
++ entityraider.setTarget(this.mob.getTarget(), org.bukkit.event.entity.EntityTargetEvent.TargetReason.FOLLOW_LEADER, true); // CraftBukkit
+ }
+ }
+
+@@ -491,8 +528,21 @@
+
+ @Override
+ public void stop() {
+- if (this.poiPos.closerToCenterThan(this.raider.position(), (double)this.distanceToPoi)) {
+- this.visited.add(this.poiPos);
++ super.stop();
++ LivingEntity entityliving = this.mob.getTarget();
++
++ if (entityliving != null) {
++ List<Raider> list = this.mob.level().getNearbyEntities(Raider.class, this.shoutTargeting, this.mob, this.mob.getBoundingBox().inflate(8.0D, 8.0D, 8.0D));
++ Iterator iterator = list.iterator();
++
++ while (iterator.hasNext()) {
++ Raider entityraider = (Raider) iterator.next();
++
++ 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-vineflower-stripped/net/minecraft/world/entity/raid/Raids.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/raid/Raids.java.patch
new file mode 100644
index 0000000000..8c6e6b1a8e
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/raid/Raids.java.patch
@@ -0,0 +1,36 @@
+--- a/net/minecraft/world/entity/raid/Raids.java
++++ b/net/minecraft/world/entity/raid/Raids.java
+@@ -118,19 +121,32 @@
+ Raid raid = this.getOrCreateRaid(serverPlayer.serverLevel(), blockPos1);
+ 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) {
++ // 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()) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/vehicle/AbstractMinecart.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/vehicle/AbstractMinecart.java.patch
new file mode 100644
index 0000000000..9ba7ab24fd
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/vehicle/AbstractMinecart.java.patch
@@ -0,0 +1,218 @@
+--- a/net/minecraft/world/entity/vehicle/AbstractMinecart.java
++++ b/net/minecraft/world/entity/vehicle/AbstractMinecart.java
+@@ -46,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;
+ private static final float PASSENGER_ATTACHMENT_Y = 0.1875F;
+@@ -86,6 +96,17 @@
+ map.put(RailShape.NORTH_EAST, Pair.of(normal2, normal1));
+ });
+
++ // 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.blocksBuilding = true;
+@@ -230,6 +286,14 @@
+
+ @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);
+ }
+@@ -239,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);
+@@ -291,17 +361,48 @@
+ }
+
+ this.setRot(this.getYRot(), this.getXRot());
+- if (this.getMinecartType() == AbstractMinecart.Type.RIDEABLE && this.getDeltaMovement().horizontalDistanceSqr() > 0.01) {
+- List<Entity> entities = this.level().getEntities(this, this.getBoundingBox().inflate(0.2F, 0.0, 0.2F), EntitySelector.pushableBy(this));
+- if (!entities.isEmpty()) {
+- for (Entity entity : entities) {
+- if (!(entity instanceof Player)
+- && !(entity instanceof IronGolem)
+- && !(entity instanceof AbstractMinecart)
+- && !this.isVehicle()
+- && !entity.isPassenger()) {
++ // 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()) {
++ Iterator iterator = list.iterator();
++
++ while (iterator.hasNext()) {
++ 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);
+ }
+ }
+@@ -309,6 +414,14 @@
+ } else {
+ for (Entity entity1 : this.level().getEntities(this, this.getBoundingBox().inflate(0.2F, 0.0, 0.2F))) {
+ 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);
+ }
+ }
+@@ -325,7 +438,7 @@
+ }
+
+ protected double getMaxSpeed() {
+- return (this.isInWater() ? 4.0 : 8.0) / 20.0;
++ return (this.isInWater() ? this.maxSpeed / 2.0D: this.maxSpeed); // CraftBukkit
+ }
+
+ public void activateMinecart(int x, int y, int z, boolean powered) {
+@@ -336,12 +449,16 @@
+ Vec3 deltaMovement = this.getDeltaMovement();
+ this.setDeltaMovement(Mth.clamp(deltaMovement.x, -maxSpeed, maxSpeed), deltaMovement.y, Mth.clamp(deltaMovement.z, -maxSpeed, maxSpeed));
+ if (this.onGround()) {
+- this.setDeltaMovement(this.getDeltaMovement().scale(0.5));
++ // 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.95));
++ // 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
+ }
+ }
+
+@@ -514,9 +654,10 @@
+ }
+
+ protected void applyNaturalSlowdown() {
+- double d = this.isVehicle() ? 0.997 : 0.96;
+- Vec3 deltaMovement = this.getDeltaMovement();
+- Vec3 var4 = deltaMovement.multiply(d, 0.0, d);
++ double d0 = this.isVehicle() || !this.slowWhenEmpty ? 0.997D : 0.96D; // CraftBukkit - add !this.slowWhenEmpty
++ Vec3 vec3d = this.getDeltaMovement();
++
++ vec3d = vec3d.multiply(d0, 0.0D, d0);
+ if (this.isInWater()) {
+ var4 = var4.scale(0.95F);
+ }
+@@ -641,7 +793,15 @@
+ if (!this.level().isClientSide) {
+ if (!entity.noPhysics && !this.noPhysics) {
+ if (!this.hasPassenger(entity)) {
+- double d = entity.getX() - this.getX();
++ // 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 = d * d + d1 * d1;
+ if (d2 >= 1.0E-4F) {
+@@ -803,4 +975,26 @@
+ HOPPER,
+ COMMAND_BLOCK;
+ }
++
++ // 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-vineflower-stripped/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java.patch
new file mode 100644
index 0000000000..31135f280a
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java.patch
@@ -0,0 +1,74 @@
+--- a/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java
++++ b/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java
+@@ -16,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 {
+ private NonNullList<ItemStack> itemStacks = NonNullList.withSize(36, ItemStack.EMPTY);
+@@ -23,12 +32,55 @@
+ private ResourceLocation lootTable;
+ private long lootTableSeed;
+
++ // CraftBukkit start
++ 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);
++ }
++
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ public InventoryHolder getOwner() {
++ org.bukkit.entity.Entity cart = getBukkitEntity();
++ if(cart instanceof InventoryHolder) return (InventoryHolder) cart;
++ return null;
++ }
++
++ @Override
++ public int getMaxStackSize() {
++ return maxStack;
++ }
++
++ public void setMaxStackSize(int size) {
++ maxStack = size;
++ }
++
++ @Override
++ public Location getLocation() {
++ return getBukkitEntity().getLocation();
++ }
++ // 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 y, double z, Level level) {
+- super(entityType, level, x, y, z);
++ 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
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/vehicle/Boat.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/vehicle/Boat.java.patch
new file mode 100644
index 0000000000..4fb52855db
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/vehicle/Boat.java.patch
@@ -0,0 +1,114 @@
+--- a/net/minecraft/world/entity/vehicle/Boat.java
++++ b/net/minecraft/world/entity/vehicle/Boat.java
+@@ -52,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);
+ private static final EntityDataAccessor<Boolean> DATA_ID_PADDLE_LEFT = SynchedEntityData.defineId(Boat.class, EntityDataSerializers.BOOLEAN);
+@@ -88,6 +101,14 @@
+ private float bubbleAngle;
+ private float bubbleAngleO;
+
++ // 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.blocksBuilding = true;
+@@ -198,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);
+ }
+ }
+@@ -272,6 +325,7 @@
+ return this.getDirection().getClockWise();
+ }
+
++ private Location lastLocation; // CraftBukkit
+ @Override
+ public void tick() {
+ this.oldStatus = this.status;
+@@ -312,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++) {
+@@ -782,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)) {
+ for (int i = 0; i < 3; i++) {
+@@ -793,6 +885,7 @@
+ }
+ }
+ }
++ } // CraftBukkit end
+ }
+
+ this.resetFallDistance();
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/vehicle/ChestBoat.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/vehicle/ChestBoat.java.patch
new file mode 100644
index 0000000000..9d7239e776
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -22,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 {
+ private static final int CONTAINER_SIZE = 27;
+@@ -212,4 +245,51 @@
+ public void stopOpen(Player player) {
+ this.level().gameEvent(GameEvent.CONTAINER_CLOSE, this.position(), GameEvent.Context.of(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-vineflower-stripped/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/vehicle/MinecartCommandBlock.java.patch
new file mode 100644
index 0000000000..b008b28e5d
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -146,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-vineflower-stripped/net/minecraft/world/entity/vehicle/MinecartTNT.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/vehicle/MinecartTNT.java.patch
new file mode 100644
index 0000000000..ddd764dafa
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/vehicle/MinecartTNT.java.patch
@@ -0,0 +1,40 @@
+--- a/net/minecraft/world/entity/vehicle/MinecartTNT.java
++++ b/net/minecraft/world/entity/vehicle/MinecartTNT.java
+@@ -19,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 {
+ private static final byte EVENT_PRIME = 10;
+@@ -99,18 +118,15 @@
+ squareRoot = 5.0;
+ }
+
+- this.level()
+- .explode(
+- this,
+- damageSource,
+- null,
+- this.getX(),
+- this.getY(),
+- this.getZ(),
+- (float)(4.0 + this.random.nextDouble() * 1.5 * squareRoot),
+- 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-vineflower-stripped/net/minecraft/world/entity/vehicle/VehicleEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/vehicle/VehicleEntity.java.patch
new file mode 100644
index 0000000000..3300c9d219
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/entity/vehicle/VehicleEntity.java.patch
@@ -0,0 +1,72 @@
+--- 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);
+ protected static final EntityDataAccessor<Integer> DATA_ID_HURTDIR = SynchedEntityData.defineId(VehicleEntity.class, EntityDataSerializers.INT);
+@@ -40,9 +35,54 @@
+ this.discard();
+ }
+ } else {
+- this.destroy(source);
+- }
++ // 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();
++ this.setDamage(this.getDamage() + amount * 10.0F);
++ this.gameEvent(GameEvent.ENTITY_DAMAGE, source.getEntity());
++ boolean flag = source.getEntity() instanceof Player && ((Player) source.getEntity()).getAbilities().instabuild;
++
++ if ((flag || this.getDamage() <= 40.0F) && !this.shouldSourceDestroy(source)) {
++ 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 {
++ // 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;
++ }
++ } else {
+ return true;
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/food/FoodData.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/food/FoodData.java.patch
new file mode 100644
index 0000000000..7c9248adc9
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/food/FoodData.java.patch
@@ -0,0 +1,109 @@
+--- a/net/minecraft/world/food/FoodData.java
++++ b/net/minecraft/world/food/FoodData.java
+@@ -12,11 +15,22 @@
+ private float saturationLevel;
+ 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() {
+- this.saturationLevel = 5.0F;
++ public FoodData() { throw new AssertionError("Whoopsie, we missed the bukkit."); } // CraftBukkit start - throw an error
++
++ // CraftBukkit start - added EntityHuman constructor
++ public FoodData(Player entityhuman) {
++ org.apache.commons.lang.Validate.notNull(entityhuman);
++ this.entityhuman = entityhuman;
+ }
++ // CraftBukkit end
+
+ public void eat(int foodLevelModifier, float saturationLevelModifier) {
+ this.foodLevel = Math.min(foodLevelModifier + this.foodLevel, 20);
+@@ -25,8 +39,18 @@
+
+ public void eat(Item item, ItemStack stack) {
+ if (item.isEdible()) {
+- FoodProperties foodProperties = item.getFoodProperties();
+- this.eat(foodProperties.getNutrition(), foodProperties.getSaturationModifier());
++ FoodProperties foodinfo = item.getFoodProperties();
++ // CraftBukkit start
++ int oldFoodLevel = foodLevel;
++
++ 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
+ }
+ }
+
+@@ -37,31 +63,43 @@
+ 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
+ }
+ }
+
+- boolean _boolean = player.level().getGameRules().getBoolean(GameRules.RULE_NATURAL_REGENERATION);
+- if (_boolean && this.saturationLevel > 0.0F && player.isHurt() && this.foodLevel >= 20) {
+- this.tickTimer++;
+- if (this.tickTimer >= 10) {
+- float min = Math.min(this.saturationLevel, 6.0F);
+- player.heal(min / 6.0F);
+- this.addExhaustion(min);
++ boolean flag = player.level().getGameRules().getBoolean(GameRules.RULE_NATURAL_REGENERATION);
++
++ if (flag && this.saturationLevel > 0.0F && player.isHurt() && this.foodLevel >= 20) {
++ ++this.tickTimer;
++ if (this.tickTimer >= this.saturatedRegenRate) { // CraftBukkit
++ float f = Math.min(this.saturationLevel, 6.0F);
++
++ 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 (_boolean && this.foodLevel >= 18 && player.isHurt()) {
+- this.tickTimer++;
+- if (this.tickTimer >= 80) {
+- player.heal(1.0F);
+- this.addExhaustion(6.0F);
++ } else if (flag && this.foodLevel >= 18 && player.isHurt()) {
++ ++this.tickTimer;
++ 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) {
++ ++this.tickTimer;
++ 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-vineflower-stripped/net/minecraft/world/inventory/AbstractContainerMenu.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/AbstractContainerMenu.java.patch
new file mode 100644
index 0000000000..d577eee6c2
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/AbstractContainerMenu.java.patch
@@ -0,0 +1,304 @@
+--- 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();
+ public static final int SLOT_CLICKED_OUTSIDE = -999;
+@@ -62,6 +77,27 @@
+ private ContainerSynchronizer synchronizer;
+ private boolean suppressRemoteUpdates;
+
++ // 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.menuType = menuType;
+ this.containerId = containerId;
+@@ -153,6 +199,15 @@
+ }
+ }
+
++ // CraftBukkit start
++ public void broadcastCarriedItem() {
++ this.remoteCarried = this.getCarried().copy();
++ if (this.synchronizer != null) {
++ this.synchronizer.sendCarriedChange(this, this.remoteCarried);
++ }
++ }
++ // CraftBukkit end
++
+ public void removeSlotListener(ContainerListener listener) {
+ this.containerListeners.remove(listener);
+ }
+@@ -328,8 +424,8 @@
+ }
+ } else if (this.quickcraftStatus == 2) {
+ if (!this.quickcraftSlots.isEmpty()) {
+- if (this.quickcraftSlots.size() == 1) {
+- int i1 = this.quickcraftSlots.iterator().next().index;
++ 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(i1, this.quickcraftType, ClickType.PICKUP, player);
+ return;
+@@ -343,23 +440,59 @@
+
+ int count = this.getCarried().getCount();
+
+- for (Slot slot1 : this.quickcraftSlots) {
+- ItemStack carried1 = this.getCarried();
+- if (slot1 != null
+- && canItemQuickReplace(slot1, carried1, true)
+- && slot1.mayPlace(carried1)
+- && (this.quickcraftType == 2 || carried1.getCount() >= this.quickcraftSlots.size())
+- && this.canDragTo(slot1)) {
+- int i2 = slot1.hasItem() ? slot1.getItem().getCount() : 0;
+- int min = Math.min(itemStack.getMaxStackSize(), slot1.getMaxStackSize(itemStack));
+- int min1 = Math.min(getQuickCraftPlaceCount(this.quickcraftSlots, this.quickcraftType, itemStack) + i2, min);
+- count -= min1 - i2;
+- slot1.setByPlayer(itemStack.copyWithCount(min1));
++ 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();
++
++ if (slot1 != null && canItemQuickReplace(slot1, itemstack2, true) && slot1.mayPlace(itemstack2) && (this.quickcraftType == 2 || itemstack2.getCount() >= this.quickcraftSlots.size()) && this.canDragTo(slot1)) {
++ int j1 = slot1.hasItem() ? slot1.getItem().getCount() : 0;
++ int k1 = Math.min(itemstack1.getMaxStackSize(), slot1.getMaxStackSize(itemstack1));
++ int l1 = Math.min(getQuickCraftPlaceCount(this.quickcraftSlots, this.quickcraftType, itemstack1) + j1, k1);
++
++ l -= l1 - j1;
++ // slot1.setByPlayer(itemstack1.copyWithCount(l1));
++ draggedSlots.put(slot1.index, itemstack1.copyWithCount(l1)); // CraftBukkit - Put in map instead of setting
+ }
+ }
+
+- itemStack.setCount(count);
+- this.setCarried(itemStack);
++ // 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();
+@@ -368,15 +501,23 @@
+ }
+ } else if (this.quickcraftStatus != 0) {
+ this.resetQuickCraft();
+- } else if ((clickType == ClickType.PICKUP || clickType == ClickType.QUICK_MOVE) && (button == 0 || button == 1)) {
+- ClickAction clickAction = button == 0 ? ClickAction.PRIMARY : ClickAction.SECONDARY;
+- if (slotId == -999) {
+- if (!this.getCarried().isEmpty()) {
+- if (clickAction == ClickAction.PRIMARY) {
+- player.drop(this.getCarried(), true);
+- this.setCarried(ItemStack.EMPTY);
+- } else {
+- player.drop(this.getCarried().split(1), true);
++ } else {
++ int i2;
++
++ if ((clickType == InventoryClickType.PICKUP || clickType == InventoryClickType.QUICK_MOVE) && (button == 0 || button == 1)) {
++ ClickAction clickaction = button == 0 ? ClickAction.PRIMARY : ClickAction.SECONDARY;
++
++ if (slotId == -999) {
++ if (!this.getCarried().isEmpty()) {
++ if (clickaction == ClickAction.PRIMARY) {
++ // CraftBukkit start
++ ItemStack carried = this.getCarried();
++ this.setCarried(ItemStack.EMPTY);
++ player.drop(carried, true);
++ // CraftBukkit start
++ } else {
++ player.drop(this.getCarried().split(1), true);
++ }
+ }
+ }
+ } else if (clickType == ClickType.QUICK_MOVE) {
+@@ -435,37 +576,56 @@
+ }
+ }
+
+- slot.setChanged();
+- }
+- } else if (clickType == ClickType.SWAP && (button >= 0 && button < 9 || button == 40)) {
+- ItemStack item = inventory.getItem(button);
+- Slot slot = this.slots.get(slotId);
+- ItemStack carried = slot.getItem();
+- if (!item.isEmpty() || !carried.isEmpty()) {
+- if (item.isEmpty()) {
+- if (slot.mayPickup(player)) {
+- inventory.setItem(button, carried);
+- slot.onSwapCraft(carried.getCount());
+- slot.setByPlayer(ItemStack.EMPTY);
+- slot.onTake(player, carried);
+- }
+- } else if (carried.isEmpty()) {
+- if (slot.mayPlace(item)) {
+- int maxStackSize = slot.getMaxStackSize(item);
+- if (item.getCount() > maxStackSize) {
+- slot.setByPlayer(item.split(maxStackSize));
+- } else {
+- inventory.setItem(button, ItemStack.EMPTY);
+- slot.setByPlayer(item);
++ 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()));
+ }
+ }
+- } else if (slot.mayPickup(player) && slot.mayPlace(item)) {
+- int maxStackSize = slot.getMaxStackSize(item);
+- if (item.getCount() > maxStackSize) {
+- slot.setByPlayer(item.split(maxStackSize));
+- slot.onTake(player, carried);
+- if (!inventory.add(carried)) {
+- player.drop(carried, true);
++ // CraftBukkit end
++ }
++ } else {
++ int j2;
++
++ if (clickType == InventoryClickType.SWAP && (button >= 0 && button < 9 || button == 40)) {
++ ItemStack itemstack4 = playerinventory.getItem(button);
++
++ slot = (Slot) this.slots.get(slotId);
++ itemstack = slot.getItem();
++ if (!itemstack4.isEmpty() || !itemstack.isEmpty()) {
++ if (itemstack4.isEmpty()) {
++ if (slot.mayPickup(player)) {
++ playerinventory.setItem(button, itemstack);
++ slot.onSwapCraft(itemstack.getCount());
++ slot.setByPlayer(ItemStack.EMPTY);
++ slot.onTake(player, itemstack);
++ }
++ } else if (itemstack.isEmpty()) {
++ if (slot.mayPlace(itemstack4)) {
++ j2 = slot.getMaxStackSize(itemstack4);
++ if (itemstack4.getCount() > j2) {
++ slot.setByPlayer(itemstack4.split(j2));
++ } else {
++ playerinventory.setItem(button, ItemStack.EMPTY);
++ slot.setByPlayer(itemstack4);
++ }
++ }
++ } else if (slot.mayPickup(player) && slot.mayPlace(itemstack4)) {
++ j2 = slot.getMaxStackSize(itemstack4);
++ if (itemstack4.getCount() > j2) {
++ slot.setByPlayer(itemstack4.split(j2));
++ slot.onTake(player, itemstack);
++ if (!playerinventory.add(itemstack)) {
++ player.drop(itemstack, true);
++ }
++ } else {
++ playerinventory.setItem(button, itemstack);
++ slot.setByPlayer(itemstack4);
++ slot.onTake(player, itemstack);
++ }
+ }
+ } else {
+ inventory.setItem(button, carried);
+@@ -539,15 +699,17 @@
+
+ public void removed(Player player) {
+ if (player instanceof ServerPlayer) {
+- ItemStack carried = this.getCarried();
+- if (!carried.isEmpty()) {
+- if (player.isAlive() && !((ServerPlayer)player).hasDisconnected()) {
+- player.getInventory().placeItemBackInInventory(carried);
++ 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(carried, false);
+ }
+
+- this.setCarried(ItemStack.EMPTY);
++ // this.setCarried(ItemStack.EMPTY); // CraftBukkit - moved up
+ }
+ }
+ }
+@@ -726,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-vineflower-stripped/net/minecraft/world/inventory/AbstractFurnaceMenu.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/AbstractFurnaceMenu.java.patch
new file mode 100644
index 0000000000..e7ef236514
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/AbstractFurnaceMenu.java.patch
@@ -0,0 +1,56 @@
+--- 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> {
+ public static final int INGREDIENT_SLOT = 0;
+@@ -30,9 +35,23 @@
+ private final RecipeType<? extends AbstractCookingRecipe> recipeType;
+ private final RecipeBookType recipeBookType;
+
+- protected AbstractFurnaceMenu(
+- MenuType<?> menuType, RecipeType<? extends AbstractCookingRecipe> recipeType, RecipeBookType recipeBookType, int containerId, Inventory playerInventory
+- ) {
++ // 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 containerId, Inventory playerInventory) {
+ this(menuType, recipeType, recipeBookType, containerId, playerInventory, new SimpleContainer(3), new SimpleContainerData(4));
+ }
+
+@@ -56,6 +67,7 @@
+ this.addSlot(new Slot(container, 0, 56, 17));
+ this.addSlot(new FurnaceFuelSlot(this, container, 1, 56, 53));
+ this.addSlot(new FurnaceResultSlot(playerInventory.player, container, 2, 116, 35));
++ this.player = playerInventory; // CraftBukkit - save player
+
+ for (int i = 0; i < 3; i++) {
+ for (int i1 = 0; i1 < 9; i1++) {
+@@ -110,6 +125,7 @@
+
+ @Override
+ public boolean stillValid(Player player) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return this.container.stillValid(player);
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/AnvilMenu.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/AnvilMenu.java.patch
new file mode 100644
index 0000000000..85a10c1c6c
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/AnvilMenu.java.patch
@@ -0,0 +1,171 @@
+--- a/net/minecraft/world/inventory/AnvilMenu.java
++++ b/net/minecraft/world/inventory/AnvilMenu.java
+@@ -20,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;
+ public static final int ADDITIONAL_SLOT = 1;
+@@ -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 containerId, Inventory playerInventory) {
+ this(containerId, playerInventory, ContainerLevelAccess.NULL);
+@@ -68,7 +78,7 @@
+
+ @Override
+ protected boolean mayPickup(Player player, boolean hasStack) {
+- return (player.getAbilities().instabuild || player.experienceLevel >= this.cost.get()) && this.cost.get() > 0;
++ 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
+@@ -90,14 +101,16 @@
+ this.inputSlots.setItem(1, ItemStack.EMPTY);
+ }
+
+- this.cost.set(0);
+- this.access.execute((level, blockPos) -> {
+- BlockState blockState = level.getBlockState(blockPos);
+- if (!player.getAbilities().instabuild && blockState.is(BlockTags.ANVIL) && player.getRandom().nextFloat() < 0.12F) {
+- BlockState blockState1 = AnvilBlock.damage(blockState);
+- if (blockState1 == null) {
+- level.removeBlock(blockPos, false);
+- level.levelEvent(1029, blockPos, 0);
++ 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 && iblockdata.is(BlockTags.ANVIL) && player.getRandom().nextFloat() < 0.12F) {
++ IBlockData iblockdata1 = AnvilBlock.damage(iblockdata);
++
++ if (iblockdata1 == null) {
++ world.removeBlock(blockposition, false);
++ world.levelEvent(1029, blockposition, 0);
+ } else {
+ level.setBlock(blockPos, blockState1, 2);
+ level.levelEvent(1030, blockPos, 0);
+@@ -113,24 +128,29 @@
+ ItemStack item = this.inputSlots.getItem(0);
+ this.cost.set(1);
+ int i = 0;
+- int i1 = 0;
+- int i2 = 0;
+- if (item.isEmpty()) {
+- this.resultSlots.setItem(0, ItemStack.EMPTY);
+- this.cost.set(0);
++ byte b0 = 0;
++ byte b1 = 0;
++
++ if (itemstack.isEmpty()) {
++ 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 itemStack = item.copy();
+ ItemStack item1 = this.inputSlots.getItem(1);
+ Map<Enchantment, Integer> enchantments = EnchantmentHelper.getEnchantments(itemStack);
+ int var19 = i1 + item.getBaseRepairCost() + (item1.isEmpty() ? 0 : item1.getBaseRepairCost());
+ this.repairItemCountCost = 0;
+- if (!item1.isEmpty()) {
+- boolean flag = item1.is(Items.ENCHANTED_BOOK) && !EnchantedBookItem.getEnchantments(item1).isEmpty();
+- if (itemStack.isDamageableItem() && itemStack.getItem().isValidRepairItem(item, item1)) {
+- int min = Math.min(itemStack.getDamageValue(), itemStack.getMaxDamage() / 4);
+- if (min <= 0) {
+- this.resultSlots.setItem(0, ItemStack.EMPTY);
+- this.cost.set(0);
++ if (!itemstack2.isEmpty()) {
++ boolean flag = itemstack2.is(Items.ENCHANTED_BOOK) && !EnchantedBookItem.getEnchantments(itemstack2).isEmpty();
++ int k;
++ int l;
++ int i1;
++
++ if (itemstack1.isDamageableItem() && itemstack1.getItem().isValidRepairItem(itemstack, itemstack2)) {
++ k = Math.min(itemstack1.getDamageValue(), itemstack1.getMaxDamage() / 4);
++ if (k <= 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;
+ }
+
+@@ -144,9 +164,9 @@
+
+ this.repairItemCountCost = i3;
+ } else {
+- if (!flag && (!itemStack.is(item1.getItem()) || !itemStack.isDamageableItem())) {
+- this.resultSlots.setItem(0, ItemStack.EMPTY);
+- this.cost.set(0);
++ if (!flag && (!itemstack1.is(itemstack2.getItem()) || !itemstack1.isDamageableItem())) {
++ 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;
+ }
+
+@@ -224,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;
+ }
+ }
+@@ -248,12 +279,12 @@
+ itemStack = ItemStack.EMPTY;
+ }
+
+- if (i2 == i && i2 > 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) {
+- itemStack = ItemStack.EMPTY;
++ if (this.cost.get() >= maximumRepairCost && !this.player.getAbilities().instabuild) { // CraftBukkit
++ itemstack1 = ItemStack.EMPTY;
+ }
+
+ if (!itemStack.isEmpty()) {
+@@ -270,7 +302,8 @@
+ EnchantmentHelper.setEnchantments(enchantments, itemStack);
+ }
+
+- this.resultSlots.setItem(0, itemStack);
++ 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();
+ }
+ }
+@@ -308,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-vineflower-stripped/net/minecraft/world/inventory/BeaconMenu.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/BeaconMenu.java.patch
new file mode 100644
index 0000000000..1ff8b5d884
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/BeaconMenu.java.patch
@@ -0,0 +1,67 @@
+--- 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 {
+ private static final int PAYMENT_SLOT = 0;
+@@ -35,6 +29,10 @@
+ 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 containerId, Container container) {
+ this(containerId, container, new SimpleContainerData(3), ContainerLevelAccess.NULL);
+@@ -42,6 +40,18 @@
+
+ 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
++ public boolean canPlaceItem(int index, ItemStack stack) {
++ return stack.is(ItemTags.BEACON_PAYMENT_ITEMS);
++ }
++
++ @Override
++ public int getMaxStackSize() {
++ return 1;
++ }
++ };
+ checkContainerDataCount(beaconData, 3);
+ this.beaconData = beaconData;
+ this.access = access;
+@@ -75,6 +90,7 @@
+
+ @Override
+ public boolean stillValid(Player player) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return stillValid(this.access, player, Blocks.BEACON);
+ }
+
+@@ -180,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-vineflower-stripped/net/minecraft/world/inventory/BrewingStandMenu.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/BrewingStandMenu.java.patch
new file mode 100644
index 0000000000..5e8cb51206
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/BrewingStandMenu.java.patch
@@ -0,0 +1,55 @@
+--- a/net/minecraft/world/inventory/BrewingStandMenu.java
++++ b/net/minecraft/world/inventory/BrewingStandMenu.java
+@@ -11,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 {
+ private static final int BOTTLE_SLOT_START = 0;
+@@ -27,12 +33,18 @@
+ private final ContainerData brewingStandData;
+ private final Slot ingredientSlot;
+
++ // 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 containerId, Inventory playerInventory, Container brewingStandContainer, ContainerData brewingStandData) {
+ super(MenuType.BREWING_STAND, containerId);
++ player = playerInventory; // CraftBukkit
+ checkContainerSize(brewingStandContainer, 5);
+ checkContainerDataCount(brewingStandData, 2);
+ this.brewingStand = brewingStandContainer;
+@@ -57,6 +72,7 @@
+
+ @Override
+ public boolean stillValid(Player player) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return this.brewingStand.stillValid(player);
+ }
+
+@@ -184,8 +211,12 @@
+ super.onTake(player, stack);
+ }
+
+- public static boolean mayPlaceItem(ItemStack stack) {
+- return stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE);
++ // CraftBukkit start
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
+ }
+ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/CartographyTableMenu.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/CartographyTableMenu.java.patch
new file mode 100644
index 0000000000..e43bc7634d
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/CartographyTableMenu.java.patch
@@ -0,0 +1,83 @@
+--- a/net/minecraft/world/inventory/CartographyTableMenu.java
++++ b/net/minecraft/world/inventory/CartographyTableMenu.java
+@@ -13,8 +10,30 @@
+ import net.minecraft.world.level.Level;
+ 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;
+@@ -45,6 +52,34 @@
+
+ public CartographyTableMenu(int containerId, Inventory playerInventory, final ContainerLevelAccess access) {
+ super(MenuType.CARTOGRAPHY_TABLE, containerId);
++ this.container = new SimpleContainer(2) {
++ @Override
++ public void setChanged() {
++ CartographyTableMenu.this.slotsChanged(this);
++ super.setChanged();
++ }
++
++ // CraftBukkit start
++ @Override
++ public Location getLocation() {
++ return access.getLocation();
++ }
++ // CraftBukkit end
++ };
++ this.resultContainer = new ResultContainer() {
++ @Override
++ public void setChanged() {
++ CartographyTableMenu.this.slotsChanged(this);
++ super.setChanged();
++ }
++
++ // CraftBukkit start
++ @Override
++ public Location getLocation() {
++ return access.getLocation();
++ }
++ // CraftBukkit end
++ };
+ this.access = access;
+ this.addSlot(new Slot(this.container, 0, 15, 15) {
+ @Override
+@@ -89,10 +128,13 @@
+ for (int i = 0; i < 9; i++) {
+ this.addSlot(new Slot(playerInventory, i, 8 + i * 18, 142));
+ }
++
++ player = (Player) playerInventory.player.getBukkitEntity(); // CraftBukkit
+ }
+
+ @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-vineflower-stripped/net/minecraft/world/inventory/ChestMenu.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/ChestMenu.java.patch
new file mode 100644
index 0000000000..c65288c9f3
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/ChestMenu.java.patch
@@ -0,0 +1,69 @@
+--- a/net/minecraft/world/inventory/ChestMenu.java
++++ b/net/minecraft/world/inventory/ChestMenu.java
+@@ -5,12 +6,38 @@
+ 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;
+
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ CraftInventory inventory;
++ if (this.container instanceof Inventory) {
++ inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryPlayer((Inventory) this.container);
++ } else if (this.container instanceof CompoundContainer) {
++ inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) this.container);
++ } else {
++ inventory = new CraftInventory(this.container);
++ }
++
++ bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), inventory, this);
++ return bukkitEntity;
++ }
++ // CraftBukkit end
++
+ private ChestMenu(MenuType<?> type, int containerId, Inventory playerInventory, int rows) {
+ this(type, containerId, playerInventory, new SimpleContainer(9 * rows), rows);
+ }
+@@ -55,9 +83,16 @@
+ container.startOpen(playerInventory.player);
+ int i = (this.containerRows - 4) * 18;
+
+- for (int i1 = 0; i1 < this.containerRows; i1++) {
+- for (int i2 = 0; i2 < 9; i2++) {
+- this.addSlot(new Slot(container, i2 + i1 * 9, 8 + i2 * 18, 18 + i1 * 18));
++ // CraftBukkit start - Save player
++ this.player = playerInventory;
++ // CraftBukkit end
++
++ int l;
++ int i1;
++
++ for (l = 0; l < this.containerRows; ++l) {
++ for (i1 = 0; i1 < 9; ++i1) {
++ this.addSlot(new Slot(container, i1 + l * 9, 8 + i1 * 18, 18 + l * 18));
+ }
+ }
+
+@@ -74,6 +110,7 @@
+
+ @Override
+ public boolean stillValid(Player player) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return this.container.stillValid(player);
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/ContainerLevelAccess.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/ContainerLevelAccess.java.patch
new file mode 100644
index 0000000000..dce50df107
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/ContainerLevelAccess.java.patch
@@ -0,0 +1,44 @@
+--- a/net/minecraft/world/inventory/ContainerLevelAccess.java
++++ b/net/minecraft/world/inventory/ContainerLevelAccess.java
+@@ -7,6 +7,21 @@
+ import net.minecraft.world.level.Level;
+
+ 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
+ public <T> Optional<T> evaluate(BiFunction<Level, BlockPos, T> levelPosConsumer) {
+@@ -16,7 +31,19 @@
+
+ static ContainerLevelAccess create(final Level level, final BlockPos pos) {
+ return new ContainerLevelAccess() {
++ // CraftBukkit start
+ @Override
++ public Level getWorld() {
++ return level;
++ }
++
++ @Override
++ public BlockPos getPosition() {
++ return pos;
++ }
++ // 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-vineflower-stripped/net/minecraft/world/inventory/CrafterMenu.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/CrafterMenu.java.patch
new file mode 100644
index 0000000000..13c60f6388
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -9,7 +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;
+@@ -109,6 +135,7 @@
+
+ @Override
+ public boolean stillValid(Player player) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return this.container.stillValid(player);
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/CraftingMenu.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/CraftingMenu.java.patch
new file mode 100644
index 0000000000..168643e8b3
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/CraftingMenu.java.patch
@@ -0,0 +1,75 @@
+--- a/net/minecraft/world/inventory/CraftingMenu.java
++++ b/net/minecraft/world/inventory/CraftingMenu.java
+@@ -15,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> {
+ public static final int RESULT_SLOT = 0;
+@@ -24,10 +28,13 @@
+ 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 = new TransientCraftingContainer(this, 3, 3);
+- private final ResultContainer resultSlots = new ResultContainer();
+- 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 containerId, Inventory playerInventory) {
+ this(containerId, playerInventory, ContainerLevelAccess.NULL);
+@@ -35,6 +42,11 @@
+
+ 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.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));
+@@ -71,6 +90,7 @@
+ }
+ }
+ }
++ itemstack = org.bukkit.craftbukkit.event.CraftEventFactory.callPreCraftEvent(container, result, itemstack, menu.getBukkitView(), optional.map(RecipeHolder::toBukkitRecipe).orElse(null) instanceof RepairItemRecipe); // CraftBukkit
+
+ result.setItem(0, itemStack);
+ menu.setRemoteSlot(0, itemStack);
+@@ -107,6 +131,7 @@
+
+ @Override
+ public boolean stillValid(Player player) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return stillValid(this.access, player, Blocks.CRAFTING_TABLE);
+ }
+
+@@ -191,4 +220,17 @@
+ public boolean shouldMoveToInventory(int slotIndex) {
+ return slotIndex != this.getResultSlotIndex();
+ }
++
++ // CraftBukkit start
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ CraftInventoryCrafting inventory = new CraftInventoryCrafting(this.craftSlots, this.resultSlots);
++ bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this);
++ return bukkitEntity;
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/DispenserMenu.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/DispenserMenu.java.patch
new file mode 100644
index 0000000000..bf785b0632
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/DispenserMenu.java.patch
@@ -0,0 +1,63 @@
+--- a/net/minecraft/world/inventory/DispenserMenu.java
++++ b/net/minecraft/world/inventory/DispenserMenu.java
+@@ -6,13 +6,22 @@
+ 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;
+ private static final int INV_SLOT_START = 9;
+ 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 containerId, Inventory playerInventory) {
+ this(containerId, playerInventory, new SimpleContainer(9));
+@@ -20,6 +30,10 @@
+
+ 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(playerInventory.player);
+@@ -43,6 +61,7 @@
+
+ @Override
+ public boolean stillValid(Player player) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return this.dispenser.stillValid(player);
+ }
+
+@@ -82,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-vineflower-stripped/net/minecraft/world/inventory/EnchantmentMenu.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/EnchantmentMenu.java.patch
new file mode 100644
index 0000000000..1a830e8631
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/EnchantmentMenu.java.patch
@@ -0,0 +1,261 @@
+--- a/net/minecraft/world/inventory/EnchantmentMenu.java
++++ b/net/minecraft/world/inventory/EnchantmentMenu.java
+@@ -25,6 +23,22 @@
+ import net.minecraft.world.level.Level;
+ 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 {
+ static final ResourceLocation EMPTY_SLOT_LAPIS_LAZULI = new ResourceLocation("item/empty_slot_lapis_lazuli");
+@@ -36,11 +45,15 @@
+ }
+ };
+ private final ContainerLevelAccess access;
+- private final RandomSource random = RandomSource.create();
+- private final DataSlot enchantmentSeed = DataSlot.standalone();
+- public final int[] costs = new int[3];
+- public final int[] enchantClue = new int[]{-1, -1, -1};
+- public final int[] levelClue = new int[]{-1, -1, -1};
++ private final RandomSource random;
++ private final DataSlot enchantmentSeed;
++ 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 containerId, Inventory playerInventory) {
+ this(containerId, playerInventory, ContainerLevelAccess.NULL);
+@@ -48,6 +61,25 @@
+
+ public EnchantmentMenu(int containerId, Inventory playerInventory, ContainerLevelAccess access) {
+ super(MenuType.ENCHANTMENT, containerId);
++ this.enchantSlots = new SimpleContainer(2) {
++ @Override
++ public void setChanged() {
++ 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();
++ this.costs = new int[3];
++ this.enchantClue = new int[]{-1, -1, -1};
++ this.levelClue = new int[]{-1, -1, -1};
+ this.access = access;
+ this.addSlot(new Slot(this.enchantSlots, 0, 15, 47) {
+ @Override
+@@ -87,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
+@@ -97,9 +131,16 @@
+ this.access.execute((level, blockPos) -> {
+ int i1 = 0;
+
+- for (BlockPos blockPos1 : EnchantmentTableBlock.BOOKSHELF_OFFSETS) {
+- if (EnchantmentTableBlock.isValidBookShelf(level, blockPos, blockPos1)) {
+- i1++;
++ if (!itemstack.isEmpty()) { // CraftBukkit - relax condition
++ this.access.execute((world, blockposition) -> {
++ int i = 0;
++ Iterator iterator = EnchantmentTableBlock.BOOKSHELF_OFFSETS.iterator();
++
++ while (iterator.hasNext()) {
++ BlockPos blockposition1 = (BlockPos) iterator.next();
++
++ if (EnchantmentTableBlock.isValidBookShelf(world, blockposition, blockposition1)) {
++ ++i;
+ }
+ }
+
+@@ -125,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 {
+@@ -145,35 +227,67 @@
+ int i = id + 1;
+ if ((item1.isEmpty() || item1.getCount() < i) && !player.getAbilities().instabuild) {
+ return false;
+- } else if (this.costs[id] <= 0
+- || item.isEmpty()
+- || (player.experienceLevel < i || player.experienceLevel < this.costs[id]) && !player.getAbilities().instabuild) {
+- return false;
+- } else {
+- this.access.execute((level, blockPos) -> {
+- ItemStack itemStack = item;
+- List<EnchantmentInstance> enchantmentList = this.getEnchantmentList(item, id, this.costs[id]);
+- if (!enchantmentList.isEmpty()) {
+- player.onEnchantmentPerformed(item, i);
+- boolean isBook = item.is(Items.BOOK);
+- if (isBook) {
+- itemStack = new ItemStack(Items.ENCHANTED_BOOK);
+- CompoundTag tag = item.getTag();
+- if (tag != null) {
+- itemStack.setTag(tag.copy());
++ } else if (this.costs[id] > 0 && !itemstack.isEmpty() && (player.experienceLevel >= j && player.experienceLevel >= this.costs[id] || player.getAbilities().instabuild)) {
++ this.access.execute((world, blockposition) -> {
++ ItemStack itemstack2 = itemstack;
++ List<EnchantmentInstance> list = this.getEnchantmentList(itemstack, id, this.costs[id]);
++
++ // 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) {
++ itemstack2 = new ItemStack(Items.ENCHANTED_BOOK);
++ CompoundTag nbttagcompound = itemstack.getTag();
++
++ if (nbttagcompound != null) {
++ itemstack2.setTag(nbttagcompound.copy());
+ }
+
+ this.enchantSlots.setItem(0, itemStack);
+ }
+
+- for (EnchantmentInstance enchantmentInstance : enchantmentList) {
+- if (isBook) {
+- EnchantedBookItem.addEnchantment(itemStack, enchantmentInstance);
+- } else {
+- itemStack.enchant(enchantmentInstance.enchantment, enchantmentInstance.level);
++ // 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;
++ }
++
++ EnchantmentInstance weightedrandomenchant = new EnchantmentInstance(nms, entry.getValue());
++ EnchantedBookItem.addEnchantment(itemstack2, weightedrandomenchant);
++ } else {
++ item.addUnsafeEnchantment(entry.getKey(), entry.getValue());
++ }
++ } catch (IllegalArgumentException e) {
++ /* Just swallow invalid enchantments */
+ }
+ }
+
++ player.onEnchantmentPerformed(itemstack, j);
++ // CraftBukkit end
++
++ // CraftBukkit - TODO: let plugins change this
+ if (!player.getAbilities().instabuild) {
+ item1.shrink(i);
+ if (item1.isEmpty()) {
+@@ -226,7 +349,8 @@
+ }
+
+ @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);
+ }
+
+@@ -274,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-vineflower-stripped/net/minecraft/world/inventory/FurnaceResultSlot.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/FurnaceResultSlot.java.patch
new file mode 100644
index 0000000000..4eb56f49d4
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/FurnaceResultSlot.java.patch
@@ -0,0 +1,22 @@
+--- a/net/minecraft/world/inventory/FurnaceResultSlot.java
++++ b/net/minecraft/world/inventory/FurnaceResultSlot.java
+@@ -44,8 +45,17 @@
+ @Override
+ protected void checkTakeAchievements(ItemStack stack) {
+ stack.onCraftedBy(this.player.level(), this.player, this.removeCount);
+- if (this.player instanceof ServerPlayer serverPlayer && this.container instanceof AbstractFurnaceBlockEntity abstractFurnaceBlockEntity) {
+- abstractFurnaceBlockEntity.awardUsedRecipesAndPopExperience(serverPlayer);
++ Player entityhuman = this.player;
++
++ if (entityhuman instanceof ServerPlayer) {
++ ServerPlayer entityplayer = (ServerPlayer) entityhuman;
++ Container iinventory = this.container;
++
++ if (iinventory instanceof AbstractFurnaceBlockEntity) {
++ AbstractFurnaceBlockEntity tileentityfurnace = (AbstractFurnaceBlockEntity) iinventory;
++
++ tileentityfurnace.awardUsedRecipesAndPopExperience(entityplayer, stack, this.removeCount); // CraftBukkit
++ }
+ }
+
+ this.removeCount = 0;
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/GrindstoneMenu.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/GrindstoneMenu.java.patch
new file mode 100644
index 0000000000..948728da0b
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/GrindstoneMenu.java.patch
@@ -0,0 +1,153 @@
+--- a/net/minecraft/world/inventory/GrindstoneMenu.java
++++ b/net/minecraft/world/inventory/GrindstoneMenu.java
+@@ -18,8 +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;
+@@ -44,6 +59,21 @@
+
+ public GrindstoneMenu(int containerId, Inventory playerInventory, final ContainerLevelAccess access) {
+ super(MenuType.GRINDSTONE, containerId);
++ this.resultSlots = new ResultContainer();
++ this.repairSlots = new SimpleContainer(2) {
++ @Override
++ public void setChanged() {
++ super.setChanged();
++ GrindstoneMenu.this.slotsChanged(this);
++ }
++
++ // CraftBukkit start
++ @Override
++ public Location getLocation() {
++ return access.getLocation();
++ }
++ // CraftBukkit end
++ };
+ this.access = access;
+ this.addSlot(new Slot(this.repairSlots, 0, 49, 19) {
+ @Override
+@@ -113,6 +150,8 @@
+ for (int i = 0; i < 9; i++) {
+ this.addSlot(new Slot(playerInventory, i, 8 + i * 18, 142));
+ }
++
++ player = (Player) playerInventory.player.getBukkitEntity(); // CraftBukkit
+ }
+
+ @Override
+@@ -124,17 +164,16 @@
+ }
+
+ private void createResult() {
+- ItemStack item = this.repairSlots.getItem(0);
+- ItemStack item1 = this.repairSlots.getItem(1);
+- boolean flag = !item.isEmpty() || !item1.isEmpty();
+- boolean flag1 = !item.isEmpty() && !item1.isEmpty();
+- if (!flag) {
+- this.resultSlots.setItem(0, ItemStack.EMPTY);
+- } else {
+- boolean flag2 = !item.isEmpty() && !item.is(Items.ENCHANTED_BOOK) && !item.isEnchanted()
+- || !item1.isEmpty() && !item1.is(Items.ENCHANTED_BOOK) && !item1.isEnchanted();
+- if (item.getCount() > 1 || item1.getCount() > 1 || !flag1 && flag2) {
+- this.resultSlots.setItem(0, ItemStack.EMPTY);
++ ItemStack itemstack = this.repairSlots.getItem(0);
++ ItemStack itemstack1 = this.repairSlots.getItem(1);
++ boolean flag = !itemstack.isEmpty() || !itemstack1.isEmpty();
++ boolean flag1 = !itemstack.isEmpty() && !itemstack1.isEmpty();
++
++ if (flag) {
++ 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) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareGrindstoneEvent(getBukkitView(), ItemStack.EMPTY); // CraftBukkit
+ this.broadcastChanges();
+ return;
+ }
+@@ -143,21 +183,22 @@
+ int max;
+ ItemStack itemStack;
+ if (flag1) {
+- if (!item.is(item1.getItem())) {
+- this.resultSlots.setItem(0, ItemStack.EMPTY);
++ if (!itemstack.is(itemstack1.getItem())) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareGrindstoneEvent(getBukkitView(), ItemStack.EMPTY); // CraftBukkit
+ this.broadcastChanges();
+ return;
+ }
+
+- Item item2 = item.getItem();
+- int i1 = item2.getMaxDamage() - item.getDamageValue();
+- int i2 = item2.getMaxDamage() - item1.getDamageValue();
+- int i3 = i1 + i2 + item2.getMaxDamage() * 5 / 100;
+- max = Math.max(item2.getMaxDamage() - i3, 0);
+- itemStack = this.mergeEnchants(item, item1);
+- if (!itemStack.isDamageableItem()) {
+- if (!ItemStack.matches(item, item1)) {
+- this.resultSlots.setItem(0, ItemStack.EMPTY);
++ Item item = itemstack.getItem();
++ int j = item.getMaxDamage() - itemstack.getDamageValue();
++ int k = item.getMaxDamage() - itemstack1.getDamageValue();
++ int l = j + k + item.getMaxDamage() * 5 / 100;
++
++ i = Math.max(item.getMaxDamage() - l, 0);
++ itemstack2 = this.mergeEnchants(itemstack, itemstack1);
++ if (!itemstack2.isDamageableItem()) {
++ if (!ItemStack.matches(itemstack, itemstack1)) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareGrindstoneEvent(getBukkitView(), ItemStack.EMPTY); // CraftBukkit
+ this.broadcastChanges();
+ return;
+ }
+@@ -170,9 +208,15 @@
+ itemStack = flag3 ? item : item1;
+ }
+
+- this.resultSlots.setItem(0, this.removeNonCurses(itemStack, max, i));
++ i = flag3 ? itemstack.getDamageValue() : itemstack1.getDamageValue();
++ itemstack2 = flag3 ? itemstack : itemstack1;
++ }
++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareGrindstoneEvent(getBukkitView(), this.removeNonCurses(itemstack2, i, b0)); // CraftBukkit
++ } else {
++ 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();
+ }
+
+@@ -228,7 +277,8 @@
+ }
+
+ @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-vineflower-stripped/net/minecraft/world/inventory/HopperMenu.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/HopperMenu.java.patch
new file mode 100644
index 0000000000..457749fe53
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/HopperMenu.java.patch
@@ -0,0 +1,50 @@
+--- a/net/minecraft/world/inventory/HopperMenu.java
++++ b/net/minecraft/world/inventory/HopperMenu.java
+@@ -6,10 +6,31 @@
+ 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;
+
++ // 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 containerId, Inventory playerInventory) {
+ this(containerId, playerInventory, new SimpleContainer(5));
+ }
+@@ -17,6 +39,7 @@
+ public HopperMenu(int containerId, Inventory playerInventory, Container container) {
+ super(MenuType.HOPPER, containerId);
+ this.hopper = container;
++ this.player = playerInventory; // CraftBukkit - save player
+ checkContainerSize(container, 5);
+ container.startOpen(playerInventory.player);
+ int i = 51;
+@@ -38,6 +64,7 @@
+
+ @Override
+ public boolean stillValid(Player player) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return this.hopper.stillValid(player);
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/HorseInventoryMenu.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/HorseInventoryMenu.java.patch
new file mode 100644
index 0000000000..019a6bc21f
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/HorseInventoryMenu.java.patch
@@ -0,0 +1,36 @@
+--- a/net/minecraft/world/inventory/HorseInventoryMenu.java
++++ b/net/minecraft/world/inventory/HorseInventoryMenu.java
+@@ -8,12 +8,32 @@
+ 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;
+
++ // 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(null, containerId);
++ super((MenuType) null, containerId);
++ player = playerInventory;
++ // CraftBukkit end
+ this.horseContainer = container;
+ this.horse = horse;
+ int i = 3;
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/InventoryMenu.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/InventoryMenu.java.patch
new file mode 100644
index 0000000000..e8bd160dfb
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/InventoryMenu.java.patch
@@ -0,0 +1,61 @@
+--- 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> {
+ public static final int CONTAINER_ID = 0;
+@@ -35,15 +38,28 @@
+ EMPTY_ARMOR_SLOT_BOOTS, EMPTY_ARMOR_SLOT_LEGGINGS, EMPTY_ARMOR_SLOT_CHESTPLATE, 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 playerInventory, boolean active, final Player owner) {
+ super(null, 0);
+ 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));
+
+ for (int i = 0; i < 2; i++) {
+@@ -251,4 +276,17 @@
+ public boolean shouldMoveToInventory(int slotIndex) {
+ return slotIndex != this.getResultSlotIndex();
+ }
++
++ // CraftBukkit start
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ CraftInventoryCrafting inventory = new CraftInventoryCrafting(this.craftSlots, this.resultSlots);
++ bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), inventory, this);
++ return bukkitEntity;
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/ItemCombinerMenu.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/ItemCombinerMenu.java.patch
new file mode 100644
index 0000000000..87031d2383
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/ItemCombinerMenu.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/inventory/ItemCombinerMenu.java
++++ b/net/minecraft/world/inventory/ItemCombinerMenu.java
+@@ -114,13 +124,10 @@
+
+ @Override
+ public boolean stillValid(Player player) {
+- return this.access
+- .evaluate(
+- (level, blockPos) -> !this.isValidBlock(level.getBlockState(blockPos))
+- ? false
+- : player.distanceToSqr((double)blockPos.getX() + 0.5, (double)blockPos.getY() + 0.5, (double)blockPos.getZ() + 0.5) <= 64.0,
+- true
+- );
++ 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);
+ }
+
+ @Override
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/LecternMenu.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/LecternMenu.java.patch
new file mode 100644
index 0000000000..51e9a044f2
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/LecternMenu.java.patch
@@ -0,0 +1,100 @@
+--- a/net/minecraft/world/inventory/LecternMenu.java
++++ b/net/minecraft/world/inventory/LecternMenu.java
+@@ -4,8 +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;
+@@ -15,24 +38,27 @@
+ private final Container lectern;
+ private final ContainerData lecternData;
+
+- public LecternMenu(int containerId) {
+- this(containerId, 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 containerId, Container lectern, ContainerData lecternData) {
+- super(MenuType.LECTERN, containerId);
+- checkContainerSize(lectern, 1);
+- checkContainerDataCount(lecternData, 1);
+- this.lectern = lectern;
+- this.lecternData = lecternData;
+- this.addSlot(new Slot(lectern, 0, 0, 0) {
++ public LecternMenu(int i, Container iinventory, ContainerData icontainerproperties, Inventory playerinventory) {
++ // CraftBukkit end
++ super(MenuType.LECTERN, i);
++ checkContainerSize(iinventory, 1);
++ checkContainerDataCount(icontainerproperties, 1);
++ this.lectern = iinventory;
++ this.lecternData = icontainerproperties;
++ this.addSlot(new Slot(iinventory, 0, 0, 0) {
+ @Override
+ public void setChanged() {
+ super.setChanged();
+ LecternMenu.this.slotsChanged(this.container);
+ }
+ });
+- this.addDataSlots(lecternData);
++ this.addDataSlots(icontainerproperties);
++ player = (Player) playerinventory.player.getBukkitEntity(); // CraftBukkit
+ }
+
+ @Override
+@@ -58,7 +84,15 @@
+ return false;
+ }
+
+- ItemStack itemStack = this.lectern.removeItemNoUpdate(0);
++ // 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();
+ if (!player.getInventory().add(itemStack)) {
+ player.drop(itemStack, false);
+@@ -83,7 +117,9 @@
+ }
+
+ @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-vineflower-stripped/net/minecraft/world/inventory/LoomMenu.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/LoomMenu.java.patch
new file mode 100644
index 0000000000..0fa04cefb8
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/LoomMenu.java.patch
@@ -0,0 +1,121 @@
+--- a/net/minecraft/world/inventory/LoomMenu.java
++++ b/net/minecraft/world/inventory/LoomMenu.java
+@@ -24,8 +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;
+@@ -63,6 +69,39 @@
+
+ public LoomMenu(int containerId, Inventory playerInventory, final ContainerLevelAccess access) {
+ super(MenuType.LOOM, containerId);
++ this.selectedBannerPatternIndex = DataSlot.standalone();
++ this.selectablePatterns = List.of();
++ this.slotUpdateListener = () -> {
++ };
++ this.inputContainer = new SimpleContainer(3) {
++ @Override
++ public void setChanged() {
++ super.setChanged();
++ 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
++ public void setChanged() {
++ super.setChanged();
++ LoomMenu.this.slotUpdateListener.run();
++ }
++
++ // CraftBukkit start
++ @Override
++ public Location getLocation() {
++ return access.getLocation();
++ }
++ // CraftBukkit end
++ };
+ this.access = access;
+ this.bannerSlot = this.addSlot(new Slot(this.inputContainer, 0, 13, 26) {
+ @Override
+@@ -118,10 +161,12 @@
+ }
+
+ this.addDataSlot(this.selectedBannerPatternIndex);
++ player = (Player) playerInventory.player.getBukkitEntity(); // CraftBukkit
+ }
+
+ @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);
+ }
+
+@@ -275,16 +332,23 @@
+ }
+
+ private void setupResultSlot(Holder<BannerPattern> pattern) {
+- ItemStack item = this.bannerSlot.getItem();
+- ItemStack item1 = this.dyeSlot.getItem();
+- ItemStack itemStack = ItemStack.EMPTY;
+- if (!item.isEmpty() && !item1.isEmpty()) {
+- itemStack = item.copyWithCount(1);
+- DyeColor dyeColor = ((DyeItem)item1.getItem()).getDyeColor();
+- CompoundTag blockEntityData = BlockItem.getBlockEntityData(itemStack);
+- ListTag list;
+- if (blockEntityData != null && blockEntityData.contains("Patterns", 9)) {
+- list = blockEntityData.getList("Patterns", 10);
++ ItemStack itemstack = this.bannerSlot.getItem();
++ ItemStack itemstack1 = this.dyeSlot.getItem();
++ ItemStack itemstack2 = ItemStack.EMPTY;
++
++ if (!itemstack.isEmpty() && !itemstack1.isEmpty()) {
++ itemstack2 = itemstack.copyWithCount(1);
++ DyeColor enumcolor = ((DyeItem) itemstack1.getItem()).getDyeColor();
++ CompoundTag nbttagcompound = BlockItem.getBlockEntityData(itemstack2);
++ ListTag nbttaglist;
++
++ if (nbttagcompound != null && nbttagcompound.contains("Patterns", 9)) {
++ nbttaglist = nbttagcompound.getList("Patterns", 10);
++ // CraftBukkit start
++ while (nbttaglist.size() > 20) {
++ nbttaglist.remove(20);
++ }
++ // CraftBukkit end
+ } else {
+ list = new ListTag();
+ if (blockEntityData == null) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/MenuType.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/MenuType.java.patch
new file mode 100644
index 0000000000..2b0f95c21c
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -26,7 +27,9 @@
+ 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", (containerId, playerInventory) -> new LecternMenu(containerId));
++ 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);
+ public static final MenuType<ShulkerBoxMenu> SHULKER_BOX = register("shulker_box", ShulkerBoxMenu::new);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/MerchantContainer.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/MerchantContainer.java.patch
new file mode 100644
index 0000000000..b4ca78ad64
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -9,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 {
+ private final Merchant merchant;
+@@ -18,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.merchant = merchant;
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/MerchantMenu.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/MerchantMenu.java.patch
new file mode 100644
index 0000000000..db2427ffea
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/MerchantMenu.java.patch
@@ -0,0 +1,53 @@
+--- a/net/minecraft/world/inventory/MerchantMenu.java
++++ b/net/minecraft/world/inventory/MerchantMenu.java
+@@ -10,6 +11,7 @@
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.item.trading.Merchant;
+ import net.minecraft.world.item.trading.MerchantOffers;
++import org.bukkit.craftbukkit.inventory.CraftInventoryView; // CraftBukkit
+
+ public class MerchantMenu extends AbstractContainerMenu {
+ protected static final int PAYMENT1_SLOT = 0;
+@@ -29,6 +32,19 @@
+ private boolean showProgressBar;
+ private boolean canRestock;
+
++ // 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 containerId, Inventory playerInventory) {
+ this(containerId, playerInventory, new ClientSideMerchant(playerInventory.player));
+ }
+@@ -40,6 +56,7 @@
+ this.addSlot(new Slot(this.tradeContainer, 0, 136, 37));
+ this.addSlot(new Slot(this.tradeContainer, 1, 162, 37));
+ this.addSlot(new MerchantResultSlot(playerInventory.player, trader, this.tradeContainer, 2, 220, 37));
++ this.player = playerInventory; // CraftBukkit - save player
+
+ for (int i = 0; i < 3; i++) {
+ for (int i1 = 0; i1 < 9; i1++) {
+@@ -147,10 +169,10 @@
+ }
+
+ private void playTradeSound() {
+- if (!this.trader.isClientSide()) {
+- Entity entity = (Entity)this.trader;
+- entity.level()
+- .playLocalSound(entity.getX(), entity.getY(), entity.getZ(), this.trader.getNotifyTradeSound(), SoundSource.NEUTRAL, 1.0F, 1.0F, false);
++ 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-vineflower-stripped/net/minecraft/world/inventory/PlayerEnderChestContainer.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/PlayerEnderChestContainer.java.patch
new file mode 100644
index 0000000000..e8a784f511
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/PlayerEnderChestContainer.java.patch
@@ -0,0 +1,25 @@
+--- a/net/minecraft/world/inventory/PlayerEnderChestContainer.java
++++ b/net/minecraft/world/inventory/PlayerEnderChestContainer.java
+@@ -7,13 +7,22 @@
+ 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-vineflower-stripped/net/minecraft/world/inventory/ResultContainer.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/ResultContainer.java.patch
new file mode 100644
index 0000000000..1a0059accf
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/ResultContainer.java.patch
@@ -0,0 +1,58 @@
+--- a/net/minecraft/world/inventory/ResultContainer.java
++++ b/net/minecraft/world/inventory/ResultContainer.java
+@@ -8,12 +9,55 @@
+ 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 = NonNullList.withSize(1, ItemStack.EMPTY);
+ @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);
++ }
++
++ @Override
+ public int getContainerSize() {
+ return 1;
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/ShulkerBoxMenu.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/ShulkerBoxMenu.java.patch
new file mode 100644
index 0000000000..256b91aa07
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/ShulkerBoxMenu.java.patch
@@ -0,0 +1,48 @@
+--- a/net/minecraft/world/inventory/ShulkerBoxMenu.java
++++ b/net/minecraft/world/inventory/ShulkerBoxMenu.java
+@@ -6,10 +6,29 @@
+ 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;
+
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
++ }
++
++ bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), new CraftInventory(this.container), this);
++ return bukkitEntity;
++ }
++ // CraftBukkit end
++
+ public ShulkerBoxMenu(int containerId, Inventory playerInventory) {
+ this(containerId, playerInventory, new SimpleContainer(27));
+ }
+@@ -18,6 +38,7 @@
+ super(MenuType.SHULKER_BOX, containerId);
+ checkContainerSize(container, 27);
+ this.container = container;
++ this.player = playerInventory; // CraftBukkit - save player
+ container.startOpen(playerInventory.player);
+ int i = 3;
+ int i1 = 9;
+@@ -41,6 +66,7 @@
+
+ @Override
+ public boolean stillValid(Player player) {
++ if (!this.checkReachable) return true; // CraftBukkit
+ return this.container.stillValid(player);
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/SmithingMenu.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/SmithingMenu.java.patch
new file mode 100644
index 0000000000..7360f102ae
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/SmithingMenu.java.patch
@@ -0,0 +1,70 @@
+--- a/net/minecraft/world/inventory/SmithingMenu.java
++++ b/net/minecraft/world/inventory/SmithingMenu.java
+@@ -14,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;
+ public static final int BASE_SLOT = 1;
+@@ -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 containerId, Inventory playerInventory) {
+ this(containerId, playerInventory, ContainerLevelAccess.NULL);
+@@ -83,16 +99,20 @@
+
+ @Override
+ public void createResult() {
+- List<RecipeHolder<SmithingRecipe>> recipesFor = this.level.getRecipeManager().getRecipesFor(RecipeType.SMITHING, this.inputSlots, this.level);
+- if (recipesFor.isEmpty()) {
+- this.resultSlots.setItem(0, ItemStack.EMPTY);
++ List<RecipeHolder<SmithingRecipe>> list = this.level.getRecipeManager().getRecipesFor(RecipeType.SMITHING, this.inputSlots, this.level);
++
++ if (list.isEmpty()) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareSmithingEvent(getBukkitView(), ItemStack.EMPTY); // CraftBukkit
+ } else {
+- RecipeHolder<SmithingRecipe> recipeHolder = recipesFor.get(0);
+- ItemStack itemStack = recipeHolder.value().assemble(this.inputSlots, this.level.registryAccess());
+- if (itemStack.isItemEnabled(this.level.enabledFeatures())) {
+- this.selectedRecipe = recipeHolder;
+- this.resultSlots.setRecipeUsed(recipeHolder);
+- this.resultSlots.setItem(0, itemStack);
++ RecipeHolder<SmithingRecipe> recipeholder = (RecipeHolder) list.get(0);
++ ItemStack itemstack = ((SmithingRecipe) recipeholder.value()).assemble(this.inputSlots, this.level.registryAccess());
++
++ if (itemstack.isItemEnabled(this.level.enabledFeatures())) {
++ this.selectedRecipe = recipeholder;
++ this.resultSlots.setRecipeUsed(recipeholder);
++ // CraftBukkit start
++ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareSmithingEvent(getBukkitView(), itemstack);
++ // CraftBukkit end
+ }
+ }
+ }
+@@ -129,4 +144,18 @@
+ .filter(i -> !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-vineflower-stripped/net/minecraft/world/inventory/StonecutterMenu.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/StonecutterMenu.java.patch
new file mode 100644
index 0000000000..8e7c6ff799
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/StonecutterMenu.java.patch
@@ -0,0 +1,97 @@
+--- a/net/minecraft/world/inventory/StonecutterMenu.java
++++ b/net/minecraft/world/inventory/StonecutterMenu.java
+@@ -17,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;
+ public static final int RESULT_SLOT = 1;
+@@ -32,24 +38,54 @@
+ long lastSoundTime;
+ final Slot inputSlot;
+ final Slot resultSlot;
+- Runnable slotUpdateListener = () -> {
+- };
+- public final Container container = new SimpleContainer(1) {
+- @Override
+- public void setChanged() {
+- super.setChanged();
+- StonecutterMenu.this.slotsChanged(this);
+- StonecutterMenu.this.slotUpdateListener.run();
++ Runnable slotUpdateListener;
++ public final Container container;
++ final ResultContainer resultContainer;
++ // CraftBukkit start
++ private CraftInventoryView bukkitEntity = null;
++ private Player player;
++
++ @Override
++ public CraftInventoryView getBukkitView() {
++ if (bukkitEntity != null) {
++ return bukkitEntity;
+ }
+ };
+ final ResultContainer resultContainer = new ResultContainer();
+
++ CraftInventoryStonecutter inventory = new CraftInventoryStonecutter(this.container, this.resultContainer);
++ bukkitEntity = new CraftInventoryView(this.player, inventory, this);
++ return bukkitEntity;
++ }
++ // CraftBukkit end
++
+ public StonecutterMenu(int containerId, Inventory playerInventory) {
+ this(containerId, playerInventory, ContainerLevelAccess.NULL);
+ }
+
+ public StonecutterMenu(int containerId, Inventory playerInventory, final ContainerLevelAccess access) {
+ super(MenuType.STONECUTTER, containerId);
++ this.selectedRecipeIndex = DataSlot.standalone();
++ this.recipes = Lists.newArrayList();
++ this.input = ItemStack.EMPTY;
++ this.slotUpdateListener = () -> {
++ };
++ this.container = new SimpleContainer(1) {
++ @Override
++ public void setChanged() {
++ super.setChanged();
++ 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 = access;
+ this.level = playerInventory.player.level();
+ this.inputSlot = this.addSlot(new Slot(this.container, 0, 20, 33));
+@@ -94,6 +133,7 @@
+ }
+
+ this.addDataSlot(this.selectedRecipeIndex);
++ player = (Player) playerInventory.player.getBukkitEntity(); // CraftBukkit
+ }
+
+ public int getSelectedRecipeIndex() {
+@@ -113,7 +153,8 @@
+ }
+
+ @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-vineflower-stripped/net/minecraft/world/inventory/TransientCraftingContainer.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/TransientCraftingContainer.java.patch
new file mode 100644
index 0000000000..1914af6287
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/inventory/TransientCraftingContainer.java.patch
@@ -0,0 +1,88 @@
+--- a/net/minecraft/world/inventory/TransientCraftingContainer.java
++++ b/net/minecraft/world/inventory/TransientCraftingContainer.java
+@@ -7,12 +9,84 @@
+ 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
++
++public class TransientCraftingContainer implements InventoryCrafting {
++
+ private final NonNullList<ItemStack> items;
+ private final int width;
+ private final int height;
+ private final AbstractContainerMenu menu;
+
++ // 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 void onOpen(CraftHumanEntity who) {
++ transaction.add(who);
++ }
++
++ public InventoryType getInvType() {
++ return items.size() == 4 ? InventoryType.CRAFTING : InventoryType.WORKBENCH;
++ }
++
++ public void onClose(CraftHumanEntity who) {
++ transaction.remove(who);
++ }
++
++ public List<HumanEntity> getViewers() {
++ return transaction;
++ }
++
++ public org.bukkit.inventory.InventoryHolder getOwner() {
++ return (owner == null) ? null : owner.getBukkitEntity();
++ }
++
++ @Override
++ public int getMaxStackSize() {
++ return maxStack;
++ }
++
++ public void setMaxStackSize(int size) {
++ maxStack = size;
++ resultInventory.setMaxStackSize(size);
++ }
++
++ @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));
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/ArmorItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/ArmorItem.java.patch
new file mode 100644
index 0000000000..0074fd1f09
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/ArmorItem.java.patch
@@ -0,0 +1,64 @@
+--- a/net/minecraft/world/item/ArmorItem.java
++++ b/net/minecraft/world/item/ArmorItem.java
+@@ -25,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 {
+ private static final EnumMap<ArmorItem.Type, UUID> ARMOR_MODIFIER_UUID_PER_TYPE = Util.make(new EnumMap<>(ArmorItem.Type.class), map -> {
+@@ -55,15 +60,42 @@
+ if (entitiesOfClass.isEmpty()) {
+ return false;
+ } else {
+- LivingEntity livingEntity = entitiesOfClass.get(0);
+- EquipmentSlot equipmentSlotForItem = Mob.getEquipmentSlotForItem(itemStack);
+- ItemStack itemStack1 = itemStack.split(1);
+- livingEntity.setItemSlot(equipmentSlotForItem, itemStack1);
+- if (livingEntity instanceof Mob) {
+- ((Mob)livingEntity).setDropChance(equipmentSlotForItem, 2.0F);
+- ((Mob)livingEntity).setPersistenceRequired();
++ LivingEntity entityliving = (LivingEntity) list.get(0);
++ EquipmentSlot enumitemslot = 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);
++
++ BlockDispenseArmorEvent event = new BlockDispenseArmorEvent(block, craftItem.clone(), (org.bukkit.craftbukkit.entity.CraftLivingEntity) entityliving.getBukkitEntity());
++ if (!DispenserBlock.eventFired) {
++ world.getCraftServer().getPluginManager().callEvent(event);
+ }
+
++ 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-vineflower-stripped/net/minecraft/world/item/ArmorStandItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/ArmorStandItem.java.patch
new file mode 100644
index 0000000000..4c3a37c97b
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/ArmorStandItem.java.patch
@@ -0,0 +1,27 @@
+--- a/net/minecraft/world/item/ArmorStandItem.java
++++ b/net/minecraft/world/item/ArmorStandItem.java
+@@ -44,13 +50,17 @@
+ return InteractionResult.FAIL;
+ }
+
+- float f = (float)Mth.floor((Mth.wrapDegrees(context.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(
+- null, armorStand.getX(), armorStand.getY(), armorStand.getZ(), SoundEvents.ARMOR_STAND_PLACE, SoundSource.BLOCKS, 0.75F, 0.8F
+- );
+- armorStand.gameEvent(GameEvent.ENTITY_PLACE, context.getPlayer());
++ float f = (float) Mth.floor((Mth.wrapDegrees(context.getRotation() - 180.0F) + 22.5F) / 45.0F) * 45.0F;
++
++ 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());
+ }
+
+ itemInHand.shrink(1);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/BlockItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/BlockItem.java.patch
new file mode 100644
index 0000000000..d8963fdd26
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/BlockItem.java.patch
@@ -0,0 +1,159 @@
+--- a/net/minecraft/world/item/BlockItem.java
++++ b/net/minecraft/world/item/BlockItem.java
+@@ -29,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 {
+ public static final String BLOCK_ENTITY_TAG = "BlockEntityTag";
+@@ -62,38 +74,53 @@
+ if (blockPlaceContext == null) {
+ return InteractionResult.FAIL;
+ } else {
+- BlockState placementState = this.getPlacementState(blockPlaceContext);
+- if (placementState == null) {
++ 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 (iblockdata == null) {
+ return InteractionResult.FAIL;
+ } else if (!this.placeBlock(blockPlaceContext, placementState)) {
+ return InteractionResult.FAIL;
+ } else {
+- BlockPos clickedPos = blockPlaceContext.getClickedPos();
+- Level level = blockPlaceContext.getLevel();
+- Player player = blockPlaceContext.getPlayer();
+- ItemStack itemInHand = blockPlaceContext.getItemInHand();
+- BlockState blockState = level.getBlockState(clickedPos);
+- if (blockState.is(placementState.getBlock())) {
+- blockState = this.updateBlockStateFromTag(clickedPos, level, itemInHand, blockState);
+- this.updateCustomBlockEntityTag(clickedPos, level, player, itemInHand, blockState);
+- blockState.getBlock().setPlacedBy(level, clickedPos, blockState, player, itemInHand);
+- if (player instanceof ServerPlayer) {
+- CriteriaTriggers.PLACED_BLOCK.trigger((ServerPlayer)player, clickedPos, itemInHand);
++ BlockPos blockposition = blockactioncontext1.getClickedPos();
++ Level world = blockactioncontext1.getLevel();
++ Player entityhuman = blockactioncontext1.getPlayer();
++ ItemStack itemstack = blockactioncontext1.getItemInHand();
++ IBlockData iblockdata1 = world.getBlockState(blockposition);
++
++ 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 = blockState.getSoundType();
+- level.playSound(
+- player,
+- clickedPos,
+- this.getPlaceSound(blockState),
+- SoundSource.BLOCKS,
+- (soundType.getVolume() + 1.0F) / 2.0F,
+- soundType.getPitch() * 0.8F
+- );
+- level.gameEvent(GameEvent.BLOCK_PLACE, clickedPos, GameEvent.Context.of(player, blockState));
+- if (player == null || !player.getAbilities().instabuild) {
+- itemInHand.shrink(1);
++ SoundType soundeffecttype = iblockdata1.getSoundType();
++
++ // 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);
+ }
+
+ return InteractionResult.sidedSuccess(level.isClientSide);
+@@ -128,13 +153,10 @@
+ CompoundTag compound = tag.getCompound("BlockStateTag");
+ StateDefinition<Block, BlockState> stateDefinition = state.getBlock().getStateDefinition();
+
+- for (String string : compound.getAllKeys()) {
+- Property<?> property = stateDefinition.getProperty(string);
+- if (property != null) {
+- String asString = compound.get(string).getAsString();
+- blockState = updateState(blockState, property, asString);
+- }
+- }
++ if (nbttagcompound != null) {
++ CompoundTag nbttagcompound1 = nbttagcompound.getCompound("BlockStateTag");
++ // CraftBukkit start
++ iblockdata1 = getBlockState(iblockdata1, nbttagcompound1);
+ }
+
+ if (blockState != state) {
+@@ -144,8 +166,25 @@
+ return blockState;
+ }
+
+- private static <T extends Comparable<T>> BlockState updateState(BlockState state, Property<T> property, String valueIdentifier) {
+- return property.getValue(valueIdentifier).map(comparable -> state.setValue(property, comparable)).orElse(state);
++ 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<?> iblockstate = blockstatelist.getProperty(s);
++
++ if (iblockstate != null) {
++ String s1 = nbttagcompound1.get(s).getAsString();
++
++ iblockdata1 = updateState(iblockdata1, iblockstate, s1);
++ }
++ }
++ }
++ return iblockdata1;
+ }
+
+ protected boolean canPlace(BlockPlaceContext context, BlockState state) {
+@@ -155,6 +193,20 @@
+ && context.getLevel().isUnobstructed(state, context.getClickedPos(), collisionContext);
+ }
+
++ 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;
++
++ 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() {
+ return true;
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/BoatItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/BoatItem.java.patch
new file mode 100644
index 0000000000..28d2866305
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/BoatItem.java.patch
@@ -0,0 +1,43 @@
+--- a/net/minecraft/world/item/BoatItem.java
++++ b/net/minecraft/world/item/BoatItem.java
+@@ -51,16 +59,32 @@
+ }
+ }
+
+- if (playerPOVHitResult.getType() == HitResult.Type.BLOCK) {
+- Boat boat = this.getBoat(level, playerPOVHitResult, itemInHand, player);
+- boat.setVariant(this.type);
+- boat.setYRot(player.getYRot());
+- if (!level.noCollision(boat, boat.getBoundingBox())) {
+- return InteractionResultHolder.fail(itemInHand);
++ 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());
++
++ 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(player, GameEvent.ENTITY_PLACE, playerPOVHitResult.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) {
+ itemInHand.shrink(1);
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/BoneMealItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/BoneMealItem.java.patch
new file mode 100644
index 0000000000..8dec6340c2
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/BoneMealItem.java.patch
@@ -0,0 +1,47 @@
+--- a/net/minecraft/world/item/BoneMealItem.java
++++ b/net/minecraft/world/item/BoneMealItem.java
+@@ -34,23 +34,31 @@
+
+ @Override
+ public InteractionResult useOn(UseOnContext context) {
+- Level level = context.getLevel();
+- BlockPos clickedPos = context.getClickedPos();
+- BlockPos blockPos = clickedPos.relative(context.getClickedFace());
+- if (growCrop(context.getItemInHand(), level, clickedPos)) {
+- if (!level.isClientSide) {
+- context.getPlayer().gameEvent(GameEvent.ITEM_INTERACT_FINISH);
+- level.levelEvent(1505, clickedPos, 0);
++ // CraftBukkit start - extract bonemeal application logic to separate, static method
++ return applyBonemeal(context);
++ }
++
++ 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);
+ } else {
+- BlockState blockState = level.getBlockState(clickedPos);
+- boolean isFaceSturdy = blockState.isFaceSturdy(level, clickedPos, context.getClickedFace());
+- if (isFaceSturdy && growWaterPlant(context.getItemInHand(), level, blockPos, context.getClickedFace())) {
+- if (!level.isClientSide) {
+- context.getPlayer().gameEvent(GameEvent.ITEM_INTERACT_FINISH);
+- level.levelEvent(1505, blockPos, 0);
++ IBlockData iblockdata = world.getBlockState(blockposition);
++ boolean flag = iblockdata.isFaceSturdy(world, blockposition, itemactioncontext.getClickedFace());
++
++ 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-vineflower-stripped/net/minecraft/world/item/BowItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/BowItem.java.patch
new file mode 100644
index 0000000000..9c7d43a137
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/BowItem.java.patch
@@ -0,0 +1,34 @@
+--- a/net/minecraft/world/item/BowItem.java
++++ b/net/minecraft/world/item/BowItem.java
+@@ -56,13 +64,30 @@
+ if (EnchantmentHelper.getItemEnchantmentLevel(Enchantments.FLAMING_ARROWS, stack) > 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
+
+ stack.hurtAndBreak(1, player, player1 -> player1.broadcastBreakEvent(player.getUsedItemHand()));
+ if (flag1 || player.getAbilities().instabuild && (projectile.is(Items.SPECTRAL_ARROW) || projectile.is(Items.TIPPED_ARROW))) {
+ 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(
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/BucketItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/BucketItem.java.patch
new file mode 100644
index 0000000000..744bc0754d
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/BucketItem.java.patch
@@ -0,0 +1,138 @@
+--- a/net/minecraft/world/item/BucketItem.java
++++ b/net/minecraft/world/item/BucketItem.java
+@@ -27,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 {
+ private final Fluid content;
+@@ -47,27 +56,39 @@
+ } else if (playerPOVHitResult.getType() != HitResult.Type.BLOCK) {
+ return InteractionResultHolder.pass(itemInHand);
+ } else {
+- BlockPos blockPos = playerPOVHitResult.getBlockPos();
+- Direction direction = playerPOVHitResult.getDirection();
+- BlockPos blockPos1 = blockPos.relative(direction);
+- if (!level.mayInteract(player, blockPos) || !player.mayUseItemAt(blockPos1, direction, itemInHand)) {
+- return InteractionResultHolder.fail(itemInHand);
+- } else if (this.content == Fluids.EMPTY) {
+- BlockState blockState = level.getBlockState(blockPos);
+- if (blockState.getBlock() instanceof BucketPickup bucketPickup) {
+- ItemStack itemStack = bucketPickup.pickupBlock(player, level, blockPos, blockState);
+- if (!itemStack.isEmpty()) {
+- player.awardStat(Stats.ITEM_USED.get(this));
+- bucketPickup.getPickupSound().ifPresent(soundEvent -> player.playSound(soundEvent, 1.0F, 1.0F));
+- level.gameEvent(player, GameEvent.FLUID_PICKUP, blockPos);
+- ItemStack itemStack1 = ItemUtils.createFilledResult(itemInHand, player, itemStack);
+- if (!level.isClientSide) {
+- CriteriaTriggers.FILLED_BUCKET.trigger((ServerPlayer)player, itemStack);
++ BlockPos blockposition = movingobjectpositionblock.getBlockPos();
++ Direction enumdirection = movingobjectpositionblock.getDirection();
++ BlockPos blockposition1 = blockposition.relative(enumdirection);
++
++ if (level.mayInteract(player, blockposition) && player.mayUseItemAt(blockposition1, enumdirection, itemstack)) {
++ IBlockData iblockdata;
++
++ if (this.content == Fluids.EMPTY) {
++ iblockdata = level.getBlockState(blockposition);
++ Block block = iblockdata.getBlock();
++
++ if (block instanceof BucketPickup) {
++ 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);
+
+- return InteractionResultHolder.sidedSuccess(itemStack1, level.isClientSide());
+- }
+- }
++ if (!itemstack1.isEmpty()) {
++ player.awardStat(Stats.ITEM_USED.get(this));
++ ifluidsource.getPickupSound().ifPresent((soundeffect) -> {
++ player.playSound(soundeffect, 1.0F, 1.0F);
++ });
++ level.gameEvent((Entity) player, GameEvent.FLUID_PICKUP, blockposition);
++ ItemStack itemstack2 = ItemUtils.createFilledResult(itemstack, player, CraftItemStack.asNMSCopy(event.getItemStack())); // CraftBukkit
+
+ return InteractionResultHolder.fail(itemInHand);
+ } else {
+@@ -82,7 +100,20 @@
+ player.awardStat(Stats.ITEM_USED.get(this));
+ return InteractionResultHolder.sidedSuccess(getEmptySuccessItem(itemInHand, player), level.isClientSide());
+ } else {
+- return InteractionResultHolder.fail(itemInHand);
++ iblockdata = level.getBlockState(blockposition);
++ BlockPos blockposition2 = iblockdata.getBlock() instanceof LiquidBlockContainer && this.content == Fluids.WATER ? blockposition : blockposition1;
++
++ 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, blockposition2, itemstack);
++ }
++
++ player.awardStat(Stats.ITEM_USED.get(this));
++ return InteractionResultHolder.sidedSuccess(getEmptySuccessItem(itemstack, player), level.isClientSide());
++ } else {
++ return InteractionResultHolder.fail(itemstack);
++ }
+ }
+ }
+ }
+@@ -98,7 +130,15 @@
+
+ @Override
+ public boolean emptyContents(@Nullable Player player, Level level, BlockPos pos, @Nullable BlockHitResult result) {
+- if (!(this.content instanceof FlowingFluid flowingFluid)) {
++ // CraftBukkit start
++ return emptyContents(player, level, pos, result, null, null, null, EnumHand.MAIN_HAND);
++ }
++
++ 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 {
+ Block block;
+@@ -134,9 +173,22 @@
+ player, pos, SoundEvents.FIRE_EXTINGUISH, SoundSource.BLOCKS, 0.5F, 2.6F + (level.random.nextFloat() - level.random.nextFloat()) * 0.8F
+ );
+
+- for (int i = 0; i < 8; i++) {
+- level.addParticle(ParticleTypes.LARGE_SMOKE, (double)x + Math.random(), (double)y + Math.random(), (double)z + Math.random(), 0.0, 0.0, 0.0);
++ // 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 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();
+
+ return true;
+ } else {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/ChorusFruitItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/ChorusFruitItem.java.patch
new file mode 100644
index 0000000000..b5bec9cc49
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/ChorusFruitItem.java.patch
@@ -0,0 +1,30 @@
+--- a/net/minecraft/world/item/ChorusFruitItem.java
++++ b/net/minecraft/world/item/ChorusFruitItem.java
+@@ -33,11 +33,22 @@
+ entityLiving.stopRiding();
+ }
+
+- Vec3 vec3 = entityLiving.position();
+- if (entityLiving.randomTeleport(d, d1, d2, true)) {
+- level.gameEvent(GameEvent.TELEPORT, vec3, GameEvent.Context.of(entityLiving));
+- SoundSource soundSource;
+- SoundEvent soundEvent;
++ Vec3 vec3d = entityLiving.position();
++
++ // 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 (!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) {
+ soundEvent = SoundEvents.FOX_TELEPORT;
+ soundSource = SoundSource.NEUTRAL;
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/CrossbowItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/CrossbowItem.java.patch
new file mode 100644
index 0000000000..49ad59c5bf
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/CrossbowItem.java.patch
@@ -0,0 +1,34 @@
+--- a/net/minecraft/world/item/CrossbowItem.java
++++ b/net/minecraft/world/item/CrossbowItem.java
+@@ -234,10 +236,28 @@
+ Vector3f vector3f = viewVector.toVector3f().rotate(quaternionf);
+ projectile.shoot((double)vector3f.x(), (double)vector3f.y(), (double)vector3f.z(), velocity, inaccuracy);
+ }
++ // 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
+
+- crossbowStack.hurtAndBreak(isFireworkRocket ? 3 : 1, shooter, contextEntity -> contextEntity.broadcastBreakEvent(hand));
+- level.addFreshEntity(projectile);
+- level.playSound(null, shooter.getX(), shooter.getY(), shooter.getZ(), SoundEvents.CROSSBOW_SHOOT, SoundSource.PLAYERS, 1.0F, soundPitch);
++ crossbowStack.hurtAndBreak(flag1 ? 3 : 1, shooter, (entityliving1) -> {
++ entityliving1.broadcastBreakEvent(hand);
++ });
++ // 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-vineflower-stripped/net/minecraft/world/item/DebugStickItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/DebugStickItem.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/DebugStickItem.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/DyeItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/DyeItem.java.patch
new file mode 100644
index 0000000000..0d41c2aec7
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/DyeItem.java.patch
@@ -0,0 +1,38 @@
+--- a/net/minecraft/world/item/DyeItem.java
++++ b/net/minecraft/world/item/DyeItem.java
+@@ -11,7 +12,7 @@
+ import net.minecraft.world.entity.player.Player;
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.block.entity.SignBlockEntity;
+-import net.minecraft.world.level.block.entity.SignText;
++import org.bukkit.event.entity.SheepDyeWoolEvent; // CraftBukkit
+
+ public class DyeItem extends Item implements SignApplicator {
+ private static final Map<DyeColor, DyeItem> ITEM_BY_COLOR = Maps.newEnumMap(DyeColor.class);
+@@ -32,7 +30,25 @@
+ stack.shrink(1);
+ }
+
+- return InteractionResult.sidedSuccess(player.level().isClientSide);
++ if (entitysheep.isAlive() && !entitysheep.isSheared() && entitysheep.getColor() != this.dyeColor) {
++ entitysheep.level().playSound(player, (Entity) entitysheep, SoundEvents.DYE_USE, SoundSource.PLAYERS, 1.0F, 1.0F);
++ if (!player.level().isClientSide) {
++ // 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);
++ }
+ }
+
+ return InteractionResult.PASS;
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/EggItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/EggItem.java.patch
new file mode 100644
index 0000000000..07e2bf1538
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/EggItem.java.patch
@@ -0,0 +1,42 @@
+--- a/net/minecraft/world/item/EggItem.java
++++ b/net/minecraft/world/item/EggItem.java
+@@ -15,23 +16,23 @@
+ }
+
+ @Override
+- public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand hand) {
+- ItemStack itemInHand = player.getItemInHand(hand);
+- level.playSound(
+- null,
+- player.getX(),
+- player.getY(),
+- player.getZ(),
+- SoundEvents.EGG_THROW,
+- SoundSource.PLAYERS,
+- 0.5F,
+- 0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F)
+- );
++ public InteractionResultHolder<ItemStack> use(Level level, Player player, EnumHand hand) {
++ ItemStack itemstack = player.getItemInHand(hand);
++
++ // 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(itemInHand);
+- thrownEgg.shootFromRotation(player, player.getXRot(), player.getYRot(), 0.0F, 1.5F, 1.0F);
+- level.addFreshEntity(thrownEgg);
++ ThrownEgg entityegg = new ThrownEgg(level, player);
++
++ 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-vineflower-stripped/net/minecraft/world/item/EndCrystalItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/EndCrystalItem.java.patch
new file mode 100644
index 0000000000..d755b53015
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/EndCrystalItem.java.patch
@@ -0,0 +1,33 @@
+--- a/net/minecraft/world/item/EndCrystalItem.java
++++ b/net/minecraft/world/item/EndCrystalItem.java
+@@ -38,15 +42,22 @@
+ if (!entities.isEmpty()) {
+ return InteractionResult.FAIL;
+ } else {
+- if (level instanceof ServerLevel) {
+- EndCrystal endCrystal = new EndCrystal(level, d + 0.5, d1, d2 + 0.5);
+- endCrystal.setShowBottom(false);
+- level.addFreshEntity(endCrystal);
+- level.gameEvent(context.getPlayer(), GameEvent.ENTITY_PLACE, blockPos);
+- EndDragonFight dragonFight = ((ServerLevel)level).getDragonFight();
+- if (dragonFight != null) {
+- dragonFight.tryRespawn();
++ if (world instanceof ServerLevel) {
++ EndCrystal entityendercrystal = new EndCrystal(world, d0 + 0.5D, d1, d2 + 0.5D);
++
++ 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 (enderdragonbattle != null) {
++ enderdragonbattle.tryRespawn();
++ }
+ }
+
+ context.getItemInHand().shrink(1);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/EnderEyeItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/EnderEyeItem.java.patch
new file mode 100644
index 0000000000..3d409494f3
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/EnderEyeItem.java.patch
@@ -0,0 +1,32 @@
+--- a/net/minecraft/world/item/EnderEyeItem.java
++++ b/net/minecraft/world/item/EnderEyeItem.java
+@@ -71,14 +79,21 @@
+ return InteractionResultHolder.pass(itemInHand);
+ } else {
+ player.startUsingItem(hand);
+- if (level instanceof ServerLevel serverLevel) {
+- BlockPos blockPos = serverLevel.findNearestMapStructure(StructureTags.EYE_OF_ENDER_LOCATED, player.blockPosition(), 100, false);
+- if (blockPos != null) {
+- EyeOfEnder eyeOfEnder = new EyeOfEnder(level, player.getX(), player.getY(0.5), player.getZ());
+- eyeOfEnder.setItem(itemInHand);
+- eyeOfEnder.signalTo(blockPos);
+- level.gameEvent(GameEvent.PROJECTILE_SHOOT, eyeOfEnder.position(), GameEvent.Context.of(player));
+- level.addFreshEntity(eyeOfEnder);
++ if (level instanceof ServerLevel) {
++ ServerLevel worldserver = (ServerLevel) level;
++ BlockPos blockposition = worldserver.findNearestMapStructure(StructureTags.EYE_OF_ENDER_LOCATED, player.blockPosition(), 100, false);
++
++ if (blockposition != null) {
++ EyeOfEnder entityendersignal = new EyeOfEnder(level, player.getX(), player.getY(0.5D), player.getZ());
++
++ 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-vineflower-stripped/net/minecraft/world/item/EnderpearlItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/EnderpearlItem.java.patch
new file mode 100644
index 0000000000..4851eaadc8
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/EnderpearlItem.java.patch
@@ -0,0 +1,37 @@
+--- a/net/minecraft/world/item/EnderpearlItem.java
++++ b/net/minecraft/world/item/EnderpearlItem.java
+@@ -15,19 +16,10 @@
+ }
+
+ @Override
+- public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand hand) {
+- ItemStack itemInHand = player.getItemInHand(hand);
+- level.playSound(
+- 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);
++ public InteractionResultHolder<ItemStack> use(Level level, Player player, EnumHand hand) {
++ ItemStack itemstack = player.getItemInHand(hand);
++
++ // CraftBukkit start - change order
+ if (!level.isClientSide) {
+ ThrownEnderpearl thrownEnderpearl = new ThrownEnderpearl(level, player);
+ thrownEnderpearl.setItem(itemInHand);
+@@ -35,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) {
+ itemInHand.shrink(1);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/FireChargeItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/FireChargeItem.java.patch
new file mode 100644
index 0000000000..3db64e44df
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/FireChargeItem.java.patch
@@ -0,0 +1,47 @@
+--- a/net/minecraft/world/item/FireChargeItem.java
++++ b/net/minecraft/world/item/FireChargeItem.java
+@@ -26,18 +29,35 @@
+ BlockPos clickedPos = context.getClickedPos();
+ BlockState blockState = level.getBlockState(clickedPos);
+ boolean flag = false;
+- if (!CampfireBlock.canLight(blockState) && !CandleBlock.canLight(blockState) && !CandleCakeBlock.canLight(blockState)) {
+- BlockPos var6 = clickedPos.relative(context.getClickedFace());
+- if (BaseFireBlock.canBePlacedAt(level, var6, context.getHorizontalDirection())) {
+- this.playSound(level, var6);
+- level.setBlockAndUpdate(var6, BaseFireBlock.getState(level, var6));
+- level.gameEvent(context.getPlayer(), GameEvent.BLOCK_PLACE, var6);
++
++ 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, clickedPos);
+- level.setBlockAndUpdate(clickedPos, blockState.setValue(BlockStateProperties.LIT, Boolean.valueOf(true)));
+- level.gameEvent(context.getPlayer(), GameEvent.BLOCK_CHANGE, clickedPos);
++ // 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-vineflower-stripped/net/minecraft/world/item/FishingRodItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/FishingRodItem.java.patch
new file mode 100644
index 0000000000..cc2d211ac0
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/FishingRodItem.java.patch
@@ -0,0 +1,42 @@
+--- a/net/minecraft/world/item/FishingRodItem.java
++++ b/net/minecraft/world/item/FishingRodItem.java
+@@ -11,7 +11,13 @@
+ 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 class FishingRodItem extends Item implements ItemVanishable {
++
+ public FishingRodItem(Item.Properties properties) {
+ super(properties);
+ }
+@@ -48,9 +40,21 @@
+ 0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F)
+ );
+ if (!level.isClientSide) {
+- int i = EnchantmentHelper.getFishingSpeedBonus(itemInHand);
+- int fishingLuckBonus = EnchantmentHelper.getFishingLuckBonus(itemInHand);
+- level.addFreshEntity(new FishingHook(player, level, fishingLuckBonus, i));
++ i = EnchantmentHelper.getFishingSpeedBonus(itemstack);
++ int j = EnchantmentHelper.getFishingLuckBonus(itemstack);
++
++ // 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-vineflower-stripped/net/minecraft/world/item/FlintAndSteelItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/FlintAndSteelItem.java.patch
new file mode 100644
index 0000000000..33db34830e
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/FlintAndSteelItem.java.patch
@@ -0,0 +1,70 @@
+--- a/net/minecraft/world/item/FlintAndSteelItem.java
++++ b/net/minecraft/world/item/FlintAndSteelItem.java
+@@ -24,34 +26,47 @@
+
+ @Override
+ public InteractionResult useOn(UseOnContext context) {
+- Player player = context.getPlayer();
+- Level level = context.getLevel();
+- BlockPos clickedPos = context.getClickedPos();
+- BlockState blockState = level.getBlockState(clickedPos);
+- if (!CampfireBlock.canLight(blockState) && !CandleBlock.canLight(blockState) && !CandleCakeBlock.canLight(blockState)) {
+- BlockPos blockPos = clickedPos.relative(context.getClickedFace());
+- if (BaseFireBlock.canBePlacedAt(level, blockPos, context.getHorizontalDirection())) {
+- level.playSound(player, blockPos, SoundEvents.FLINTANDSTEEL_USE, SoundSource.BLOCKS, 1.0F, level.getRandom().nextFloat() * 0.4F + 0.8F);
+- BlockState state = BaseFireBlock.getState(level, blockPos);
+- level.setBlock(blockPos, state, 11);
+- level.gameEvent(player, GameEvent.BLOCK_PLACE, clickedPos);
+- ItemStack itemInHand = context.getItemInHand();
+- if (player instanceof ServerPlayer) {
+- CriteriaTriggers.PLACED_BLOCK.trigger((ServerPlayer)player, blockPos, itemInHand);
+- itemInHand.hurtAndBreak(1, player, contextPlayer -> contextPlayer.broadcastBreakEvent(context.getHand()));
++ Player entityhuman = context.getPlayer();
++ Level world = context.getLevel();
++ BlockPos blockposition = context.getClickedPos();
++ IBlockData iblockdata = world.getBlockState(blockposition);
++
++ if (!CampfireBlock.canLight(iblockdata) && !CandleBlock.canLight(iblockdata) && !CandleCakeBlock.canLight(iblockdata)) {
++ BlockPos blockposition1 = blockposition.relative(context.getClickedFace());
++
++ 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);
+
+ return InteractionResult.sidedSuccess(level.isClientSide());
+ } else {
+ return InteractionResult.FAIL;
+ }
+ } else {
+- level.playSound(player, clickedPos, SoundEvents.FLINTANDSTEEL_USE, SoundSource.BLOCKS, 1.0F, level.getRandom().nextFloat() * 0.4F + 0.8F);
+- level.setBlock(clickedPos, blockState.setValue(BlockStateProperties.LIT, Boolean.valueOf(true)), 11);
+- level.gameEvent(player, GameEvent.BLOCK_CHANGE, clickedPos);
+- if (player != null) {
+- context.getItemInHand().hurtAndBreak(1, player, contextPlayer -> contextPlayer.broadcastBreakEvent(context.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());
++ });
++ return InteractionResult.PASS;
+ }
++ // 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-vineflower-stripped/net/minecraft/world/item/HangingEntityItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/HangingEntityItem.java.patch
new file mode 100644
index 0000000000..af58c26e59
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/HangingEntityItem.java.patch
@@ -0,0 +1,44 @@
+--- 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);
+ private final EntityType<? extends HangingEntity> type;
+@@ -65,11 +74,24 @@
+ EntityType.updateCustomEntityTag(level, player, hangingEntity, tag);
+ }
+
+- if (hangingEntity.survives()) {
+- if (!level.isClientSide) {
+- hangingEntity.playPlacementSound();
+- level.gameEvent(player, GameEvent.ENTITY_PLACE, hangingEntity.position());
+- level.addFreshEntity(hangingEntity);
++ if (((HangingEntity) object).survives()) {
++ 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();
++ world.gameEvent((Entity) entityhuman, GameEvent.ENTITY_PLACE, ((HangingEntity) object).position());
++ world.addFreshEntity((Entity) object);
+ }
+
+ itemInHand.shrink(1);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/ItemStack.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/ItemStack.java.patch
new file mode 100644
index 0000000000..e318ac0812
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/ItemStack.java.patch
@@ -0,0 +1,327 @@
+--- 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 -> instance.group(
+@@ -181,11 +211,22 @@
+ this.item = null;
+ }
+
+- private ItemStack(CompoundTag compoundTag) {
+- this.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);
+ }
+
+@@ -194,6 +236,11 @@
+ }
+ }
+
++ private ItemStack(CompoundTag compoundTag) {
++ this.load(compoundTag);
++ // CraftBukkit end
++ }
++
+ public static ItemStack of(CompoundTag compoundTag) {
+ try {
+ return new ItemStack(compoundTag);
+@@ -270,12 +318,169 @@
+ return InteractionResult.PASS;
+ } else {
+ Item item = this.getItem();
+- InteractionResult interactionResult = item.useOn(context);
+- if (player != null && interactionResult.shouldAwardStats()) {
+- player.awardStat(Stats.ITEM_USED.get(item));
++ // CraftBukkit start - handle all block place event logic here
++ CompoundTag oldData = this.getTagClone();
++ int oldCount = this.getCount();
++ ServerLevel world = (ServerLevel) context.getLevel();
++
++ if (!(item instanceof BucketItem || item instanceof SolidBucketItem)) { // if not bucket
++ world.captureBlockStates = true;
++ // special case bonemeal
++ if (item == Items.BONE_MEAL) {
++ world.captureTreeGeneration = true;
++ }
+ }
+
+- 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;
+ }
+ }
+
+@@ -349,7 +581,22 @@
+ }
+ }
+
+- amount -= i;
++ 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;
+ }
+@@ -371,6 +618,12 @@
+ if (this.hurt(amount, entity.getRandom(), entity instanceof ServerPlayer ? (ServerPlayer)entity : null)) {
+ onBroken.accept(entity);
+ 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 (entity instanceof Player) {
+ ((Player)entity).awardStat(Stats.ITEM_BROKEN.get(item));
+@@ -513,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());
+@@ -925,6 +1210,13 @@
+ list.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());
+ if (this.hasCustomHoverName()) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/LeadItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/LeadItem.java.patch
new file mode 100644
index 0000000000..db1ad8e4ff
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/LeadItem.java.patch
@@ -0,0 +1,99 @@
+--- a/net/minecraft/world/item/LeadItem.java
++++ b/net/minecraft/world/item/LeadItem.java
+@@ -11,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 {
+ public LeadItem(Item.Properties properties) {
+@@ -19,13 +27,15 @@
+
+ @Override
+ public InteractionResult useOn(UseOnContext context) {
+- Level level = context.getLevel();
+- BlockPos clickedPos = context.getClickedPos();
+- BlockState blockState = level.getBlockState(clickedPos);
+- if (blockState.is(BlockTags.FENCES)) {
+- Player player = context.getPlayer();
+- if (!level.isClientSide && player != null) {
+- bindPlayerMobs(player, level, clickedPos);
++ Level world = context.getLevel();
++ BlockPos blockposition = context.getClickedPos();
++ IBlockData iblockdata = world.getBlockState(blockposition);
++
++ if (iblockdata.is(BlockTags.FENCES)) {
++ Player entityhuman = context.getPlayer();
++
++ if (!world.isClientSide && entityhuman != null) {
++ bindPlayerMobs(entityhuman, world, blockposition, context.getHand()); // CraftBukkit - Pass hand
+ }
+
+ return InteractionResult.sidedSuccess(level.isClientSide);
+@@ -34,24 +44,41 @@
+ }
+ }
+
+- public static InteractionResult bindPlayerMobs(Player player, Level level, BlockPos pos) {
+- 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 d = 7.0;
+ int x = pos.getX();
+ int y = pos.getY();
+ int z = pos.getZ();
+
+- for (Mob mob : level.getEntitiesOfClass(
+- Mob.class, new AABB((double)x - 7.0, (double)y - 7.0, (double)z - 7.0, (double)x + 7.0, (double)y + 7.0, (double)z + 7.0)
+- )) {
+- if (mob.getLeashHolder() == player) {
+- if (leashFenceKnotEntity == null) {
+- leashFenceKnotEntity = LeashFenceKnotEntity.getOrCreateKnot(level, pos);
+- leashFenceKnotEntity.playPlacementSound();
++ while (iterator.hasNext()) {
++ Mob entityinsentient = (Mob) iterator.next();
++
++ 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;
+ }
+ }
+@@ -62,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-vineflower-stripped/net/minecraft/world/item/MapItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/MapItem.java.patch
new file mode 100644
index 0000000000..06b6d7d15a
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/MapItem.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/world/item/MapItem.java
++++ b/net/minecraft/world/item/MapItem.java
+@@ -64,8 +67,9 @@
+
+ @Nullable
+ public static Integer getMapId(ItemStack stack) {
+- CompoundTag tag = stack.getTag();
+- return tag != null && tag.contains("map", 99) ? tag.getInt("map") : null;
++ CompoundTag nbttagcompound = stack.getTag();
++
++ return nbttagcompound != null && nbttagcompound.contains("map", 99) ? nbttagcompound.getInt("map") : -1; // CraftBukkit - make new maps for no tag
+ }
+
+ private static int createNewSavedData(
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/MilkBucketItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/MilkBucketItem.java.patch
new file mode 100644
index 0000000000..45e78e72b9
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -28,7 +31,7 @@
+ }
+
+ if (!level.isClientSide) {
+- entityLiving.removeAllEffects();
++ entityLiving.removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.MILK); // CraftBukkit
+ }
+
+ return stack.isEmpty() ? new ItemStack(Items.BUCKET) : stack;
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/MinecartItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/MinecartItem.java.patch
new file mode 100644
index 0000000000..5c82298462
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/MinecartItem.java.patch
@@ -0,0 +1,90 @@
+--- a/net/minecraft/world/item/MinecartItem.java
++++ b/net/minecraft/world/item/MinecartItem.java
+@@ -17,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 {
+ private static final DispenseItemBehavior DISPENSE_ITEM_BEHAVIOR = new DefaultDispenseItemBehavior() {
+@@ -58,12 +63,40 @@
+ }
+ }
+
+- AbstractMinecart abstractMinecart = AbstractMinecart.createMinecart(
+- serverLevel, d, d1 + d3, d2, ((MinecartItem)itemStack.getItem()).type, itemStack, null
+- );
+- serverLevel.addFreshEntity(abstractMinecart);
+- itemStack.shrink(1);
+- return itemStack;
++ // 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);
++
++ 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;
+ }
+
+ @Override
+@@ -97,19 +132,15 @@
+ d = 0.5;
+ }
+
+- AbstractMinecart abstractMinecart = AbstractMinecart.createMinecart(
+- serverLevel,
+- (double)clickedPos.getX() + 0.5,
+- (double)clickedPos.getY() + 0.0625 + d,
+- (double)clickedPos.getZ() + 0.5,
+- this.type,
+- itemInHand,
+- context.getPlayer()
+- );
+- serverLevel.addFreshEntity(abstractMinecart);
+- serverLevel.gameEvent(
+- GameEvent.ENTITY_PLACE, clickedPos, GameEvent.Context.of(context.getPlayer(), serverLevel.getBlockState(clickedPos.below()))
+- );
++ AbstractMinecart entityminecartabstract = AbstractMinecart.createMinecart(worldserver, (double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.0625D + d0, (double) blockposition.getZ() + 0.5D, this.type, itemstack, context.getPlayer());
++
++ // 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())));
+ }
+
+ itemInHand.shrink(1);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/PotionItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/PotionItem.java.patch
new file mode 100644
index 0000000000..fce2791a33
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -51,7 +60,7 @@
+ if (mobEffectInstance.getEffect().isInstantenous()) {
+ mobEffectInstance.getEffect().applyInstantenousEffect(player, player, entityLiving, mobEffectInstance.getAmplifier(), 1.0);
+ } else {
+- entityLiving.addEffect(new MobEffectInstance(mobEffectInstance));
++ entityLiving.addEffect(new MobEffectInstance(mobeffect), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.POTION_DRINK); // CraftBukkit
+ }
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/RecordItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/RecordItem.java.patch
new file mode 100644
index 0000000000..d1ff6cdcae
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/RecordItem.java.patch
@@ -0,0 +1,36 @@
+--- a/net/minecraft/world/item/RecordItem.java
++++ b/net/minecraft/world/item/RecordItem.java
+@@ -36,16 +38,23 @@
+
+ @Override
+ public InteractionResult useOn(UseOnContext context) {
+- Level level = context.getLevel();
+- BlockPos clickedPos = context.getClickedPos();
+- BlockState blockState = level.getBlockState(clickedPos);
+- if (blockState.is(Blocks.JUKEBOX) && !blockState.getValue(JukeboxBlock.HAS_RECORD)) {
+- ItemStack itemInHand = context.getItemInHand();
+- if (!level.isClientSide) {
+- Player player = context.getPlayer();
+- if (level.getBlockEntity(clickedPos) instanceof JukeboxBlockEntity jukeboxBlockEntity) {
+- jukeboxBlockEntity.setTheItem(itemInHand.copy());
+- level.gameEvent(GameEvent.BLOCK_CHANGE, clickedPos, GameEvent.Context.of(player, blockState));
++ Level world = context.getLevel();
++ BlockPos blockposition = context.getClickedPos();
++ IBlockData iblockdata = world.getBlockState(blockposition);
++
++ if (iblockdata.is(Blocks.JUKEBOX) && !(Boolean) iblockdata.getValue(JukeboxBlock.HAS_RECORD)) {
++ ItemStack itemstack = context.getItemInHand();
++
++ if (!world.isClientSide) {
++ if (true) return InteractionResult.SUCCESS; // CraftBukkit - handled in ItemStack
++ Player entityhuman = context.getPlayer();
++ BlockEntity tileentity = world.getBlockEntity(blockposition);
++
++ if (tileentity instanceof JukeboxBlockEntity) {
++ JukeboxBlockEntity tileentityjukebox = (JukeboxBlockEntity) tileentity;
++
++ tileentityjukebox.setTheItem(itemstack.copy());
++ world.gameEvent(GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of(entityhuman, iblockdata));
+ }
+
+ itemInHand.shrink(1);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/SignItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/SignItem.java.patch
new file mode 100644
index 0000000000..686ee12c1c
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/SignItem.java.patch
@@ -0,0 +1,42 @@
+--- a/net/minecraft/world/item/SignItem.java
++++ b/net/minecraft/world/item/SignItem.java
+@@ -11,6 +12,9 @@
+ import net.minecraft.world.level.block.state.BlockState;
+
+ public class SignItem extends StandingAndWallBlockItem {
++
++ public static BlockPos openSign; // CraftBukkit
++
+ public SignItem(Item.Properties properties, Block standingBlock, Block wallBlock) {
+ super(standingBlock, wallBlock, properties, Direction.DOWN);
+ }
+@@ -22,12 +26,23 @@
+ @Override
+ protected boolean updateCustomBlockEntityTag(BlockPos pos, Level level, @Nullable Player player, ItemStack stack, BlockState state) {
+ boolean flag = super.updateCustomBlockEntityTag(pos, level, player, stack, state);
+- if (!level.isClientSide
+- && !flag
+- && player != null
+- && level.getBlockEntity(pos) instanceof SignBlockEntity signBlockEntity
+- && level.getBlockState(pos).getBlock() instanceof SignBlock signBlock) {
+- signBlock.openTextEdit(player, signBlockEntity, true);
++
++ if (!level.isClientSide && !flag && player != null) {
++ BlockEntity tileentity = level.getBlockEntity(pos);
++
++ if (tileentity instanceof SignBlockEntity) {
++ SignBlockEntity tileentitysign = (SignBlockEntity) tileentity;
++ Block block = level.getBlockState(pos).getBlock();
++
++ if (block instanceof SignBlock) {
++ SignBlock blocksign = (SignBlock) block;
++
++ // CraftBukkit start - SPIGOT-4678
++ // blocksign.openTextEdit(entityhuman, tileentitysign, true);
++ SignItem.openSign = pos;
++ // CraftBukkit end
++ }
++ }
+ }
+
+ return flag;
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/SnowballItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/SnowballItem.java.patch
new file mode 100644
index 0000000000..84d40286bb
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/SnowballItem.java.patch
@@ -0,0 +1,41 @@
+--- a/net/minecraft/world/item/SnowballItem.java
++++ b/net/minecraft/world/item/SnowballItem.java
+@@ -15,28 +16,24 @@
+ }
+
+ @Override
+- public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand hand) {
+- ItemStack itemInHand = player.getItemInHand(hand);
+- level.playSound(
+- null,
+- player.getX(),
+- player.getY(),
+- player.getZ(),
+- SoundEvents.SNOWBALL_THROW,
+- SoundSource.NEUTRAL,
+- 0.5F,
+- 0.4F / (level.getRandom().nextFloat() * 0.4F + 0.8F)
+- );
++ public InteractionResultHolder<ItemStack> use(Level level, Player player, EnumHand hand) {
++ ItemStack itemstack = player.getItemInHand(hand);
++
++ // 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);
+ snowball.setItem(itemInHand);
+ 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) {
+- itemInHand.shrink(1);
++ // CraftBukkit start - moved up
++ /*
++ if (!entityhuman.getAbilities().instabuild) {
++ itemstack.shrink(1);
+ }
+
+ return InteractionResultHolder.sidedSuccess(itemInHand, level.isClientSide());
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/SpawnEggItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/SpawnEggItem.java.patch
new file mode 100644
index 0000000000..dad4efbbc6
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/SpawnEggItem.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/world/item/SpawnEggItem.java
++++ b/net/minecraft/world/item/SpawnEggItem.java
+@@ -176,8 +179,8 @@
+ if (!breedOffspring.isBaby()) {
+ return Optional.empty();
+ } else {
+- breedOffspring.moveTo(pos.x(), pos.y(), pos.z(), 0.0F, 0.0F);
+- serverLevel.addFreshEntityWithPassengers(breedOffspring);
++ ((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()) {
+ breedOffspring.setCustomName(stack.getHoverName());
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/StandingAndWallBlockItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/StandingAndWallBlockItem.java.patch
new file mode 100644
index 0000000000..5c47dd78db
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -9,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 {
+ protected final Block wallBlock;
+@@ -42,7 +54,19 @@
+ }
+ }
+
+- return blockState != null && level.isUnobstructed(blockState, clickedPos, CollisionContext.empty()) ? blockState : 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-vineflower-stripped/net/minecraft/world/item/TridentItem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/TridentItem.java.patch
new file mode 100644
index 0000000000..e2792900ad
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/TridentItem.java.patch
@@ -0,0 +1,100 @@
+--- a/net/minecraft/world/item/TridentItem.java
++++ b/net/minecraft/world/item/TridentItem.java
+@@ -62,39 +68,70 @@
+ int riptide = EnchantmentHelper.getRiptide(stack);
+ if (riptide <= 0 || player.isInWaterOrRain()) {
+ if (!level.isClientSide) {
+- stack.hurtAndBreak(1, player, contextEntity -> contextEntity.broadcastBreakEvent(entityLiving.getUsedItemHand()));
+- if (riptide == 0) {
+- ThrownTrident thrownTrident = new ThrownTrident(level, player, stack);
+- thrownTrident.shootFromRotation(player, player.getXRot(), player.getYRot(), 0.0F, 2.5F + (float)riptide * 0.5F, 1.0F);
+- if (player.getAbilities().instabuild) {
+- thrownTrident.pickup = AbstractArrow.Pickup.CREATIVE_ONLY;
++ // CraftBukkit - moved down
++ /*
++ itemstack.hurtAndBreak(1, entityhuman, (entityhuman1) -> {
++ entityhuman1.broadcastBreakEvent(entityliving.getUsedItemHand());
++ });
++ */
++ if (k == 0) {
++ ThrownTrident entitythrowntrident = new ThrownTrident(level, entityhuman, stack);
++
++ entitythrowntrident.shootFromRotation(entityhuman, entityhuman.getXRot(), entityhuman.getYRot(), 0.0F, 2.5F + (float) k * 0.5F, 1.0F);
++ if (entityhuman.getAbilities().instabuild) {
++ entitythrowntrident.pickup = AbstractArrow.Pickup.CREATIVE_ONLY;
+ }
+
+- level.addFreshEntity(thrownTrident);
+- level.playSound(null, thrownTrident, SoundEvents.TRIDENT_THROW, SoundSource.PLAYERS, 1.0F, 1.0F);
+- if (!player.getAbilities().instabuild) {
+- player.getInventory().removeItem(stack);
++ // 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 (riptide > 0) {
+- float yRot = player.getYRot();
+- float xRot = player.getXRot();
+- float f = -Mth.sin(yRot * (float) (Math.PI / 180.0)) * Mth.cos(xRot * (float) (Math.PI / 180.0));
+- float f1 = -Mth.sin(xRot * (float) (Math.PI / 180.0));
+- float f2 = Mth.cos(yRot * (float) (Math.PI / 180.0)) * Mth.cos(xRot * (float) (Math.PI / 180.0));
+- float squareRoot = Mth.sqrt(f * f + f1 * f1 + f2 * f2);
+- float f3 = 3.0F * ((1.0F + (float)riptide) / 4.0F);
+- f *= f3 / squareRoot;
+- f1 *= f3 / squareRoot;
+- f2 *= f3 / squareRoot;
+- player.push((double)f, (double)f1, (double)f2);
+- player.startAutoSpinAttack(20);
+- if (player.onGround()) {
+- float f4 = 1.1999999F;
+- player.move(MoverType.SELF, new Vec3(0.0, 1.1999999F, 0.0));
++ entityhuman.awardStat(Stats.ITEM_USED.get(this));
++ if (k > 0) {
++ // 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);
++ float f5 = Mth.sqrt(f2 * f2 + f3 * f3 + f4 * f4);
++ float f6 = 3.0F * ((1.0F + (float) k) / 4.0F);
++
++ f2 *= f6 / f5;
++ f3 *= f6 / f5;
++ f4 *= f6 / f5;
++ entityhuman.push((double) f2, (double) f3, (double) f4);
++ entityhuman.startAutoSpinAttack(20);
++ if (entityhuman.onGround()) {
++ float f7 = 1.1999999F;
++
++ entityhuman.move(EnumMoveType.SELF, new Vec3(0.0D, 1.1999999284744263D, 0.0D));
+ }
+
+ SoundEvent soundEvent;
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/BlastingRecipe.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/BlastingRecipe.java.patch
new file mode 100644
index 0000000000..f73982496b
--- /dev/null
+++ b/patch-remap/mache-vineflower-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 string, CookingBookCategory cookingBookCategory, Ingredient ingredient, ItemStack itemStack, float f, int i) {
+ super(RecipeType.BLASTING, string, cookingBookCategory, ingredient, itemStack, f, i);
+@@ -17,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-vineflower-stripped/net/minecraft/world/item/crafting/CampfireCookingRecipe.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/CampfireCookingRecipe.java.patch
new file mode 100644
index 0000000000..c33d2d4a9b
--- /dev/null
+++ b/patch-remap/mache-vineflower-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 string, CookingBookCategory cookingBookCategory, Ingredient ingredient, ItemStack itemStack, float f, int i) {
+ super(RecipeType.CAMPFIRE_COOKING, string, cookingBookCategory, ingredient, itemStack, f, i);
+@@ -17,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-vineflower-stripped/net/minecraft/world/item/crafting/CustomRecipe.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/CustomRecipe.java.patch
new file mode 100644
index 0000000000..0a032bd01d
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/CustomRecipe.java.patch
@@ -0,0 +1,29 @@
+--- a/net/minecraft/world/item/crafting/CustomRecipe.java
++++ b/net/minecraft/world/item/crafting/CustomRecipe.java
+@@ -3,7 +3,13 @@
+ 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
++
++public abstract class CustomRecipe implements RecipeCrafting {
++
+ private final CraftingBookCategory category;
+
+ public CustomRecipe(CraftingBookCategory craftingBookCategory) {
+@@ -24,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-vineflower-stripped/net/minecraft/world/item/crafting/Ingredient.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/Ingredient.java.patch
new file mode 100644
index 0000000000..17af072826
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/Ingredient.java.patch
@@ -0,0 +1,35 @@
+--- a/net/minecraft/world/item/crafting/Ingredient.java
++++ b/net/minecraft/world/item/crafting/Ingredient.java
+@@ -34,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);
+
+@@ -60,8 +67,22 @@
+ } else if (this.isEmpty()) {
+ return stack.isEmpty();
+ } else {
+- for (ItemStack itemStack : this.getItems()) {
+- if (itemStack.is(stack.getItem())) {
++ ItemStack[] aitemstack = this.getItems();
++ int i = aitemstack.length;
++
++ for (int j = 0; j < i; ++j) {
++ ItemStack itemstack1 = aitemstack[j];
++
++ // 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-vineflower-stripped/net/minecraft/world/item/crafting/Recipe.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/Recipe.java.patch
new file mode 100644
index 0000000000..00d4c5ced3
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -62,4 +67,6 @@
+ NonNullList<Ingredient> ingredients = this.getIngredients();
+ return ingredients.isEmpty() || ingredients.stream().anyMatch(ingredient -> ingredient.getItems().length == 0);
+ }
++
++ org.bukkit.inventory.Recipe toBukkitRecipe(org.bukkit.NamespacedKey id); // CraftBukkit
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/RecipeHolder.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/RecipeHolder.java.patch
new file mode 100644
index 0000000000..d7bfcda029
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/RecipeHolder.java.patch
@@ -0,0 +1,22 @@
+--- a/net/minecraft/world/item/crafting/RecipeHolder.java
++++ b/net/minecraft/world/item/crafting/RecipeHolder.java
+@@ -1,9 +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 record RecipeHolder<T extends net.minecraft.world.item.crafting.Recipe<?>>(ResourceLocation id, T value) {
++
++ 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-vineflower-stripped/net/minecraft/world/item/crafting/RecipeManager.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/RecipeManager.java.patch
new file mode 100644
index 0000000000..ea766ea2ff
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/RecipeManager.java.patch
@@ -0,0 +1,165 @@
+--- a/net/minecraft/world/item/crafting/RecipeManager.java
++++ b/net/minecraft/world/item/crafting/RecipeManager.java
+@@ -23,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;
+@@ -36,7 +43,7 @@
+ public class RecipeManager extends SimpleJsonResourceReloadListener {
+ 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;
+
+@@ -47,32 +53,63 @@
+ @Override
+ protected void apply(Map<ResourceLocation, JsonElement> object, ResourceManager resourceManager, ProfilerFiller profiler) {
+ this.hasErrors = false;
+- Map<RecipeType<?>, Builder<ResourceLocation, RecipeHolder<?>>> map = 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();
+
+ for (Entry<ResourceLocation, JsonElement> entry : object.entrySet()) {
+ ResourceLocation resourceLocation = entry.getKey();
+
+ try {
+- RecipeHolder<?> recipeHolder = fromJson(resourceLocation, GsonHelper.convertToJsonObject(entry.getValue(), "top element"));
+- map.computeIfAbsent(recipeHolder.value().getType(), type -> ImmutableMap.builder()).put(resourceLocation, recipeHolder);
+- builder.put(resourceLocation, recipeHolder);
+- } catch (IllegalArgumentException | JsonParseException var10) {
+- LOGGER.error("Parsing error loading recipe {}", resourceLocation, var10);
++ RecipeHolder<?> recipeholder = fromJson(minecraftkey, GsonHelper.convertToJsonObject((JsonElement) entry.getValue(), "top element"));
++
++ // 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 {}", minecraftkey, jsonparseexception);
+ }
+ }
+
+- this.recipes = map.entrySet().stream().collect(ImmutableMap.toImmutableMap(Entry::getKey, entry1 -> entry1.getValue().build()));
+- this.byName = builder.build();
+- LOGGER.info("Loaded {} recipes", map.size());
++ this.recipes = (Map) map1.entrySet().stream().collect(ImmutableMap.toImmutableMap(Entry::getKey, (entry1) -> {
++ return (entry1.getValue()); // CraftBukkit
++ }));
++ 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 inventory, Level level) {
+- return this.byType(recipeType).values().stream().filter(recipeHolder -> recipeHolder.value().matches(inventory, level)).findFirst();
++ // 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(
+@@ -107,7 +145,7 @@
+ }
+
+ private <C extends Container, T extends Recipe<C>> Map<ResourceLocation, RecipeHolder<T>> byType(RecipeType<T> recipeType) {
+- return (Map<ResourceLocation, RecipeHolder<T>>) ((Map<ResourceLocation, ?>) this.recipes.getOrDefault(recipeType, Collections.emptyMap()));
++ return (Map) this.recipes.getOrDefault(recipeType, new Object2ObjectLinkedOpenHashMap<>()); // CraftBukkit
+ }
+
+ public <C extends Container, T extends Recipe<C>> NonNullList<ItemStack> getRemainingItemsFor(RecipeType<T> recipeType, C inventory, Level level) {
+@@ -144,21 +188,45 @@
+
+ public void replaceRecipes(Iterable<RecipeHolder<?>> recipes) {
+ 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();
+- recipes.forEach(recipeHolder -> {
+- Map<ResourceLocation, RecipeHolder<?>> map1 = map.computeIfAbsent(recipeHolder.value().getType(), recipeType -> Maps.newHashMap());
+- ResourceLocation resourceLocation = recipeHolder.id();
+- RecipeHolder<?> recipeHolder1 = map1.put(resourceLocation, (RecipeHolder<?>)recipeHolder);
+- builder.put(resourceLocation, (RecipeHolder<?>)recipeHolder);
+- if (recipeHolder1 != null) {
+- throw new IllegalStateException("Duplicate recipe ignored with ID " + resourceLocation);
++
++ recipes.forEach((recipeholder) -> {
++ Map<ResourceLocation, RecipeHolder<?>> map1 = (Map) map.computeIfAbsent(recipeholder.value().getType(), (recipes) -> {
++ return new Object2ObjectLinkedOpenHashMap<>(); // CraftBukkit
++ });
++ ResourceLocation minecraftkey = recipeholder.id();
++ RecipeHolder<?> recipeholder1 = (RecipeHolder) map1.put(minecraftkey, recipeholder);
++
++ builder.put(minecraftkey, recipeholder);
++ if (recipeholder1 != null) {
++ throw new IllegalStateException("Duplicate recipe ignored with ID " + minecraftkey);
+ }
+ });
+ this.recipes = ImmutableMap.copyOf(map);
+- this.byName = builder.build();
++ this.byName = Maps.newHashMap(builder.build()); // CraftBukkit
+ }
+
++ // 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
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/ShapedRecipe.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/ShapedRecipe.java.patch
new file mode 100644
index 0000000000..f5537291f4
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/ShapedRecipe.java.patch
@@ -0,0 +1,86 @@
+--- a/net/minecraft/world/item/crafting/ShapedRecipe.java
++++ b/net/minecraft/world/item/crafting/ShapedRecipe.java
+@@ -10,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 {
+ final ShapedRecipePattern pattern;
+@@ -30,7 +37,69 @@
+ this(string, 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-vineflower-stripped/net/minecraft/world/item/crafting/ShapelessRecipe.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/ShapelessRecipe.java.patch
new file mode 100644
index 0000000000..97a06c348e
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/ShapelessRecipe.java.patch
@@ -0,0 +1,39 @@
+--- 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 {
+ final String group;
+@@ -27,7 +34,23 @@
+ this.ingredients = list;
+ }
+
++ // 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-vineflower-stripped/net/minecraft/world/item/crafting/SmeltingRecipe.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/SmeltingRecipe.java.patch
new file mode 100644
index 0000000000..352ab54802
--- /dev/null
+++ b/patch-remap/mache-vineflower-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 string, CookingBookCategory cookingBookCategory, Ingredient ingredient, ItemStack itemStack, float f, int i) {
+ super(RecipeType.SMELTING, string, cookingBookCategory, ingredient, itemStack, f, i);
+@@ -17,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-vineflower-stripped/net/minecraft/world/item/crafting/SmithingTransformRecipe.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/SmithingTransformRecipe.java.patch
new file mode 100644
index 0000000000..1928a3054d
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/SmithingTransformRecipe.java.patch
@@ -0,0 +1,60 @@
+--- a/net/minecraft/world/item/crafting/SmithingTransformRecipe.java
++++ b/net/minecraft/world/item/crafting/SmithingTransformRecipe.java
+@@ -10,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 {
+ final Ingredient template;
+@@ -70,17 +78,33 @@
+ return Stream.of(this.template, this.base, this.addition).anyMatch(Ingredient::isEmpty);
+ }
+
+- public static class Serializer implements RecipeSerializer<SmithingTransformRecipe> {
+- private static final Codec<SmithingTransformRecipe> CODEC = RecordCodecBuilder.create(
+- instance -> instance.group(
+- Ingredient.CODEC.fieldOf("template").forGetter(smithingTransformRecipe -> smithingTransformRecipe.template),
+- Ingredient.CODEC.fieldOf("base").forGetter(smithingTransformRecipe -> smithingTransformRecipe.base),
+- Ingredient.CODEC.fieldOf("addition").forGetter(smithingTransformRecipe -> smithingTransformRecipe.addition),
+- ItemStack.ITEM_WITH_COUNT_CODEC.fieldOf("result").forGetter(smithingTransformRecipe -> smithingTransformRecipe.result)
+- )
+- .apply(instance, SmithingTransformRecipe::new)
+- );
++ // 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;
++ }), Ingredient.CODEC.fieldOf("base").forGetter((smithingtransformrecipe) -> {
++ return smithingtransformrecipe.base;
++ }), Ingredient.CODEC.fieldOf("addition").forGetter((smithingtransformrecipe) -> {
++ return smithingtransformrecipe.addition;
++ }), ItemStack.ITEM_WITH_COUNT_CODEC.fieldOf("result").forGetter((smithingtransformrecipe) -> {
++ return smithingtransformrecipe.result;
++ })).apply(instance, SmithingTransformRecipe::new);
++ });
++
++ public a() {}
++
+ @Override
+ public Codec<SmithingTransformRecipe> codec() {
+ return CODEC;
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/SmithingTrimRecipe.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/SmithingTrimRecipe.java.patch
new file mode 100644
index 0000000000..400058b878
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/SmithingTrimRecipe.java.patch
@@ -0,0 +1,37 @@
+--- a/net/minecraft/world/item/crafting/SmithingTrimRecipe.java
++++ b/net/minecraft/world/item/crafting/SmithingTrimRecipe.java
+@@ -18,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 {
+ final Ingredient template;
+@@ -99,15 +113,12 @@
+ return Stream.of(this.template, this.base, this.addition).anyMatch(Ingredient::isEmpty);
+ }
+
+- public static class Serializer implements RecipeSerializer<SmithingTrimRecipe> {
+- private static final Codec<SmithingTrimRecipe> CODEC = RecordCodecBuilder.create(
+- instance -> instance.group(
+- Ingredient.CODEC.fieldOf("template").forGetter(smithingTrimRecipe -> smithingTrimRecipe.template),
+- Ingredient.CODEC.fieldOf("base").forGetter(smithingTrimRecipe -> smithingTrimRecipe.base),
+- Ingredient.CODEC.fieldOf("addition").forGetter(smithingTrimRecipe -> smithingTrimRecipe.addition)
+- )
+- .apply(instance, SmithingTrimRecipe::new)
+- );
++ // 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
+
+ @Override
+ public Codec<SmithingTrimRecipe> codec() {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/SmokingRecipe.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/SmokingRecipe.java.patch
new file mode 100644
index 0000000000..8ca8e06845
--- /dev/null
+++ b/patch-remap/mache-vineflower-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 string, CookingBookCategory cookingBookCategory, Ingredient ingredient, ItemStack itemStack, float f, int i) {
+ super(RecipeType.SMOKING, string, cookingBookCategory, ingredient, itemStack, f, i);
+@@ -17,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-vineflower-stripped/net/minecraft/world/item/crafting/StonecutterRecipe.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/crafting/StonecutterRecipe.java.patch
new file mode 100644
index 0000000000..3d3dd4157c
--- /dev/null
+++ b/patch-remap/mache-vineflower-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 string, Ingredient ingredient, ItemStack itemStack) {
+ super(RecipeType.STONECUTTING, RecipeSerializer.STONECUTTER, string, ingredient, itemStack);
+@@ -19,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-vineflower-stripped/net/minecraft/world/item/enchantment/DamageEnchantment.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/enchantment/DamageEnchantment.java.patch
new file mode 100644
index 0000000000..e89dde3451
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/enchantment/DamageEnchantment.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/item/enchantment/DamageEnchantment.java
++++ b/net/minecraft/world/item/enchantment/DamageEnchantment.java
+@@ -62,9 +57,14 @@
+
+ @Override
+ public void doPostAttack(LivingEntity user, Entity target, int level) {
+- if (target instanceof LivingEntity livingEntity && this.type == 2 && level > 0 && livingEntity.getMobType() == MobType.ARTHROPOD) {
+- int i = 20 + user.getRandom().nextInt(10 * level);
+- livingEntity.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, i, 3));
++ if (target instanceof LivingEntity) {
++ LivingEntity entityliving1 = (LivingEntity) target;
++
++ if (this.type == 2 && level > 0 && entityliving1.getMobType() == EnumMonsterType.ARTHROPOD) {
++ int j = 20 + user.getRandom().nextInt(10 * level);
++
++ entityliving1.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, j, 3), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
++ }
+ }
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/enchantment/FrostWalkerEnchantment.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/enchantment/FrostWalkerEnchantment.java.patch
new file mode 100644
index 0000000000..89a7a488e8
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/enchantment/FrostWalkerEnchantment.java.patch
@@ -0,0 +1,36 @@
+--- a/net/minecraft/world/item/enchantment/FrostWalkerEnchantment.java
++++ b/net/minecraft/world/item/enchantment/FrostWalkerEnchantment.java
+@@ -41,17 +44,22 @@
+ int min = Math.min(16, 2 + levelConflicting);
+ BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
+
+- for (BlockPos blockPos : BlockPos.betweenClosed(pos.offset(-min, -1, -min), pos.offset(min, -1, min))) {
+- if (blockPos.closerToCenterThan(living.position(), (double)min)) {
+- mutableBlockPos.set(blockPos.getX(), blockPos.getY() + 1, blockPos.getZ());
+- BlockState blockState1 = level.getBlockState(mutableBlockPos);
+- if (blockState1.isAir()) {
+- BlockState blockState2 = level.getBlockState(blockPos);
+- if (blockState2 == FrostedIceBlock.meltsInto()
+- && blockState.canSurvive(level, blockPos)
+- && level.isUnobstructed(blockState, blockPos, CollisionContext.empty())) {
+- level.setBlockAndUpdate(blockPos, blockState);
+- level.scheduleTick(blockPos, Blocks.FROSTED_ICE, Mth.nextInt(living.getRandom(), 60, 120));
++ while (iterator.hasNext()) {
++ BlockPos blockposition1 = (BlockPos) iterator.next();
++
++ if (blockposition1.closerToCenterThan(living.position(), (double) j)) {
++ blockposition_mutableblockposition.set(blockposition1.getX(), blockposition1.getY() + 1, blockposition1.getZ());
++ IBlockData iblockdata1 = level.getBlockState(blockposition_mutableblockposition);
++
++ if (iblockdata1.isAir()) {
++ IBlockData iblockdata2 = level.getBlockState(blockposition1);
++
++ 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-vineflower-stripped/net/minecraft/world/item/trading/Merchant.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/trading/Merchant.java.patch
new file mode 100644
index 0000000000..75f2487c54
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -49,4 +52,6 @@
+ }
+
+ boolean isClientSide();
++
++ org.bukkit.craftbukkit.inventory.CraftMerchant getCraftMerchant(); // CraftBukkit
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/trading/MerchantOffer.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/item/trading/MerchantOffer.java.patch
new file mode 100644
index 0000000000..260f3829e8
--- /dev/null
+++ b/patch-remap/mache-vineflower-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,6 +5,8 @@
+ 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;
+@@ -17,6 +9,33 @@
+ private float priceMultiplier;
+ private int xp = 1;
+
++ 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 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.baseCostA = ItemStack.of(compoundTag.getCompound("buy"));
+ this.costB = ItemStack.of(compoundTag.getCompound("buyB"));
+@@ -88,9 +113,11 @@
+ if (this.baseCostA.isEmpty()) {
+ return ItemStack.EMPTY;
+ } else {
+- int count = this.baseCostA.getCount();
+- int max = Math.max(0, Mth.floor((float)(count * this.demand) * this.priceMultiplier));
+- return this.baseCostA.copyWithCount(Mth.clamp(count + max + this.specialPriceDiff, 1, this.baseCostA.getItem().getMaxStackSize()));
++ 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()));
+ }
+ }
+
+@@ -210,7 +235,11 @@
+ if (!this.satisfiedBy(playerOfferA, playerOfferB)) {
+ return false;
+ } else {
+- playerOfferA.shrink(this.getCostA().getCount());
++ // CraftBukkit start
++ if (!this.getCostA().isEmpty()) {
++ playerOfferA.shrink(this.getCostA().getCount());
++ }
++ // CraftBukkit end
+ if (!this.getCostB().isEmpty()) {
+ playerOfferB.shrink(this.getCostB().getCount());
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/BaseCommandBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/BaseCommandBlock.java.patch
new file mode 100644
index 0000000000..6ad276ed91
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/BaseCommandBlock.java.patch
@@ -0,0 +1,76 @@
+--- a/net/minecraft/world/level/BaseCommandBlock.java
++++ b/net/minecraft/world/level/BaseCommandBlock.java
+@@ -28,7 +29,11 @@
+ @Nullable
+ private Component lastOutput;
+ private String command = "";
+- private Component name = DEFAULT_NAME;
++ private Component name;
++ // CraftBukkit start
++ @Override
++ public abstract org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper);
++ // CraftBukkit end
+
+ public int getSuccessCount() {
+ return this.successCount;
+@@ -101,30 +111,36 @@
+ }
+
+ public boolean performCommand(Level level) {
+- if (level.isClientSide || level.getGameTime() == this.lastExecution) {
+- return false;
+- } else if ("Searge".equalsIgnoreCase(this.command)) {
+- this.lastOutput = Component.literal("#itzlipofutzli");
+- this.successCount = 1;
+- return true;
+- } else {
+- this.successCount = 0;
+- MinecraftServer server = this.getLevel().getServer();
+- if (server.isCommandBlockEnabled() && !StringUtil.isNullOrEmpty(this.command)) {
+- try {
+- this.lastOutput = null;
+- CommandSourceStack commandSourceStack = this.createCommandSourceStack().withCallback((flag, i) -> {
+- if (flag) {
+- this.successCount++;
+- }
+- });
+- server.getCommands().performPrefixedCommand(commandSourceStack, this.command);
+- } catch (Throwable var6) {
+- CrashReport crashReport = CrashReport.forThrowable(var6, "Executing command block");
+- CrashReportCategory crashReportCategory = crashReport.addCategory("Command to be executed");
+- crashReportCategory.setDetail("Command", this::getCommand);
+- crashReportCategory.setDetail("Name", () -> this.getName().getString());
+- throw new ReportedException(crashReport);
++ if (!level.isClientSide && level.getGameTime() != this.lastExecution) {
++ if ("Searge".equalsIgnoreCase(this.command)) {
++ this.lastOutput = Component.literal("#itzlipofutzli");
++ this.successCount = 1;
++ return true;
++ } else {
++ this.successCount = 0;
++ MinecraftServer minecraftserver = this.getLevel().getServer();
++
++ if (minecraftserver.isCommandBlockEnabled() && !StringUtil.isNullOrEmpty(this.command)) {
++ try {
++ this.lastOutput = null;
++ CommandSourceStack commandlistenerwrapper = this.createCommandSourceStack().withCallback((flag, i) -> {
++ if (flag) {
++ ++this.successCount;
++ }
++
++ });
++
++ minecraftserver.getCommands().dispatchServerCommand(commandlistenerwrapper, this.command); // CraftBukkit
++ } catch (Throwable throwable) {
++ CrashReport crashreport = CrashReport.forThrowable(throwable, "Executing command block");
++ CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Command to be executed");
++
++ crashreportsystemdetails.setDetail("Command", this::getCommand);
++ crashreportsystemdetails.setDetail("Name", () -> {
++ return this.getName().getString();
++ });
++ throw new ReportedException(crashreport);
++ }
+ }
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/BaseSpawner.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/BaseSpawner.java.patch
new file mode 100644
index 0000000000..b15a786d4a
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/BaseSpawner.java.patch
@@ -0,0 +1,24 @@
+--- a/net/minecraft/world/level/BaseSpawner.java
++++ b/net/minecraft/world/level/BaseSpawner.java
+@@ -47,6 +52,7 @@
+
+ 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 pos) {
+@@ -157,7 +155,12 @@
+ }
+ }
+
+- if (!serverLevel.tryAddFreshEntityWithPassengers(entity)) {
++ // 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-vineflower-stripped/net/minecraft/world/level/BlockGetter.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/BlockGetter.java.patch
new file mode 100644
index 0000000000..792bece095
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/BlockGetter.java.patch
@@ -0,0 +1,48 @@
+--- a/net/minecraft/world/level/BlockGetter.java
++++ b/net/minecraft/world/level/BlockGetter.java
+@@ -64,22 +58,30 @@
+ );
+ }
+
++ // 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 ? movingobjectpositionblock : movingobjectpositionblock1;
++ }
++ // CraftBukkit end
++
+ default BlockHitResult clip(ClipContext context) {
+- return traverseBlocks(context.getFrom(), context.getTo(), context, (traverseContext, traversePos) -> {
+- BlockState blockState = this.getBlockState(traversePos);
+- FluidState fluidState = this.getFluidState(traversePos);
+- Vec3 from = traverseContext.getFrom();
+- Vec3 to = traverseContext.getTo();
+- VoxelShape blockShape = traverseContext.getBlockShape(blockState, this, traversePos);
+- BlockHitResult blockHitResult = this.clipWithInteractionOverride(from, to, traversePos, blockShape, blockState);
+- VoxelShape fluidShape = traverseContext.getFluidShape(fluidState, this, traversePos);
+- BlockHitResult blockHitResult1 = fluidShape.clip(from, to, traversePos);
+- double d = blockHitResult == null ? Double.MAX_VALUE : traverseContext.getFrom().distanceToSqr(blockHitResult.getLocation());
+- double d1 = blockHitResult1 == null ? Double.MAX_VALUE : traverseContext.getFrom().distanceToSqr(blockHitResult1.getLocation());
+- return d <= d1 ? blockHitResult : blockHitResult1;
+- }, failContext -> {
+- Vec3 vec3 = failContext.getFrom().subtract(failContext.getTo());
+- return BlockHitResult.miss(failContext.getTo(), Direction.getNearest(vec3.x, vec3.y, vec3.z), BlockPos.containing(failContext.getTo()));
++ 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-vineflower-stripped/net/minecraft/world/level/ClipContext.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/ClipContext.java.patch
new file mode 100644
index 0000000000..a747312578
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/ClipContext.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/level/ClipContext.java
++++ b/net/minecraft/world/level/ClipContext.java
+@@ -21,7 +22,7 @@
+ private final CollisionContext collisionContext;
+
+ public ClipContext(Vec3 from, Vec3 to, ClipContext.Block block, ClipContext.Fluid fluid, Entity entity) {
+- this(from, to, block, fluid, CollisionContext.of(entity));
++ this(from, to, block, fluid, (entity == null) ? CollisionContext.empty() : CollisionContext.of(entity)); // CraftBukkit
+ }
+
+ public ClipContext(Vec3 vec3, Vec3 vec31, ClipContext.Block block, ClipContext.Fluid fluid, CollisionContext collisionContext) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/Explosion.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/Explosion.java.patch
new file mode 100644
index 0000000000..c30cb70685
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/Explosion.java.patch
@@ -0,0 +1,209 @@
+--- a/net/minecraft/world/level/Explosion.java
++++ b/net/minecraft/world/level/Explosion.java
+@@ -36,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 {
+ private static final ExplosionDamageCalculator EXPLOSION_DAMAGE_CALCULATOR = new ExplosionDamageCalculator();
+@@ -55,8 +66,12 @@
+ private final ParticleOptions smallExplosionParticles;
+ private final ParticleOptions largeExplosionParticles;
+ private final SoundEvent explosionSound;
+- private final ObjectArrayList<BlockPos> toBlow = new ObjectArrayList<>();
+- private final Map<Player, Vec3> hitPlayers = Maps.newHashMap();
++ 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));
+@@ -138,17 +97,18 @@
+ ) {
+ this.level = level;
+ this.source = entity;
+- this.radius = f;
+- this.x = d;
++ this.radius = (float) Math.max(f, 0.0); // CraftBukkit - clamp bad values
++ this.x = d0;
+ this.y = d1;
+ this.z = d2;
+ this.fire = flag;
+- this.blockInteraction = 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.blockInteraction = explosion_effect;
++ this.damageSource = damagesource == null ? world.damageSources().explosion(this) : damagesource;
++ this.damageCalculator = explosiondamagecalculator == null ? this.makeDamageCalculator(entity) : explosiondamagecalculator;
++ 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) {
+@@ -198,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();
+ int i = 16;
+@@ -270,7 +246,37 @@
+ double var43 = d8 / squareRoot1;
+ double var45 = d9 / squareRoot1;
+ 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 d10 = (1.0 - d6) * (double)getSeenPercent(vec3, entity);
+@@ -329,6 +334,11 @@
+ this.level.getProfiler().push("explosion_blocks");
+ List<Pair<ItemStack, BlockPos>> list = new ArrayList<>();
+ 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);
+
+ for (BlockPos blockPos : this.toBlow) {
+ this.level
+@@ -340,27 +365,76 @@
+ Block.popResource(this.level, pair.getSecond(), pair.getFirst());
+ }
+
++ 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 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(blockposition).onExplosionHit(this.level, blockposition, this, (itemstack, blockposition1) -> {
++ addOrAppendStack(list, itemstack, blockposition1);
++ });
++ }
++
++ Iterator iterator = list.iterator();
++
++ while (iterator.hasNext()) {
++ Pair<ItemStack, BlockPos> pair = (Pair) iterator.next();
++
++ Block.popResource(this.level, (BlockPos) pair.getSecond(), (ItemStack) pair.getFirst());
++ }
++
+ this.level.getProfiler().pop();
+ }
+
+ if (this.fire) {
+- for (BlockPos blockPos1 : this.toBlow) {
+- 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));
++ ObjectListIterator objectlistiterator1 = this.toBlow.iterator();
++
++ while (objectlistiterator1.hasNext()) {
++ BlockPos blockposition1 = (BlockPos) objectlistiterator1.next();
++
++ 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) {
+- for (int i = 0; i < list.size(); i++) {
+- Pair<ItemStack, BlockPos> pair = list.get(i);
+- ItemStack itemStack1 = pair.getFirst();
+- if (ItemEntity.areMergable(itemStack1, itemStack)) {
+- list.set(i, Pair.of(ItemEntity.merge(itemStack1, itemStack, 16), pair.getSecond()));
+- if (itemStack.isEmpty()) {
++ 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();
++
++ if (ItemEntity.areMergable(itemstack1, itemstack)) {
++ list.set(i, Pair.of(ItemEntity.merge(itemstack1, itemstack, 16), (BlockPos) pair.getSecond()));
++ if (itemstack.isEmpty()) {
+ return;
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/GameRules.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/GameRules.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/GameRules.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/Level.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/Level.java.patch
new file mode 100644
index 0000000000..1511956e29
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/Level.java.patch
@@ -0,0 +1,341 @@
+--- a/net/minecraft/world/level/Level.java
++++ b/net/minecraft/world/level/Level.java
+@@ -73,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);
+ public static final ResourceKey<Level> OVERWORLD = ResourceKey.create(Registries.DIMENSION, new ResourceLocation("overworld"));
+@@ -113,35 +142,63 @@
+ private final DamageSources damageSources;
+ private long subTickCount;
+
+- protected Level(
+- WritableLevelData levelData,
+- ResourceKey<Level> dimension,
+- RegistryAccess registryAccess,
+- Holder<DimensionType> dimensionTypeRegistration,
+- Supplier<ProfilerFiller> profiler,
+- boolean isClientSide,
+- boolean isDebug,
+- long biomeZoomSeed,
+- int maxChainedNeighborUpdates
+- ) {
+- this.profiler = profiler;
+- this.levelData = levelData;
+- this.dimensionTypeRegistration = dimensionTypeRegistration;
+- this.dimensionTypeId = dimensionTypeRegistration.unwrapKey()
+- .orElseThrow(() -> new IllegalArgumentException("Dimension must be registered, got " + dimensionTypeRegistration));
+- final DimensionType dimensionType = dimensionTypeRegistration.value();
+- this.dimension = dimension;
+- this.isClientSide = isClientSide;
+- if (dimensionType.coordinateScale() != 1.0) {
++ // 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 = worlddatamutable;
++ this.dimensionTypeRegistration = holder;
++ this.dimensionTypeId = (ResourceKey) holder.unwrapKey().orElseThrow(() -> {
++ return new IllegalArgumentException("Dimension must be registered, got " + holder);
++ });
++ final DimensionType dimensionmanager = (DimensionType) holder.value();
++
++ this.dimension = resourcekey;
++ this.isClientSide = flag;
++ if (dimensionmanager.coordinateScale() != 1.0D) {
+ this.worldBorder = new WorldBorder() {
+ @Override
+ public double getCenterX() {
+- return super.getCenterX() / dimensionType.coordinateScale();
++ return super.getCenterX(); // CraftBukkit
+ }
+
+ @Override
+ public double getCenterZ() {
+- return super.getCenterZ() / dimensionType.coordinateScale();
++ return super.getCenterZ(); // CraftBukkit
+ }
+ };
+ } else {
+@@ -149,11 +206,47 @@
+ }
+
+ this.thread = Thread.currentThread();
+- this.biomeManager = new BiomeManager(this, biomeZoomSeed);
+- this.isDebug = isDebug;
+- this.neighborUpdater = new CollectingNeighborUpdater(this, maxChainedNeighborUpdates);
+- this.registryAccess = registryAccess;
+- this.damageSources = new DamageSources(registryAccess);
++ this.biomeManager = new BiomeManager(this, i);
++ this.isDebug = flag1;
++ this.neighborUpdater = new CollectingNeighborUpdater(this, j);
++ 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
+@@ -209,7 +303,18 @@
+ }
+
+ @Override
+- public boolean setBlock(BlockPos pos, BlockState state, int flags, int recursionLeft) {
++ 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()) {
+@@ -217,8 +322,24 @@
+ } else {
+ LevelChunk chunkAt = this.getChunkAt(pos);
+ Block block = state.getBlock();
+- BlockState blockState = chunkAt.setBlockState(pos, state, (flags & 64) != 0);
+- if (blockState == 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 blockState1 = this.getBlockState(pos);
+@@ -250,13 +373,66 @@
+ this.onBlockStateChange(pos, blockState, blockState1);
+ }
+
++ // 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 pos, BlockState blockState, BlockState newState) {
++ // 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
+
+ @Override
+ public boolean removeBlock(BlockPos pos, boolean isMoving) {
+@@ -340,7 +518,15 @@
+ }
+
+ @Override
+- public BlockState getBlockState(BlockPos pos) {
++ 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 {
+@@ -630,19 +732,30 @@
+ @Nullable
+ @Override
+ public BlockEntity getBlockEntity(BlockPos pos) {
+- if (this.isOutsideBuildHeight(pos)) {
+- return null;
+- } else {
+- return !this.isClientSide && Thread.currentThread() != this.thread
+- ? null
+- : this.getChunkAt(pos).getBlockEntity(pos, LevelChunk.EntityCreationType.IMMEDIATE);
++ // CraftBukkit start
++ return getBlockEntity(pos, true);
++ }
++
++ @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));
+ }
+
+ public void setBlockEntity(BlockEntity blockEntity) {
+- BlockPos blockPos = blockEntity.getBlockPos();
+- if (!this.isOutsideBuildHeight(blockPos)) {
+- this.getChunkAt(blockPos).addAndRegisterBlockEntity(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-vineflower-stripped/net/minecraft/world/level/LevelAccessor.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/LevelAccessor.java.patch
new file mode 100644
index 0000000000..af665f012d
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -114,4 +114,6 @@
+ default void gameEvent(GameEvent event, BlockPos pos, GameEvent.Context context) {
+ this.gameEvent(event, Vec3.atCenterOf(pos), context);
+ }
++
++ net.minecraft.server.level.ServerLevel getMinecraftWorld(); // CraftBukkit
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/LevelWriter.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/LevelWriter.java.patch
new file mode 100644
index 0000000000..0a28444cc3
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -27,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-vineflower-stripped/net/minecraft/world/level/NaturalSpawner.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/NaturalSpawner.java.patch
new file mode 100644
index 0000000000..eb4649b9bd
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/NaturalSpawner.java.patch
@@ -0,0 +1,132 @@
+--- a/net/minecraft/world/level/NaturalSpawner.java
++++ b/net/minecraft/world/level/NaturalSpawner.java
+@@ -46,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 {
+ private static final Logger LOGGER = LogUtils.getLogger();
+@@ -101,13 +118,31 @@
+ ) {
+ level.getProfiler().push("spawner");
+
+- for (MobCategory mobCategory : SPAWNING_CATEGORIES) {
+- if ((spawnFriendlies || !mobCategory.isFriendly())
+- && (spawnMonsters || mobCategory.isFriendly())
+- && (forcedDespawn || !mobCategory.isPersistent())
+- && spawnState.canSpawnForCategory(mobCategory, chunk.getPos())) {
+- spawnCategoryForChunk(mobCategory, level, chunk, spawnState::canSpawn, spawnState::afterSpawn);
++ LevelData worlddata = level.getLevelData(); // CraftBukkit - Other mob type spawn tick rate
++
++ for (int j = 0; j < i; ++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 (!spawnThisTick || limit == 0) {
++ continue;
++ }
++
++ 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);
++ }
+ }
+
+ level.getProfiler().pop();
+@@ -195,8 +211,33 @@
+ return;
+ }
+
+- if (mobForSpawn.isMaxGroupSizeReached(i3)) {
+- break;
++ if (isValidSpawnPostitionForType(level, category, structuremanager, chunkgenerator, biomesettingsmobs_c, blockposition_mutableblockposition, d2) && filter.test(biomesettingsmobs_c.type, blockposition_mutableblockposition, chunk)) {
++ Mob entityinsentient = getMobForSpawn(level, biomesettingsmobs_c.type);
++
++ if (entityinsentient == null) {
++ return;
++ }
++
++ 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;
++ }
++
++ if (entityinsentient.isMaxGroupSizeReached(k1)) {
++ break label53;
++ }
++ }
+ }
+ }
+ }
+@@ -388,19 +409,15 @@
+ continue;
+ }
+
+- entity.moveTo(d, (double)topNonCollidingPos.getY(), d1, random.nextFloat() * 360.0F, 0.0F);
+- if (entity instanceof Mob mob
+- && mob.checkSpawnRules(levelAccessor, MobSpawnType.CHUNK_GENERATION)
+- && mob.checkSpawnObstruction(levelAccessor)) {
+- spawnGroupData = mob.finalizeSpawn(
+- levelAccessor,
+- levelAccessor.getCurrentDifficultyAt(mob.blockPosition()),
+- MobSpawnType.CHUNK_GENERATION,
+- spawnGroupData,
+- null
+- );
+- levelAccessor.addFreshEntityWithPassengers(mob);
+- flag = true;
++ entity.moveTo(d0, (double) blockposition.getY(), d1, random.nextFloat() * 360.0F, 0.0F);
++ if (entity instanceof Mob) {
++ Mob entityinsentient = (Mob) entity;
++
++ 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;
++ }
+ }
+ }
+
+@@ -526,9 +536,12 @@
+ return this.unmodifiableMobCategoryCounts;
+ }
+
+- boolean canSpawnForCategory(MobCategory category, ChunkPos pos) {
+- int i = category.getMaxInstancesPerChunk() * this.spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER;
+- return this.mobCategoryCounts.getInt(category) < i && this.localMobCapCalculator.canSpawn(category, pos);
++ // CraftBukkit start
++ boolean canSpawnForCategory(MobCategory enumcreaturetype, ChunkPos chunkcoordintpair, int limit) {
++ int i = limit * this.spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER;
++ // CraftBukkit end
++
++ return this.mobCategoryCounts.getInt(enumcreaturetype) >= i ? false : this.localMobCapCalculator.canSpawn(enumcreaturetype, chunkcoordintpair);
+ }
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/ServerLevelAccessor.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/ServerLevelAccessor.java.patch
new file mode 100644
index 0000000000..3ca302d2f6
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -7,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-vineflower-stripped/net/minecraft/world/level/block/AbstractCandleBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/AbstractCandleBlock.java.patch
new file mode 100644
index 0000000000..d34ac23abf
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/AbstractCandleBlock.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/world/level/block/AbstractCandleBlock.java
++++ b/net/minecraft/world/level/block/AbstractCandleBlock.java
+@@ -43,6 +45,11 @@
+ @Override
+ public void onProjectileHit(Level level, BlockState 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-vineflower-stripped/net/minecraft/world/level/block/BambooSaplingBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BambooSaplingBlock.java.patch
new file mode 100644
index 0000000000..e084647095
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BambooSaplingBlock.java.patch
@@ -0,0 +1,10 @@
+--- a/net/minecraft/world/level/block/BambooSaplingBlock.java
++++ b/net/minecraft/world/level/block/BambooSaplingBlock.java
+@@ -92,6 +95,6 @@
+ }
+
+ protected void growBamboo(Level level, BlockPos state) {
+- level.setBlock(state.above(), Blocks.BAMBOO.defaultBlockState().setValue(BambooStalkBlock.LEAVES, BambooLeaves.SMALL), 3);
++ 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-vineflower-stripped/net/minecraft/world/level/block/BambooStalkBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BambooStalkBlock.java.patch
new file mode 100644
index 0000000000..15d1b8e885
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BambooStalkBlock.java.patch
@@ -0,0 +1,75 @@
+--- a/net/minecraft/world/level/block/BambooStalkBlock.java
++++ b/net/minecraft/world/level/block/BambooStalkBlock.java
+@@ -176,10 +183,11 @@
+ int i = heightAboveUpToMax + heightBelowUpToMax + 1;
+ int i1 = 1 + random.nextInt(2);
+
+- for (int i2 = 0; i2 < i1; i2++) {
+- BlockPos blockPos = pos.above(heightAboveUpToMax);
+- BlockState blockState = level.getBlockState(blockPos);
+- if (i >= 16 || blockState.getValue(STAGE) == 1 || !level.isEmptyBlock(blockPos.above())) {
++ for (int i1 = 0; i1 < l; ++i1) {
++ BlockPos blockposition1 = pos.above(i);
++ IBlockData iblockdata1 = level.getBlockState(blockposition1);
++
++ 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;
+ }
+
+@@ -194,28 +203,39 @@
+ return player.getMainHandItem().getItem() instanceof SwordItem ? 1.0F : super.getDestroyProgress(state, player, level, pos);
+ }
+
+- protected void growBamboo(BlockState state, Level level, BlockPos pos, RandomSource random, int age) {
+- BlockState blockState = level.getBlockState(pos.below());
+- BlockPos blockPos = pos.below(2);
+- BlockState blockState1 = level.getBlockState(blockPos);
+- 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 (age >= 1) {
+- if (!blockState.is(Blocks.BAMBOO) || blockState.getValue(LEAVES) == BambooLeaves.NONE) {
+- bambooLeaves = BambooLeaves.SMALL;
+- } else if (blockState.is(Blocks.BAMBOO) && blockState.getValue(LEAVES) != BambooLeaves.NONE) {
+- bambooLeaves = BambooLeaves.LARGE;
+- if (blockState1.is(Blocks.BAMBOO)) {
+- level.setBlock(pos.below(), blockState.setValue(LEAVES, BambooLeaves.SMALL), 3);
+- level.setBlock(blockPos, blockState1.setValue(LEAVES, BambooLeaves.NONE), 3);
++ 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
++ }
+ }
+ }
+ }
+
+- int i = state.getValue(AGE) != 1 && !blockState1.is(Blocks.BAMBOO) ? 0 : 1;
+- int i1 = (age < 11 || !(random.nextFloat() < 0.25F)) && age != 15 ? 0 : 1;
+- level.setBlock(
+- pos.above(), this.defaultBlockState().setValue(AGE, Integer.valueOf(i)).setValue(LEAVES, bambooLeaves).setValue(STAGE, Integer.valueOf(i1)), 3
+- );
++ int j = (Integer) state.getValue(BambooStalkBlock.AGE) != 1 && !iblockdata2.is(Blocks.BAMBOO) ? 0 : 1;
++ int k = (age < 11 || random.nextFloat() >= 0.25F) && age != 15 ? 0 : 1;
++
++ // 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 level, BlockPos pos) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BaseFireBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BaseFireBlock.java.patch
new file mode 100644
index 0000000000..6683c134f4
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BaseFireBlock.java.patch
@@ -0,0 +1,47 @@
+--- a/net/minecraft/world/level/block/BaseFireBlock.java
++++ b/net/minecraft/world/level/block/BaseFireBlock.java
+@@ -128,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
+ }
+ }
+
+@@ -148,13 +155,13 @@
+ }
+
+ if (!state.canSurvive(level, pos)) {
+- level.removeBlock(pos, false);
++ 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
+@@ -199,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-vineflower-stripped/net/minecraft/world/level/block/BasePressurePlateBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BasePressurePlateBlock.java.patch
new file mode 100644
index 0000000000..782819529b
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BasePressurePlateBlock.java.patch
@@ -0,0 +1,59 @@
+--- a/net/minecraft/world/level/block/BasePressurePlateBlock.java
++++ b/net/minecraft/world/level/block/BasePressurePlateBlock.java
+@@ -20,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 {
+ protected static final VoxelShape PRESSED_AABB = Block.box(1.0, 0.0, 1.0, 15.0, 0.5, 15.0);
+@@ -83,10 +89,25 @@
+ private void checkPressed(@Nullable Entity entity, Level level, BlockPos pos, BlockState state, int currentSignal) {
+ int signalStrength = this.getSignalStrength(level, pos);
+ boolean flag = currentSignal > 0;
+- boolean flag1 = signalStrength > 0;
+- if (currentSignal != signalStrength) {
+- BlockState blockState = this.setSignalForState(state, signalStrength);
+- level.setBlock(pos, blockState, 2);
++ boolean flag1 = j > 0;
++
++ // CraftBukkit start - Interact Pressure Plate
++ org.bukkit.World bworld = level.getWorld();
++ org.bukkit.plugin.PluginManager manager = level.getCraftServer().getPluginManager();
++
++ if (flag != flag1) {
++ BlockRedstoneEvent eventRedstone = new BlockRedstoneEvent(bworld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()), currentSignal, j);
++ manager.callEvent(eventRedstone);
++
++ flag1 = eventRedstone.getNewCurrent() > 0;
++ j = eventRedstone.getNewCurrent();
++ }
++ // CraftBukkit end
++
++ if (currentSignal != j) {
++ IBlockData iblockdata1 = this.setSignalForState(state, j);
++
++ level.setBlock(pos, iblockdata1, 2);
+ this.updateNeighbours(level, pos);
+ level.setBlocksDirty(pos, state, blockState);
+ }
+@@ -136,9 +158,17 @@
+ }
+
+ protected static int getEntityCount(Level level, AABB box, Class<? extends Entity> entityClass) {
+- return level.getEntitiesOfClass(entityClass, box, EntitySelector.NO_SPECTATORS.and(entity -> !entity.isIgnoringBlockTriggers())).size();
++ // 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();
++ })); // CraftBukkit
++ }
++
+ protected abstract int getSignalStrength(Level level, BlockPos pos);
+
+ protected abstract int getSignalForState(BlockState state);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BedBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BedBlock.java.patch
new file mode 100644
index 0000000000..9c276d1845
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BedBlock.java.patch
@@ -0,0 +1,82 @@
+--- a/net/minecraft/world/level/block/BedBlock.java
++++ b/net/minecraft/world/level/block/BedBlock.java
+@@ -92,7 +94,8 @@
+ }
+ }
+
+- if (!canSetSpawn(level)) {
++ // CraftBukkit - moved world and biome check into EntityHuman
++ if (false && !canSetSpawn(level)) {
+ level.removeBlock(pos, false);
+ BlockPos blockPos = pos.relative(state.getValue(FACING).getOpposite());
+ if (level.getBlockState(blockPos).is(this)) {
+@@ -109,9 +114,18 @@
+
+ return InteractionResult.SUCCESS;
+ } else {
+- player.startSleepInBed(pos).ifLeft(bedSleepingProblem -> {
+- if (bedSleepingProblem.getMessage() != null) {
+- player.displayClientMessage(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);
+ }
+ });
+ return InteractionResult.SUCCESS;
+@@ -119,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 pos) {
+@@ -305,8 +352,14 @@
+ public void setPlacedBy(Level level, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
+ super.setPlacedBy(level, pos, state, placer, stack);
+ if (!level.isClientSide) {
+- BlockPos blockPos = pos.relative(state.getValue(FACING));
+- level.setBlock(blockPos, state.setValue(PART, BedPart.HEAD), 3);
++ BlockPos blockposition1 = pos.relative((Direction) state.getValue(BedBlock.FACING));
++
++ 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-vineflower-stripped/net/minecraft/world/level/block/BeehiveBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BeehiveBlock.java.patch
new file mode 100644
index 0000000000..1b83368ec4
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BeehiveBlock.java.patch
@@ -0,0 +1,22 @@
+--- a/net/minecraft/world/level/block/BeehiveBlock.java
++++ b/net/minecraft/world/level/block/BeehiveBlock.java
+@@ -104,10 +111,15 @@
+ return;
+ }
+
+- for (Bee bee : entitiesOfClass) {
+- if (bee.getTarget() == null) {
+- Player player = Util.getRandom(entitiesOfClass1, level.random);
+- bee.setTarget(player);
++ Iterator iterator = list.iterator();
++
++ while (iterator.hasNext()) {
++ Bee entitybee = (Bee) iterator.next();
++
++ if (entitybee.getTarget() == null) {
++ Player entityhuman = (Player) Util.getRandom(list1, level.random);
++
++ entitybee.setTarget(entityhuman, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER, true); // CraftBukkit
+ }
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BellBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BellBlock.java.patch
new file mode 100644
index 0000000000..86f3d426df
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -140,6 +146,11 @@
+ if (direction == null) {
+ direction = level.getBlockState(pos).getValue(FACING);
+ }
++ // CraftBukkit start
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBellRingEvent(level, pos, direction, entity)) {
++ return false;
++ }
++ // CraftBukkit end
+
+ ((BellBlockEntity)blockEntity).onHit(direction);
+ level.playSound(null, pos, SoundEvents.BELL_BLOCK, SoundSource.BLOCKS, 2.0F, 1.0F);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BigDripleafBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BigDripleafBlock.java.patch
new file mode 100644
index 0000000000..ede16ddc3f
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BigDripleafBlock.java.patch
@@ -0,0 +1,113 @@
+--- a/net/minecraft/world/level/block/BigDripleafBlock.java
++++ b/net/minecraft/world/level/block/BigDripleafBlock.java
+@@ -39,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 {
+ public static final MapCodec<BigDripleafBlock> CODEC = simpleCodec(BigDripleafBlock::new);
+@@ -133,8 +119,8 @@
+ }
+
+ @Override
+- public void onProjectileHit(Level level, BlockState state, BlockHitResult hit, Projectile projectile) {
+- this.setTiltAndScheduleTick(state, level, hit.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
+@@ -189,8 +178,21 @@
+ @Override
+ public void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
+ if (!level.isClientSide) {
+- if (state.getValue(TILT) == Tilt.NONE && canEntityTilt(pos, entity) && !level.hasNeighborSignal(pos)) {
+- this.setTiltAndScheduleTick(state, level, pos, Tilt.UNSTABLE, 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
+ }
+ }
+ }
+@@ -202,9 +206,9 @@
+ } else {
+ Tilt tilt = state.getValue(TILT);
+ if (tilt == Tilt.UNSTABLE) {
+- this.setTiltAndScheduleTick(state, level, pos, 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(state, level, pos, 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(state, level, pos);
+ }
+@@ -227,10 +234,12 @@
+ return entity.onGround() && entity.position().y > (double)((float)pos.getY() + 0.6875F);
+ }
+
+- private void setTiltAndScheduleTick(BlockState state, Level level, BlockPos pos, Tilt tilt, @Nullable SoundEvent sound) {
+- setTilt(state, level, pos, tilt);
+- if (sound != null) {
+- playTiltSound(level, pos, sound);
++ // 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 _int = DELAY_UNTIL_NEXT_TILT_STATE.getInt(tilt);
+@@ -239,19 +250,29 @@
+ }
+ }
+
+- private static void resetTilt(BlockState state, Level level, BlockPos pos) {
+- setTilt(state, level, pos, Tilt.NONE);
+- if (state.getValue(TILT) != Tilt.NONE) {
++ 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 state, Level level, BlockPos pos, Tilt tilt) {
+- Tilt tilt1 = state.getValue(TILT);
+- level.setBlock(pos, state.setValue(TILT, tilt), 2);
++ // 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);
++
++ world.setBlock(blockposition, (IBlockData) iblockdata.setValue(BigDripleafBlock.TILT, tilt), 2);
+ if (tilt.causesVibration() && tilt != tilt1) {
+ level.gameEvent(null, GameEvent.BLOCK_CHANGE, pos);
+ }
++
++ return true; // CraftBukkit
+ }
+
+ @Override
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/Block.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/Block.java.patch
new file mode 100644
index 0000000000..e13e448ee0
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/Block.java.patch
@@ -0,0 +1,57 @@
+--- a/net/minecraft/world/level/block/Block.java
++++ b/net/minecraft/world/level/block/Block.java
+@@ -328,9 +346,16 @@
+
+ private static void popResource(Level level, Supplier<ItemEntity> itemEntitySupplier, ItemStack stack) {
+ if (!level.isClientSide && !stack.isEmpty() && level.getGameRules().getBoolean(GameRules.RULE_DOBLOCKDROPS)) {
+- ItemEntity itemEntity = itemEntitySupplier.get();
+- itemEntity.setDefaultPickUpDelay();
+- level.addFreshEntity(itemEntity);
++ ItemEntity entityitem = (ItemEntity) itemEntitySupplier.get();
++
++ entityitem.setDefaultPickUpDelay();
++ // CraftBukkit start
++ if (level.captureDrops != null) {
++ level.captureDrops.add(entityitem);
++ } else {
++ level.addFreshEntity(entityitem);
++ }
++ // CraftBukkit end
+ }
+ }
+
+@@ -357,7 +381,7 @@
+
+ public void playerDestroy(Level level, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) {
+ player.awardStat(Stats.BLOCK_MINED.get(this));
+- player.causeFoodExhaustion(0.005F);
++ player.causeFoodExhaustion(0.005F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.BLOCK_MINED); // CraftBukkit - EntityExhaustionEvent
+ dropResources(state, level, pos, blockEntity, player, tool);
+ }
+
+@@ -495,15 +518,22 @@
+ return this.builtInRegistryHolder;
+ }
+
+- protected void tryDropExperience(ServerLevel level, BlockPos pos, ItemStack heldItem, IntProvider amount) {
+- if (EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, heldItem) == 0) {
+- int i = amount.sample(level.random);
++ // 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(worldserver.random);
++
+ if (i > 0) {
+ this.popExperience(level, pos, i);
+ }
+ }
+ }
+
++ 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;
+ private final BlockState second;
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BuddingAmethystBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BuddingAmethystBlock.java.patch
new file mode 100644
index 0000000000..493f0f9ea9
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BuddingAmethystBlock.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/world/level/block/BuddingAmethystBlock.java
++++ b/net/minecraft/world/level/block/BuddingAmethystBlock.java
+@@ -41,10 +43,9 @@
+ }
+
+ if (block != null) {
+- BlockState blockState1 = block.defaultBlockState()
+- .setValue(AmethystClusterBlock.FACING, direction)
+- .setValue(AmethystClusterBlock.WATERLOGGED, Boolean.valueOf(blockState.getFluidState().getType() == Fluids.WATER));
+- level.setBlockAndUpdate(blockPos, blockState1);
++ IBlockData iblockdata2 = (IBlockData) ((IBlockData) block.defaultBlockState().setValue(AmethystClusterBlock.FACING, enumdirection)).setValue(AmethystClusterBlock.WATERLOGGED, iblockdata1.getFluidState().getType() == Fluids.WATER);
++
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, blockposition1, iblockdata2); // CraftBukkit
+ }
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BushBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BushBlock.java.patch
new file mode 100644
index 0000000000..58e85bc2cf
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/BushBlock.java.patch
@@ -0,0 +1,22 @@
+--- a/net/minecraft/world/level/block/BushBlock.java
++++ b/net/minecraft/world/level/block/BushBlock.java
+@@ -24,10 +25,15 @@
+ }
+
+ @Override
+- public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, LevelAccessor level, BlockPos currentPos, BlockPos facingPos) {
+- return !state.canSurvive(level, currentPos)
+- ? Blocks.AIR.defaultBlockState()
+- : super.updateShape(state, facing, facingState, level, currentPos, facingPos);
++ 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-vineflower-stripped/net/minecraft/world/level/block/ButtonBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ButtonBlock.java.patch
new file mode 100644
index 0000000000..1c6b231b61
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ButtonBlock.java.patch
@@ -0,0 +1,79 @@
+--- a/net/minecraft/world/level/block/ButtonBlock.java
++++ b/net/minecraft/world/level/block/ButtonBlock.java
+@@ -33,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 {
+ public static final MapCodec<ButtonBlock> CODEC = RecordCodecBuilder.mapCodec(
+@@ -114,6 +128,19 @@
+ if (state.getValue(POWERED)) {
+ return InteractionResult.CONSUME;
+ } else {
++ // 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(player, GameEvent.BLOCK_ACTIVATE, pos);
+@@ -184,14 +211,38 @@
+ }
+ }
+
+- protected void checkPressed(BlockState state, Level level, BlockPos pos) {
+- AbstractArrow abstractArrow = this.type.canButtonBeActivatedByArrows()
+- ? level.getEntitiesOfClass(AbstractArrow.class, state.getShape(level, pos).bounds().move(pos)).stream().findFirst().orElse(null)
+- : null;
+- boolean flag = abstractArrow != null;
+- boolean flag1 = state.getValue(POWERED);
++ protected void checkPressed(IBlockData state, Level level, BlockPos pos) {
++ AbstractArrow entityarrow = this.type.canButtonBeActivatedByArrows() ? (AbstractArrow) level.getEntitiesOfClass(AbstractArrow.class, state.getShape(level, pos).bounds().move(pos)).stream().findFirst().orElse(null) : null; // CraftBukkit - decompile error
++ boolean flag = entityarrow != null;
++ boolean flag1 = (Boolean) state.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(pos, state.setValue(POWERED, Boolean.valueOf(flag)), 3);
++ // 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(null, level, pos, flag);
+ level.gameEvent(abstractArrow, flag ? GameEvent.BLOCK_ACTIVATE : GameEvent.BLOCK_DEACTIVATE, pos);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CactusBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CactusBlock.java.patch
new file mode 100644
index 0000000000..b1cf4e1b6a
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CactusBlock.java.patch
@@ -0,0 +1,43 @@
+--- a/net/minecraft/world/level/block/CactusBlock.java
++++ b/net/minecraft/world/level/block/CactusBlock.java
+@@ -20,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 {
+ public static final MapCodec<CactusBlock> CODEC = simpleCodec(CactusBlock::new);
+@@ -57,12 +62,14 @@
+ }
+
+ if (i < 3) {
+- int i1 = state.getValue(AGE);
+- if (i1 == 15) {
+- level.setBlockAndUpdate(blockPos, this.defaultBlockState());
+- BlockState blockState = state.setValue(AGE, Integer.valueOf(0));
+- level.setBlock(pos, blockState, 4);
+- level.neighborChanged(blockState, blockPos, this, pos, false);
++ int j = (Integer) state.getValue(CactusBlock.AGE);
++
++ if (j == 15) {
++ CraftEventFactory.handleBlockGrowEvent(level, blockposition1, this.defaultBlockState()); // CraftBukkit
++ IBlockData iblockdata1 = (IBlockData) state.setValue(CactusBlock.AGE, 0);
++
++ level.setBlock(pos, iblockdata1, 4);
++ level.neighborChanged(iblockdata1, blockposition1, this, pos, false);
+ } else {
+ level.setBlock(pos, state.setValue(AGE, Integer.valueOf(i1 + 1)), 4);
+ }
+@@ -103,8 +119,10 @@
+ }
+
+ @Override
+- public void entityInside(BlockState state, Level level, BlockPos pos, 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-vineflower-stripped/net/minecraft/world/level/block/CakeBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CakeBlock.java.patch
new file mode 100644
index 0000000000..1e895b0276
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -96,9 +92,21 @@
+ return InteractionResult.PASS;
+ } else {
+ player.awardStat(Stats.EAT_CAKE_SLICE);
+- player.getFoodData().eat(2, 0.1F);
+- int i = state.getValue(BITES);
+- level.gameEvent(player, GameEvent.EAT, pos);
++ // CraftBukkit start
++ // entityhuman.getFoodData().eat(2, 0.1F);
++ int oldFoodLevel = player.getFoodData().foodLevel;
++
++ 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) {
+ level.setBlock(pos, state.setValue(BITES, Integer.valueOf(i + 1)), 3);
+ } else {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CampfireBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CampfireBlock.java.patch
new file mode 100644
index 0000000000..af0c8b6a1c
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CampfireBlock.java.patch
@@ -0,0 +1,48 @@
+--- 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 {
+ public static final MapCodec<CampfireBlock> CODEC = RecordCodecBuilder.mapCodec(
+@@ -110,9 +107,11 @@
+ }
+
+ @Override
+- public void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
+- if (state.getValue(LIT) && entity instanceof LivingEntity && !EnchantmentHelper.hasFrostWalker((LivingEntity)entity)) {
+- entity.hurt(level.damageSources().inFire(), (float)this.fireDamage);
++ 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(state, level, pos, entity);
+@@ -236,10 +217,16 @@
+ }
+
+ @Override
+- public void onProjectileHit(Level level, BlockState state, BlockHitResult hit, Projectile projectile) {
+- BlockPos blockPos = hit.getBlockPos();
+- if (!level.isClientSide && projectile.isOnFire() && projectile.mayInteract(level, blockPos) && !state.getValue(LIT) && !state.getValue(WATERLOGGED)) {
+- level.setBlock(blockPos, state.setValue(BlockStateProperties.LIT, Boolean.valueOf(true)), 11);
++ public void onProjectileHit(Level level, IBlockData state, BlockHitResult hit, Projectile projectile) {
++ BlockPos blockposition = hit.getBlockPos();
++
++ 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-vineflower-stripped/net/minecraft/world/level/block/CarvedPumpkinBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CarvedPumpkinBlock.java.patch
new file mode 100644
index 0000000000..d1513a9c00
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CarvedPumpkinBlock.java.patch
@@ -0,0 +1,31 @@
+--- a/net/minecraft/world/level/block/CarvedPumpkinBlock.java
++++ b/net/minecraft/world/level/block/CarvedPumpkinBlock.java
+@@ -22,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 {
+ public static final MapCodec<CarvedPumpkinBlock> CODEC = simpleCodec(CarvedPumpkinBlock::new);
+@@ -78,9 +89,15 @@
+ }
+
+ private static void spawnGolemInWorld(Level level, BlockPattern.BlockPatternMatch patternMatch, Entity golem, BlockPos pos) {
+- clearPatternBlocks(level, patternMatch);
+- golem.moveTo((double)pos.getX() + 0.5, (double)pos.getY() + 0.05, (double)pos.getZ() + 0.5, 0.0F, 0.0F);
+- level.addFreshEntity(golem);
++ // 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();
+
+ for (ServerPlayer serverPlayer : level.getEntitiesOfClass(ServerPlayer.class, golem.getBoundingBox().inflate(5.0))) {
+ CriteriaTriggers.SUMMONED_ENTITY.trigger(serverPlayer, golem);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CauldronBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CauldronBlock.java.patch
new file mode 100644
index 0000000000..1cfb65fad4
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CauldronBlock.java.patch
@@ -0,0 +1,31 @@
+--- a/net/minecraft/world/level/block/CauldronBlock.java
++++ b/net/minecraft/world/level/block/CauldronBlock.java
+@@ -10,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 {
+ public static final MapCodec<CauldronBlock> CODEC = simpleCodec(CauldronBlock::new);
+@@ -57,14 +63,12 @@
+ @Override
+ protected void receiveStalactiteDrip(BlockState state, Level level, BlockPos pos, Fluid fluid) {
+ if (fluid == Fluids.WATER) {
+- BlockState blockState = Blocks.WATER_CAULDRON.defaultBlockState();
+- level.setBlockAndUpdate(pos, blockState);
+- level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(blockState));
++ 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) {
+- BlockState blockState = Blocks.LAVA_CAULDRON.defaultBlockState();
+- level.setBlockAndUpdate(pos, blockState);
+- level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(blockState));
++ 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-vineflower-stripped/net/minecraft/world/level/block/CaveVines.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CaveVines.java.patch
new file mode 100644
index 0000000000..fb71d103ab
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CaveVines.java.patch
@@ -0,0 +1,43 @@
+--- a/net/minecraft/world/level/block/CaveVines.java
++++ b/net/minecraft/world/level/block/CaveVines.java
+@@ -17,13 +18,37 @@
+ 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.0, 0.0, 1.0, 15.0, 16.0, 15.0);
+ BooleanProperty BERRIES = BlockStateProperties.BERRIES;
+
+- static InteractionResult use(@Nullable Entity entity, BlockState state, Level level, BlockPos pos) {
+- if (state.getValue(BERRIES)) {
+- Block.popResource(level, pos, 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(null, pos, SoundEvents.CAVE_VINES_PICK_BERRIES, SoundSource.BLOCKS, 1.0F, f);
+ BlockState blockState = state.setValue(BERRIES, Boolean.valueOf(false));
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CeilingHangingSignBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CeilingHangingSignBlock.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CeilingHangingSignBlock.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ChangeOverTimeBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ChangeOverTimeBlock.java.patch
new file mode 100644
index 0000000000..3028aee354
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ChangeOverTimeBlock.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/world/level/block/ChangeOverTimeBlock.java
++++ b/net/minecraft/world/level/block/ChangeOverTimeBlock.java
+@@ -15,8 +17,11 @@
+
+ default void changeOverTime(BlockState blockState, ServerLevel serverLevel, BlockPos blockPos, RandomSource randomSource) {
+ float f = 0.05688889F;
+- if (randomSource.nextFloat() < 0.05688889F) {
+- this.getNextState(blockState, serverLevel, blockPos, randomSource).ifPresent(blockState1 -> serverLevel.setBlockAndUpdate(blockPos, blockState1));
++
++ if (randomsource.nextFloat() < 0.05688889F) {
++ this.getNextState(iblockdata, worldserver, blockposition, randomsource).ifPresent((iblockdata1) -> {
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(worldserver, blockposition, iblockdata1); // CraftBukkit
++ });
+ }
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ChestBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ChestBlock.java.patch
new file mode 100644
index 0000000000..2470949692
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ChestBlock.java.patch
@@ -0,0 +1,78 @@
+--- a/net/minecraft/world/level/block/ChestBlock.java
++++ b/net/minecraft/world/level/block/ChestBlock.java
+@@ -85,9 +89,17 @@
+ 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 first, final ChestBlockEntity second) {
+ final Container container = new CompoundContainer(first, second);
+@@ -104,15 +106,17 @@
+ }
+ }
+
+- @Override
+- public Component getDisplayName() {
+- if (first.hasCustomName()) {
+- return first.getDisplayName();
+- } else {
+- return (Component)(second.hasCustomName() ? second.getDisplayName() : Component.translatable("container.chestDouble"));
+- }
+- }
+- });
++ // CraftBukkit start
++ public static class DoubleInventory implements ITileInventory {
++
++ private final ChestBlockEntity tileentitychest;
++ private final ChestBlockEntity tileentitychest1;
++ public final CompoundContainer inventorylargechest;
++
++ public DoubleInventory(ChestBlockEntity tileentitychest, ChestBlockEntity tileentitychest1, CompoundContainer inventorylargechest) {
++ this.tileentitychest = tileentitychest;
++ this.tileentitychest1 = tileentitychest1;
++ this.inventorylargechest = inventorylargechest;
+ }
+
+ @Override
+@@ -125,6 +136,7 @@
+ return Optional.empty();
+ }
+ };
++ // CraftBukkit end
+
+ @Override
+ public MapCodec<? extends ChestBlock> codec() {
+@@ -298,10 +308,17 @@
+
+ @Nullable
+ @Override
+- public MenuProvider getMenuProvider(BlockState state, Level level, BlockPos pos) {
+- return this.combine(state, level, pos, false).apply(MENU_PROVIDER_COMBINER).orElse(null);
++ public ITileInventory getMenuProvider(IBlockData state, Level level, BlockPos pos) {
++ // CraftBukkit start
++ return getMenuProvider(state, level, pos, false);
+ }
+
++ @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
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ChorusFlowerBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ChorusFlowerBlock.java.patch
new file mode 100644
index 0000000000..451b46f71b
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ChorusFlowerBlock.java.patch
@@ -0,0 +1,95 @@
+--- 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 -> instance.group(
+@@ -95,9 +103,13 @@
+ flag = true;
+ }
+
+- if (flag && allNeighborsEmpty(level, blockPos, null) && level.isEmptyBlock(pos.above(2))) {
+- level.setBlock(pos, ChorusPlantBlock.getStateWithConnections(level, pos, this.plant.defaultBlockState()), 2);
+- this.placeGrownFlower(level, blockPos, 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) {
+ int i1 = random.nextInt(4);
+ if (flag1) {
+@@ -106,24 +118,35 @@
+
+ boolean flag2 = false;
+
+- for (int i3 = 0; i3 < i1; i3++) {
+- Direction randomDirection = Direction.Plane.HORIZONTAL.getRandomDirection(random);
+- BlockPos blockPos1 = pos.relative(randomDirection);
+- if (level.isEmptyBlock(blockPos1)
+- && level.isEmptyBlock(blockPos1.below())
+- && allNeighborsEmpty(level, blockPos1, randomDirection.getOpposite())) {
+- this.placeGrownFlower(level, blockPos1, i + 1);
+- flag2 = true;
++ for (int l = 0; l < j; ++l) {
++ Direction enumdirection = Direction.Plane.HORIZONTAL.getRandomDirection(random);
++ BlockPos blockposition2 = pos.relative(enumdirection);
++
++ 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) {
+ level.setBlock(pos, ChorusPlantBlock.getStateWithConnections(level, pos, this.plant.defaultBlockState()), 2);
+ } else {
+- this.placeDeadFlower(level, pos);
++ // 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(level, pos);
++ // CraftBukkit start - add event
++ if (CraftEventFactory.handleBlockGrowEvent(level, pos, this.defaultBlockState().setValue(ChorusFlowerBlock.AGE, Integer.valueOf(5)), 2)) {
++ this.placeDeadFlower(level, pos);
++ }
++ // CraftBukkit end
+ }
+ }
+ }
+@@ -249,10 +279,16 @@
+ }
+
+ @Override
+- public void onProjectileHit(Level level, BlockState state, BlockHitResult hit, Projectile projectile) {
+- BlockPos blockPos = hit.getBlockPos();
+- if (!level.isClientSide && projectile.mayInteract(level, blockPos) && projectile.mayBreak(level)) {
+- level.destroyBlock(blockPos, true, projectile);
++ public void onProjectileHit(Level level, IBlockData state, BlockHitResult hit, Projectile projectile) {
++ BlockPos blockposition = hit.getBlockPos();
++
++ 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-vineflower-stripped/net/minecraft/world/level/block/CocoaBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CocoaBlock.java.patch
new file mode 100644
index 0000000000..c7c8c72b71
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CocoaBlock.java.patch
@@ -0,0 +1,21 @@
+--- 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 {
+ public static final MapCodec<CocoaBlock> CODEC = simpleCodec(CocoaBlock::new);
+@@ -131,8 +131,8 @@
+ }
+
+ @Override
+- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
+- level.setBlock(pos, state.setValue(AGE, Integer.valueOf(state.getValue(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-vineflower-stripped/net/minecraft/world/level/block/CommandBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CommandBlock.java.patch
new file mode 100644
index 0000000000..4df7027985
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CommandBlock.java.patch
@@ -0,0 +1,47 @@
+--- a/net/minecraft/world/level/block/CommandBlock.java
++++ b/net/minecraft/world/level/block/CommandBlock.java
+@@ -31,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 -> instance.group(Codec.BOOL.fieldOf("automatic").forGetter(commandBlock -> commandBlock.automatic), propertiesCodec())
+@@ -62,14 +66,27 @@
+ @Override
+ public void neighborChanged(BlockState state, Level level, BlockPos pos, Block block, BlockPos fromPos, boolean isMoving) {
+ if (!level.isClientSide) {
+- if (level.getBlockEntity(pos) instanceof CommandBlockEntity commandBlockEntity) {
+- boolean hasNeighborSignal = level.hasNeighborSignal(pos);
+- boolean isPowered = commandBlockEntity.isPowered();
+- commandBlockEntity.setPowered(hasNeighborSignal);
+- if (!isPowered && !commandBlockEntity.isAutomatic() && commandBlockEntity.getMode() != CommandBlockEntity.Mode.SEQUENCE) {
+- if (hasNeighborSignal) {
+- commandBlockEntity.markConditionMet();
+- level.scheduleTick(pos, this, 1);
++ BlockEntity tileentity = level.getBlockEntity(pos);
++
++ 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;
++
++ 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) {
++ tileentitycommand.markConditionMet();
++ level.scheduleTick(pos, (Block) this, 1);
+ }
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ComparatorBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ComparatorBlock.java.patch
new file mode 100644
index 0000000000..db3ff9b0f0
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ComparatorBlock.java.patch
@@ -0,0 +1,42 @@
+--- 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 {
+ public static final MapCodec<ComparatorBlock> CODEC = simpleCodec(ComparatorBlock::new);
+@@ -157,13 +162,24 @@
+ comparatorBlockEntity.setOutputSignal(i);
+ }
+
+- if (i1 != i || state.getValue(MODE) == ComparatorMode.COMPARE) {
+- boolean shouldTurnOn = this.shouldTurnOn(level, pos, state);
+- boolean flag = state.getValue(POWERED);
+- if (flag && !shouldTurnOn) {
+- level.setBlock(pos, state.setValue(POWERED, Boolean.valueOf(false)), 2);
+- } else if (!flag && shouldTurnOn) {
+- level.setBlock(pos, state.setValue(POWERED, Boolean.valueOf(true)), 2);
++ if (j != i || state.getValue(ComparatorBlock.MODE) == ComparatorMode.COMPARE) {
++ boolean flag = this.shouldTurnOn(level, pos, state);
++ boolean flag1 = (Boolean) state.getValue(ComparatorBlock.POWERED);
++
++ if (flag1 && !flag) {
++ // 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) {
++ // 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, pos, state);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ComposterBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ComposterBlock.java.patch
new file mode 100644
index 0000000000..690b7d1f1b
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ComposterBlock.java.patch
@@ -0,0 +1,158 @@
+--- 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 {
+ public static final MapCodec<ComposterBlock> CODEC = simpleCodec(ComposterBlock::new);
+@@ -251,10 +255,19 @@
+ }
+ }
+
+- public static BlockState insertItem(Entity entity, BlockState state, ServerLevel level, ItemStack stack, BlockPos pos) {
+- int i = state.getValue(LEVEL);
+- if (i < 7 && COMPOSTABLES.containsKey(stack.getItem())) {
+- BlockState blockState = addItem(entity, state, level, pos, stack);
++ public static IBlockData insertItem(Entity entity, IBlockData state, ServerLevel level, ItemStack stack, BlockPos pos) {
++ int i = (Integer) state.getValue(ComposterBlock.LEVEL);
++
++ 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
++
+ stack.shrink(1);
+ return blockState;
+ } else {
+@@ -262,7 +275,15 @@
+ }
+ }
+
+- public static BlockState extractProduce(Entity entity, BlockState state, Level level, BlockPos pos) {
++ 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(pos, 0.5, 1.01, 0.5).offsetRandom(level.random, 0.7F);
+ ItemEntity itemEntity = new ItemEntity(level, vec3.x(), vec3.y(), vec3.z(), new ItemStack(Items.BONE_MEAL));
+@@ -282,11 +306,18 @@
+ return blockState;
+ }
+
+- static BlockState addItem(@Nullable Entity entity, BlockState state, LevelAccessor level, BlockPos pos, ItemStack stack) {
+- int i = state.getValue(LEVEL);
+- float _float = COMPOSTABLES.getFloat(stack.getItem());
+- if ((i != 0 || !(_float > 0.0F)) && !(level.getRandom().nextDouble() < (double)_float)) {
+- return state;
++ 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) && rand >= (double) f) {
++ return iblockdata;
+ } else {
+ int i1 = i + 1;
+ BlockState blockState = state.setValue(LEVEL, Integer.valueOf(i1));
+@@ -329,18 +362,26 @@
+ }
+
+ @Override
+- public WorldlyContainer getContainer(BlockState state, LevelAccessor level, BlockPos pos) {
+- int i = state.getValue(LEVEL);
+- if (i == 8) {
+- return new ComposterBlock.OutputContainer(state, level, pos, new ItemStack(Items.BONE_MEAL));
+- } else {
+- return (WorldlyContainer)(i < 7 ? new ComposterBlock.InputContainer(state, level, pos) : new ComposterBlock.EmptyContainer());
+- }
++ public WorldlyContainer getContainer(IBlockData state, LevelAccessor level, BlockPos pos) {
++ int i = (Integer) state.getValue(ComposterBlock.LEVEL);
++
++ // 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)));
+ }
+
+- static class EmptyContainer extends SimpleContainer implements WorldlyContainer {
+- public EmptyContainer() {
+- super(0);
++ public static class OutputContainer extends SimpleContainer implements WorldlyContainer {
++
++ private final IBlockData state;
++ private final LevelAccessor level;
++ private final BlockPos pos;
++ private boolean changed;
++
++ 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
+@@ -357,6 +403,19 @@
+ public boolean canTakeItemThroughFace(int index, ItemStack stack, Direction direction) {
+ return false;
+ }
++
++ @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
++ }
+ }
+
+ static class InputContainer extends SimpleContainer implements WorldlyContainer {
+@@ -367,6 +427,7 @@
+
+ public InputContainer(BlockState state, LevelAccessor level, BlockPos pos) {
+ super(1);
++ this.bukkitOwner = new CraftBlockInventoryHolder(level, pos, this); // CraftBukkit
+ this.state = state;
+ this.level = level;
+ this.pos = pos;
+@@ -410,11 +470,9 @@
+ private final BlockPos pos;
+ private boolean changed;
+
+- public OutputContainer(BlockState state, LevelAccessor level, BlockPos pos, ItemStack stack) {
+- super(stack);
+- this.state = state;
+- this.level = level;
+- this.pos = pos;
++ public EmptyContainer(LevelAccessor generatoraccess, BlockPos blockposition) { // CraftBukkit
++ super(0);
++ this.bukkitOwner = new CraftBlockInventoryHolder(generatoraccess, blockposition, this); // CraftBukkit
+ }
+
+ @Override
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ConcretePowderBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ConcretePowderBlock.java.patch
new file mode 100644
index 0000000000..7334a8a06a
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ConcretePowderBlock.java.patch
@@ -0,0 +1,87 @@
+--- a/net/minecraft/world/level/block/ConcretePowderBlock.java
++++ b/net/minecraft/world/level/block/ConcretePowderBlock.java
+@@ -15,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 -> instance.group(
+@@ -37,16 +42,34 @@
+ @Override
+ public void onLand(Level level, BlockPos pos, BlockState state, BlockState replaceableState, FallingBlockEntity fallingBlock) {
+ if (shouldSolidify(level, pos, replaceableState)) {
+- level.setBlock(pos, this.concrete.defaultBlockState(), 3);
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(level, pos, this.concrete.defaultBlockState(), 3); // CraftBukkit
+ }
+ }
+
+ @Override
+- public BlockState getStateForPlacement(BlockPlaceContext context) {
+- BlockGetter level = context.getLevel();
+- BlockPos clickedPos = context.getClickedPos();
+- BlockState blockState = level.getBlockState(clickedPos);
+- return shouldSolidify(level, clickedPos, blockState) ? this.concrete.defaultBlockState() : super.getStateForPlacement(context);
++ public IBlockData getStateForPlacement(BlockPlaceContext context) {
++ Level world = context.getLevel();
++ BlockPos blockposition = context.getClickedPos();
++ IBlockData iblockdata = world.getBlockState(blockposition);
++
++ // 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 level, BlockPos pos, BlockState state) {
+@@ -77,10 +105,26 @@
+ }
+
+ @Override
+- public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, LevelAccessor level, BlockPos currentPos, BlockPos facingPos) {
+- return touchesLiquid(level, currentPos)
+- ? this.concrete.defaultBlockState()
+- : super.updateShape(state, facing, facingState, level, currentPos, facingPos);
++ 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-vineflower-stripped/net/minecraft/world/level/block/CoralBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CoralBlock.java.patch
new file mode 100644
index 0000000000..39f509e892
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CoralBlock.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/world/level/block/CoralBlock.java
++++ b/net/minecraft/world/level/block/CoralBlock.java
+@@ -37,6 +39,11 @@
+ @Override
+ public void tick(BlockState 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-vineflower-stripped/net/minecraft/world/level/block/CoralFanBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CoralFanBlock.java.patch
new file mode 100644
index 0000000000..e5fbee2b9a
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CoralFanBlock.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/world/level/block/CoralFanBlock.java
++++ b/net/minecraft/world/level/block/CoralFanBlock.java
+@@ -38,7 +40,12 @@
+ @Override
+ public void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+ if (!scanForWater(state, level, pos)) {
+- level.setBlock(pos, this.deadBlock.defaultBlockState().setValue(WATERLOGGED, Boolean.valueOf(false)), 2);
++ // 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-vineflower-stripped/net/minecraft/world/level/block/CoralPlantBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CoralPlantBlock.java.patch
new file mode 100644
index 0000000000..9d6f2c3db4
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CoralPlantBlock.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/world/level/block/CoralPlantBlock.java
++++ b/net/minecraft/world/level/block/CoralPlantBlock.java
+@@ -43,7 +45,12 @@
+ @Override
+ public void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+ if (!scanForWater(state, level, pos)) {
+- level.setBlock(pos, this.deadBlock.defaultBlockState().setValue(WATERLOGGED, Boolean.valueOf(false)), 2);
++ // 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-vineflower-stripped/net/minecraft/world/level/block/CoralWallFanBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CoralWallFanBlock.java.patch
new file mode 100644
index 0000000000..1c33d2a2d2
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CoralWallFanBlock.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/world/level/block/CoralWallFanBlock.java
++++ b/net/minecraft/world/level/block/CoralWallFanBlock.java
+@@ -38,7 +40,12 @@
+ @Override
+ public void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+ if (!scanForWater(state, level, pos)) {
+- level.setBlock(pos, this.deadBlock.defaultBlockState().setValue(WATERLOGGED, Boolean.valueOf(false)).setValue(FACING, state.getValue(FACING)), 2);
++ // 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-vineflower-stripped/net/minecraft/world/level/block/CropBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CropBlock.java.patch
new file mode 100644
index 0000000000..9376dde184
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/CropBlock.java.patch
@@ -0,0 +1,49 @@
+--- 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 {
+ public static final MapCodec<CropBlock> CODEC = simpleCodec(CropBlock::new);
+@@ -85,11 +78,13 @@
+ @Override
+ public void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+ if (level.getRawBrightness(pos, 0) >= 9) {
+- int age = this.getAge(state);
+- if (age < this.getMaxAge()) {
+- float growthSpeed = getGrowthSpeed(this, level, pos);
+- if (random.nextInt((int)(25.0F / growthSpeed) + 1) == 0) {
+- level.setBlock(pos, this.getStateForAge(age + 1), 2);
++ int i = this.getAge(state);
++
++ if (i < this.getMaxAge()) {
++ float f = getGrowthSpeed(this, level, pos);
++
++ if (random.nextInt((int) (25.0F / f) + 1) == 0) {
++ CraftEventFactory.handleBlockGrowEvent(level, pos, this.getStateForAge(i + 1), 2); // CraftBukkit
+ }
+ }
+ }
+@@ -102,7 +99,7 @@
+ i = maxAge;
+ }
+
+- level.setBlock(pos, this.getStateForAge(i), 2);
++ CraftEventFactory.handleBlockGrowEvent(level, pos, this.getStateForAge(i), 2); // CraftBukkit
+ }
+
+ protected int getBonemealAgeIncrease(Level level) {
+@@ -163,8 +160,8 @@
+ }
+
+ @Override
+- public void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
+- if (entity instanceof Ravager && level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
++ 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);
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DaylightDetectorBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DaylightDetectorBlock.java.patch
new file mode 100644
index 0000000000..944ed45073
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DaylightDetectorBlock.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/world/level/block/DaylightDetectorBlock.java
++++ b/net/minecraft/world/level/block/DaylightDetectorBlock.java
+@@ -69,9 +72,10 @@
+ i = Math.round((float)i * Mth.cos(sunAngle));
+ }
+
+- int var7 = Mth.clamp(i, 0, 15);
+- if (state.getValue(POWER) != var7) {
+- level.setBlock(pos, state.setValue(POWER, Integer.valueOf(var7)), 3);
++ i = Mth.clamp(i, 0, 15);
++ 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-vineflower-stripped/net/minecraft/world/level/block/DetectorRailBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DetectorRailBlock.java.patch
new file mode 100644
index 0000000000..48e8df7fa7
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DetectorRailBlock.java.patch
@@ -0,0 +1,29 @@
+--- a/net/minecraft/world/level/block/DetectorRailBlock.java
++++ b/net/minecraft/world/level/block/DetectorRailBlock.java
+@@ -24,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 {
+ public static final MapCodec<DetectorRailBlock> CODEC = simpleCodec(DetectorRailBlock::new);
+@@ -91,6 +87,18 @@
+ flag1 = true;
+ }
+
++ 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) {
+ BlockState blockState = state.setValue(POWERED, Boolean.valueOf(true));
+ level.setBlock(pos, blockState, 3);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DiodeBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DiodeBlock.java.patch
new file mode 100644
index 0000000000..dc5eef8e62
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DiodeBlock.java.patch
@@ -0,0 +1,44 @@
+--- 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 {
+ protected static final VoxelShape SHAPE = Block.box(0.0, 0.0, 0.0, 16.0, 2.0, 16.0);
+@@ -50,15 +53,26 @@
+ @Override
+ public void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+ if (!this.isLocked(level, pos, state)) {
+- boolean flag = state.getValue(POWERED);
+- boolean shouldTurnOn = this.shouldTurnOn(level, pos, state);
+- if (flag && !shouldTurnOn) {
+- level.setBlock(pos, state.setValue(POWERED, Boolean.valueOf(false)), 2);
++ boolean flag = (Boolean) state.getValue(DiodeBlock.POWERED);
++ boolean flag1 = this.shouldTurnOn(level, pos, state);
++
++ if (flag && !flag1) {
++ // 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) {
+- level.setBlock(pos, state.setValue(POWERED, Boolean.valueOf(true)), 2);
+- if (!shouldTurnOn) {
+- level.scheduleTick(pos, this, this.getDelay(state), TickPriority.VERY_HIGH);
++ // 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) {
++ level.scheduleTick(pos, (Block) this, this.getDelay(state), TickPriority.VERY_HIGH);
++ }
+ }
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DispenserBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DispenserBlock.java.patch
new file mode 100644
index 0000000000..e4f1362f72
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DispenserBlock.java.patch
@@ -0,0 +1,27 @@
+--- a/net/minecraft/world/level/block/DispenserBlock.java
++++ b/net/minecraft/world/level/block/DispenserBlock.java
+@@ -48,6 +49,7 @@
+ new Object2ObjectOpenHashMap<>(), map -> map.defaultReturnValue(new DefaultDispenseItemBehavior())
+ );
+ private static final int TRIGGER_DURATION = 4;
++ public static boolean eventFired = false; // CraftBukkit
+
+ @Override
+ public MapCodec<? extends DispenserBlock> codec() {
+@@ -93,10 +98,12 @@
+ serverLevel.levelEvent(1001, blockPos, 0);
+ serverLevel.gameEvent(GameEvent.BLOCK_ACTIVATE, blockPos, GameEvent.Context.of(dispenserBlockEntity.getBlockState()));
+ } else {
+- ItemStack item = dispenserBlockEntity.getItem(randomSlot);
+- DispenseItemBehavior dispenseMethod = this.getDispenseMethod(item);
+- if (dispenseMethod != DispenseItemBehavior.NOOP) {
+- dispenserBlockEntity.setItem(randomSlot, dispenseMethod.dispense(blockSource, item));
++ ItemStack itemstack = tileentitydispenser.getItem(i);
++ DispenseItemBehavior idispensebehavior = this.getDispenseMethod(itemstack);
++
++ if (idispensebehavior != DispenseItemBehavior.NOOP) {
++ eventFired = false; // CraftBukkit - reset event status
++ tileentitydispenser.setItem(i, idispensebehavior.dispense(sourceblock, itemstack));
+ }
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DoorBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DoorBlock.java.patch
new file mode 100644
index 0000000000..4c9bb9185a
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -37,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 {
+ public static final MapCodec<DoorBlock> CODEC = RecordCodecBuilder.mapCodec(
+@@ -223,13 +220,28 @@
+ }
+
+ @Override
+- public void neighborChanged(BlockState state, Level level, BlockPos pos, Block block, BlockPos fromPos, boolean isMoving) {
+- boolean flag = level.hasNeighborSignal(pos)
+- || level.hasNeighborSignal(pos.relative(state.getValue(HALF) == DoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN));
+- if (!this.defaultBlockState().is(block) && flag != state.getValue(POWERED)) {
+- if (flag != state.getValue(OPEN)) {
+- this.playSound(null, level, pos, flag);
+- level.gameEvent(null, flag ? GameEvent.BLOCK_OPEN : GameEvent.BLOCK_CLOSE, pos);
++ 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);
++
++ 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(pos, state.setValue(POWERED, Boolean.valueOf(flag)).setValue(OPEN, Boolean.valueOf(flag)), 2);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DoublePlantBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DoublePlantBlock.java.patch
new file mode 100644
index 0000000000..c37c0fc460
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DoublePlantBlock.java.patch
@@ -0,0 +1,35 @@
+--- a/net/minecraft/world/level/block/DoublePlantBlock.java
++++ b/net/minecraft/world/level/block/DoublePlantBlock.java
+@@ -105,15 +100,23 @@
+ super.playerDestroy(level, player, pos, Blocks.AIR.defaultBlockState(), te, stack);
+ }
+
+- protected static void preventDropFromBottomPart(Level level, BlockPos blockPos, BlockState blockState, Player player) {
+- DoubleBlockHalf doubleBlockHalf = blockState.getValue(HALF);
+- if (doubleBlockHalf == DoubleBlockHalf.UPPER) {
+- BlockPos blockPos1 = blockPos.below();
+- BlockState blockState1 = level.getBlockState(blockPos1);
+- if (blockState1.is(blockState.getBlock()) && blockState1.getValue(HALF) == DoubleBlockHalf.LOWER) {
+- BlockState blockState2 = blockState1.getFluidState().is(Fluids.WATER) ? Blocks.WATER.defaultBlockState() : Blocks.AIR.defaultBlockState();
+- level.setBlock(blockPos1, blockState2, 35);
+- level.levelEvent(player, 2001, blockPos1, Block.getId(blockState1));
++ 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 (blockpropertydoubleblockhalf == BlockPropertyDoubleBlockHalf.UPPER) {
++ BlockPos blockposition1 = blockposition.below();
++ IBlockData iblockdata1 = world.getBlockState(blockposition1);
++
++ if (iblockdata1.is(iblockdata.getBlock()) && iblockdata1.getValue(DoublePlantBlock.HALF) == BlockPropertyDoubleBlockHalf.LOWER) {
++ IBlockData iblockdata2 = iblockdata1.getFluidState().is((Fluid) Fluids.WATER) ? Blocks.WATER.defaultBlockState() : Blocks.AIR.defaultBlockState();
++
++ world.setBlock(blockposition1, iblockdata2, 35);
++ world.levelEvent(entityhuman, 2001, blockposition1, Block.getId(iblockdata1));
+ }
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DragonEggBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DragonEggBlock.java.patch
new file mode 100644
index 0000000000..f0402a4d3f
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DragonEggBlock.java.patch
@@ -0,0 +1,40 @@
+--- 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 {
+ public static final MapCodec<DragonEggBlock> CODEC = simpleCodec(DragonEggBlock::new);
+@@ -49,13 +51,22 @@
+ private void teleport(BlockState state, Level level, BlockPos pos) {
+ WorldBorder worldBorder = level.getWorldBorder();
+
+- for (int i = 0; i < 1000; i++) {
+- BlockPos blockPos = pos.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(blockPos).isAir() && worldBorder.isWithinBounds(blockPos)) {
++ for (int i = 0; i < 1000; ++i) {
++ BlockPos blockposition1 = pos.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(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 i1 = 0; i1 < 128; i1++) {
+ double randomDouble = level.random.nextDouble();
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DropExperienceBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DropExperienceBlock.java.patch
new file mode 100644
index 0000000000..9a32d222bf
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DropExperienceBlock.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/level/block/DropExperienceBlock.java
++++ b/net/minecraft/world/level/block/DropExperienceBlock.java
+@@ -32,8 +31,16 @@
+ @Override
+ public void spawnAfterBreak(BlockState state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) {
+ super.spawnAfterBreak(state, level, pos, stack, dropExperience);
+- if (dropExperience) {
+- this.tryDropExperience(level, pos, stack, this.xpRange);
++ // CraftBukkit start - Delegate to getExpDrop
++ }
++
++ @Override
++ public int getExpDrop(IBlockData iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) {
++ if (flag) {
++ return this.tryDropExperience(worldserver, blockposition, itemstack, this.xpRange);
+ }
++
++ return 0;
++ // CraftBukkit end
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DropperBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DropperBlock.java.patch
new file mode 100644
index 0000000000..e7aa037c18
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/DropperBlock.java.patch
@@ -0,0 +1,53 @@
+--- a/net/minecraft/world/level/block/DropperBlock.java
++++ b/net/minecraft/world/level/block/DropperBlock.java
+@@ -18,11 +19,14 @@
+ 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
+ public MapCodec<DropperBlock> codec() {
+@@ -62,13 +71,30 @@
+ if (containerAt == null) {
+ itemStack = DISPENSE_BEHAVIOUR.dispense(blockSource, item);
+ } else {
+- itemStack = HopperBlockEntity.addItem(dispenserBlockEntity, containerAt, item.copy().split(1), direction.getOpposite());
+- if (itemStack.isEmpty()) {
+- itemStack = item.copy();
+- itemStack.shrink(1);
++ // 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 {
+ itemStack = item.copy();
+ }
++
++ 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 {
++ itemstack1 = itemstack.copy();
++ }
+ }
+
+ dispenserBlockEntity.setItem(randomSlot, itemStack);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/EndPortalBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/EndPortalBlock.java.patch
new file mode 100644
index 0000000000..47f398995a
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/EndPortalBlock.java.patch
@@ -0,0 +1,51 @@
+--- 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 {
+ public static final MapCodec<EndPortalBlock> CODEC = simpleCodec(EndPortalBlock::new);
+@@ -45,21 +51,25 @@
+ }
+
+ @Override
+- public void entityInside(BlockState 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.dimension() == Level.END ? Level.OVERWORLD : Level.END;
+- ServerLevel level1 = ((ServerLevel)level).getServer().getLevel(resourceKey);
+- if (level1 == null) {
+- return;
++ 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 (worldserver == null) {
++ // return; // CraftBukkit - always fire event in case plugins wish to change it
+ }
+
+- entity.changeDimension(level1);
++ // 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-vineflower-stripped/net/minecraft/world/level/block/FarmBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/FarmBlock.java.patch
new file mode 100644
index 0000000000..af8d092023
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/FarmBlock.java.patch
@@ -0,0 +1,79 @@
+--- a/net/minecraft/world/level/block/FarmBlock.java
++++ b/net/minecraft/world/level/block/FarmBlock.java
+@@ -27,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 {
+ public static final MapCodec<FarmBlock> CODEC = simpleCodec(FarmBlock::new);
+@@ -88,32 +95,52 @@
+ int i = state.getValue(MOISTURE);
+ if (!isNearWater(level, pos) && !level.isRainingAt(pos.above())) {
+ if (i > 0) {
+- level.setBlock(pos, state.setValue(MOISTURE, Integer.valueOf(i - 1)), 2);
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleMoistureChangeEvent(level, pos, (IBlockData) state.setValue(FarmBlock.MOISTURE, i - 1), 2); // CraftBukkit
+ } else if (!shouldMaintainFarmland(level, pos)) {
+ turnToDirt(null, state, level, pos);
+ }
+ } else if (i < 7) {
+- level.setBlock(pos, state.setValue(MOISTURE, Integer.valueOf(7)), 2);
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleMoistureChangeEvent(level, pos, (IBlockData) state.setValue(FarmBlock.MOISTURE, 7), 2); // CraftBukkit
+ }
+ }
+
+ @Override
+- public void fallOn(Level level, BlockState state, BlockPos pos, Entity entity, float fallDistance) {
+- 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) {
++ 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, state, pos, entity, fallDistance);
++ // super.fallOn(world, iblockdata, blockposition, entity, f); // CraftBukkit - moved up
+ }
+
+- public static void turnToDirt(@Nullable Entity entity, BlockState state, Level level, BlockPos pos) {
+- BlockState blockState = pushEntitiesUp(state, Blocks.DIRT.defaultBlockState(), level, pos);
+- level.setBlockAndUpdate(pos, blockState);
+- level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(entity, blockState));
++ 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(pos, iblockdata1);
++ level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(entity, iblockdata1));
+ }
+
+ private static boolean shouldMaintainFarmland(BlockGetter level, BlockPos pos) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/FenceGateBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/FenceGateBlock.java.patch
new file mode 100644
index 0000000000..1e6032f92b
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/FenceGateBlock.java.patch
@@ -0,0 +1,40 @@
+--- a/net/minecraft/world/level/block/FenceGateBlock.java
++++ b/net/minecraft/world/level/block/FenceGateBlock.java
+@@ -195,19 +167,24 @@
+ @Override
+ public void neighborChanged(BlockState state, Level level, BlockPos pos, Block block, BlockPos fromPos, boolean isMoving) {
+ if (!level.isClientSide) {
+- boolean hasNeighborSignal = level.hasNeighborSignal(pos);
+- if (state.getValue(POWERED) != hasNeighborSignal) {
+- level.setBlock(pos, state.setValue(POWERED, Boolean.valueOf(hasNeighborSignal)).setValue(OPEN, Boolean.valueOf(hasNeighborSignal)), 2);
+- if (state.getValue(OPEN) != hasNeighborSignal) {
+- level.playSound(
+- null,
+- pos,
+- hasNeighborSignal ? this.type.fenceGateOpen() : this.type.fenceGateClose(),
+- SoundSource.BLOCKS,
+- 1.0F,
+- level.getRandom().nextFloat() * 0.1F + 0.9F
+- );
+- level.gameEvent(null, hasNeighborSignal ? GameEvent.BLOCK_OPEN : GameEvent.BLOCK_CLOSE, pos);
++ 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) state.getValue(FenceGateBlock.POWERED) != flag1) {
++ level.setBlock(pos, (IBlockData) ((IBlockData) state.setValue(FenceGateBlock.POWERED, flag1)).setValue(FenceGateBlock.OPEN, flag1), 2);
++ if ((Boolean) state.getValue(FenceGateBlock.OPEN) != flag1) {
++ level.playSound((Player) null, pos, flag1 ? this.type.fenceGateOpen() : this.type.fenceGateClose(), SoundSource.BLOCKS, 1.0F, level.getRandom().nextFloat() * 0.1F + 0.9F);
++ level.gameEvent((Entity) null, flag1 ? GameEvent.BLOCK_OPEN : GameEvent.BLOCK_CLOSE, pos);
+ }
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/FireBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/FireBlock.java.patch
new file mode 100644
index 0000000000..2fa5e5c3d2
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/FireBlock.java.patch
@@ -0,0 +1,182 @@
+--- a/net/minecraft/world/level/block/FireBlock.java
++++ b/net/minecraft/world/level/block/FireBlock.java
+@@ -29,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 {
+ public static final MapCodec<FireBlock> CODEC = simpleCodec(FireBlock::new);
+@@ -113,8 +106,25 @@
+ }
+
+ @Override
+- public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, LevelAccessor level, BlockPos currentPos, BlockPos facingPos) {
+- return this.canSurvive(state, level, currentPos) ? this.getStateWithAge(level, currentPos, state.getValue(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
+@@ -157,14 +173,15 @@
+ level.scheduleTick(pos, this, getFireTickDelay(level.random));
+ if (level.getGameRules().getBoolean(GameRules.RULE_DOFIRETICK)) {
+ if (!state.canSurvive(level, pos)) {
+- level.removeBlock(pos, false);
++ fireExtinguished(level, pos); // CraftBukkit - invalid place location
+ }
+
+- BlockState blockState = level.getBlockState(pos.below());
+- boolean isTag = blockState.is(level.dimensionType().infiniburn());
+- int i = state.getValue(AGE);
+- if (!isTag && level.isRaining() && this.isNearRain(level, pos) && random.nextFloat() < 0.2F + (float)i * 0.03F) {
+- level.removeBlock(pos, false);
++ IBlockData iblockdata1 = level.getBlockState(pos.below());
++ boolean flag = iblockdata1.is(level.dimensionType().infiniburn());
++ int i = (Integer) state.getValue(FireBlock.AGE);
++
++ 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 min = Math.min(15, i + random.nextInt(3) / 2);
+ if (i != min) {
+@@ -174,16 +192,17 @@
+
+ if (!isTag) {
+ if (!this.isValidFireLocation(level, pos)) {
+- BlockPos blockPos = pos.below();
+- if (!level.getBlockState(blockPos).isFaceSturdy(level, blockPos, Direction.UP) || i > 3) {
+- level.removeBlock(pos, false);
++ BlockPos blockposition1 = pos.below();
++
++ if (!level.getBlockState(blockposition1).isFaceSturdy(level, blockposition1, Direction.UP) || i > 3) {
++ fireExtinguished(level, pos); // CraftBukkit
+ }
+
+ return;
+ }
+
+ if (i == 15 && random.nextInt(4) == 0 && !this.canBurn(level.getBlockState(pos.below()))) {
+- level.removeBlock(pos, false);
++ fireExtinguished(level, pos); // CraftBukkit
+ return;
+ }
+ }
+@@ -198,13 +210,24 @@
+ this.checkBurnOut(level, pos.south(), 300 + i1, random, i);
+ BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
+
+- for (int i2 = -1; i2 <= 1; i2++) {
+- for (int i3 = -1; i3 <= 1; i3++) {
+- for (int i4 = -1; i4 <= 4; i4++) {
+- if (i2 != 0 || i4 != 0 || i3 != 0) {
+- int i5 = 100;
+- if (i4 > 1) {
+- i5 += (i4 - 1) * 100;
++ // 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) {
++ for (int j1 = -1; j1 <= 4; ++j1) {
++ if (l != 0 || j1 != 0 || i1 != 0) {
++ int k1 = 100;
++
++ if (j1 > 1) {
++ k1 += (j1 - 1) * 100;
+ }
+
+ mutableBlockPos.setWithOffset(pos, i2, i4, i3);
+@@ -215,9 +240,18 @@
+ i6 /= 2;
+ }
+
+- if (i6 > 0 && random.nextInt(i5) <= i6 && (!level.isRaining() || !this.isNearRain(level, mutableBlockPos))) {
+- int min1 = Math.min(15, i + random.nextInt(5) / 4);
+- level.setBlock(mutableBlockPos, this.getStateWithAge(level, mutableBlockPos, min1), 3);
++ if (i2 > 0 && random.nextInt(k1) <= i2 && (!level.isRaining() || !this.isNearRain(level, blockposition_mutableblockposition))) {
++ int j2 = Math.min(15, i + random.nextInt(5) / 4);
++
++ // 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
+ }
+ }
+ }
+@@ -248,13 +275,32 @@
+ : this.igniteOdds.getInt(state.getBlock());
+ }
+
+- private void checkBurnOut(Level level, BlockPos pos, int chance, RandomSource random, int age) {
+- int burnOdds = this.getBurnOdds(level.getBlockState(pos));
+- if (random.nextInt(chance) < burnOdds) {
+- BlockState blockState = level.getBlockState(pos);
+- if (random.nextInt(age + 10) < 5 && !level.isRainingAt(pos)) {
+- int min = Math.min(age + random.nextInt(5) / 4, 15);
+- level.setBlock(pos, this.getStateWithAge(level, pos, min), 3);
++ 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) {
++ IBlockData iblockdata = world.getBlockState(blockposition);
++
++ // 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);
++
++ world.setBlock(blockposition, this.getStateWithAge(world, blockposition, l), 3);
+ } else {
+ level.removeBlock(pos, false);
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/FungusBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/FungusBlock.java.patch
new file mode 100644
index 0000000000..31d4b67445
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/FungusBlock.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/level/block/FungusBlock.java
++++ b/net/minecraft/world/level/block/FungusBlock.java
+@@ -72,7 +72,16 @@
+ }
+
+ @Override
+- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
+- this.getFeature(level).ifPresent(holder -> holder.value().place(level, level.getChunkSource().getGenerator(), random, pos));
++ 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-vineflower-stripped/net/minecraft/world/level/block/GrowingPlantHeadBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/GrowingPlantHeadBlock.java.patch
new file mode 100644
index 0000000000..1c29745146
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/GrowingPlantHeadBlock.java.patch
@@ -0,0 +1,20 @@
+--- a/net/minecraft/world/level/block/GrowingPlantHeadBlock.java
++++ b/net/minecraft/world/level/block/GrowingPlantHeadBlock.java
+@@ -43,11 +43,12 @@
+ }
+
+ @Override
+- public void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+- if (state.getValue(AGE) < 25 && random.nextDouble() < this.growPerTickProbability) {
+- BlockPos blockPos = pos.relative(this.growthDirection);
+- if (this.canGrowInto(level.getBlockState(blockPos))) {
+- level.setBlockAndUpdate(blockPos, this.getGrowIntoState(state, level.random));
++ public void randomTick(IBlockData state, ServerLevel level, BlockPos pos, RandomSource random) {
++ if ((Integer) state.getValue(GrowingPlantHeadBlock.AGE) < 25 && random.nextDouble() < this.growPerTickProbability) {
++ BlockPos blockposition1 = pos.relative(this.growthDirection);
++
++ 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-vineflower-stripped/net/minecraft/world/level/block/IceBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/IceBlock.java.patch
new file mode 100644
index 0000000000..4edb78a100
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -54,7 +58,12 @@
+ }
+ }
+
+- protected void melt(BlockState state, Level level, BlockPos pos) {
++ 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(pos, false);
+ } else {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/InfestedBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/InfestedBlock.java.patch
new file mode 100644
index 0000000000..b858fa3c4c
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/InfestedBlock.java.patch
@@ -0,0 +1,28 @@
+--- 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 {
+ public static final MapCodec<InfestedBlock> CODEC = RecordCodecBuilder.mapCodec(
+@@ -49,11 +50,12 @@
+ }
+
+ private void spawnInfestation(ServerLevel level, BlockPos pos) {
+- Silverfish silverfish = EntityType.SILVERFISH.create(level);
+- if (silverfish != null) {
+- silverfish.moveTo((double)pos.getX() + 0.5, (double)pos.getY(), (double)pos.getZ() + 0.5, 0.0F, 0.0F);
+- level.addFreshEntity(silverfish);
+- silverfish.spawnAnim();
++ Silverfish entitysilverfish = (Silverfish) EntityType.SILVERFISH.create(level);
++
++ 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-vineflower-stripped/net/minecraft/world/level/block/LayeredCauldronBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/LayeredCauldronBlock.java.patch
new file mode 100644
index 0000000000..a74549e358
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/LayeredCauldronBlock.java.patch
@@ -0,0 +1,99 @@
+--- a/net/minecraft/world/level/block/LayeredCauldronBlock.java
++++ b/net/minecraft/world/level/block/LayeredCauldronBlock.java
+@@ -16,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 {
+ public static final MapCodec<LayeredCauldronBlock> CODEC = RecordCodecBuilder.mapCodec(
+@@ -62,10 +66,12 @@
+ @Override
+ public void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
+ if (!level.isClientSide && entity.isOnFire() && this.isEntityInsideContent(state, pos, entity)) {
+- entity.clearFire();
++ // CraftBukkit start
+ if (entity.mayInteract(level, pos)) {
+ this.handleEntityOnFireInside(state, level, pos);
+ }
++ entity.clearFire();
++ // CraftBukkit end
+ }
+ }
+
+@@ -77,19 +87,43 @@
+ }
+ }
+
+- public static void lowerFillLevel(BlockState state, Level level, BlockPos pos) {
+- int i = state.getValue(LEVEL) - 1;
+- BlockState blockState = i == 0 ? Blocks.CAULDRON.defaultBlockState() : state.setValue(LEVEL, Integer.valueOf(i));
+- level.setBlockAndUpdate(pos, blockState);
+- level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(blockState));
++ public static void lowerFillLevel(IBlockData state, Level level, BlockPos pos) {
++ // CraftBukkit start
++ lowerFillLevel(state, level, pos, null, CauldronLevelChangeEvent.ChangeReason.UNKNOWN);
+ }
+
++ public static boolean lowerFillLevel(IBlockData iblockdata, Level world, BlockPos blockposition, Entity entity, CauldronLevelChangeEvent.ChangeReason reason) {
++ int i = (Integer) iblockdata.getValue(LayeredCauldronBlock.LEVEL) - 1;
++ IBlockData iblockdata1 = i == 0 ? Blocks.CAULDRON.defaultBlockState() : (IBlockData) iblockdata.setValue(LayeredCauldronBlock.LEVEL, i);
++
++ return changeLevel(iblockdata, world, blockposition, iblockdata1, entity, reason);
++ }
++
++ // 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
+- public void handlePrecipitation(BlockState state, Level level, BlockPos pos, Biome.Precipitation precipitation) {
+- if (CauldronBlock.shouldHandlePrecipitation(level, precipitation) && state.getValue(LEVEL) != 3 && precipitation == this.precipitationType) {
+- BlockState blockState = state.cycle(LEVEL);
+- level.setBlockAndUpdate(pos, blockState);
+- level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(blockState));
++ public void handlePrecipitation(IBlockData state, Level level, BlockPos pos, Biome.Precipitation precipitation) {
++ if (CauldronBlock.shouldHandlePrecipitation(level, precipitation) && (Integer) state.getValue(LayeredCauldronBlock.LEVEL) != 3 && precipitation == this.precipitationType) {
++ IBlockData iblockdata1 = (IBlockData) state.cycle(LayeredCauldronBlock.LEVEL);
++
++ changeLevel(state, level, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL); // CraftBukkit
+ }
+ }
+
+@@ -106,9 +140,13 @@
+ @Override
+ protected void receiveStalactiteDrip(BlockState state, Level level, BlockPos pos, Fluid fluid) {
+ if (!this.isFull(state)) {
+- BlockState blockState = state.setValue(LEVEL, Integer.valueOf(state.getValue(LEVEL) + 1));
+- level.setBlockAndUpdate(pos, blockState);
+- level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(blockState));
++ IBlockData iblockdata1 = (IBlockData) state.setValue(LayeredCauldronBlock.LEVEL, (Integer) state.getValue(LayeredCauldronBlock.LEVEL) + 1);
++
++ // 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-vineflower-stripped/net/minecraft/world/level/block/LeavesBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/LeavesBlock.java.patch
new file mode 100644
index 0000000000..993ad9adb7
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/LeavesBlock.java.patch
@@ -0,0 +1,25 @@
+--- a/net/minecraft/world/level/block/LeavesBlock.java
++++ b/net/minecraft/world/level/block/LeavesBlock.java
+@@ -23,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 {
+ public static final MapCodec<LeavesBlock> CODEC = simpleCodec(LeavesBlock::new);
+@@ -61,6 +58,14 @@
+ @Override
+ public void randomTick(BlockState 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-vineflower-stripped/net/minecraft/world/level/block/LecternBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/LecternBlock.java.patch
new file mode 100644
index 0000000000..db1ae8d6c9
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/LecternBlock.java.patch
@@ -0,0 +1,36 @@
+--- a/net/minecraft/world/level/block/LecternBlock.java
++++ b/net/minecraft/world/level/block/LecternBlock.java
+@@ -219,18 +206,21 @@
+ }
+ }
+
+- private void popBook(BlockState state, Level level, BlockPos pos) {
+- if (level.getBlockEntity(pos) instanceof LecternBlockEntity lecternBlockEntity) {
+- Direction direction = state.getValue(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)pos.getX() + 0.5 + (double)f, (double)(pos.getY() + 1), (double)pos.getZ() + 0.5 + (double)f1, itemStack
+- );
+- itemEntity.setDefaultPickUpDelay();
+- level.addFreshEntity(itemEntity);
+- lecternBlockEntity.clearContent();
++ 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 (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);
++
++ entityitem.setDefaultPickUpDelay();
++ level.addFreshEntity(entityitem);
++ tileentitylectern.clearContent();
+ }
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/LeverBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/LeverBlock.java.patch
new file mode 100644
index 0000000000..c922efe239
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -26,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 {
+ public static final MapCodec<LeverBlock> CODEC = simpleCodec(LeverBlock::new);
+@@ -99,10 +102,25 @@
+
+ return InteractionResult.SUCCESS;
+ } else {
+- BlockState blockState = this.pull(state, level, pos);
+- float f = blockState.getValue(POWERED) ? 0.6F : 0.5F;
+- level.playSound(null, pos, SoundEvents.LEVER_CLICK, SoundSource.BLOCKS, 0.3F, f);
+- level.gameEvent(player, blockState.getValue(POWERED) ? GameEvent.BLOCK_ACTIVATE : GameEvent.BLOCK_DEACTIVATE, pos);
++ // 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;
++
++ 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-vineflower-stripped/net/minecraft/world/level/block/LightningRodBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/LightningRodBlock.java.patch
new file mode 100644
index 0000000000..109f8d0bd8
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/LightningRodBlock.java.patch
@@ -0,0 +1,66 @@
+--- a/net/minecraft/world/level/block/LightningRodBlock.java
++++ b/net/minecraft/world/level/block/LightningRodBlock.java
+@@ -30,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 {
+ public static final MapCodec<LightningRodBlock> CODEC = simpleCodec(LightningRodBlock::new);
+@@ -82,8 +88,20 @@
+ return state.getValue(POWERED) && state.getValue(FACING) == direction ? 15 : 0;
+ }
+
+- public void onLightningStrike(BlockState state, Level level, BlockPos pos) {
+- level.setBlock(pos, state.setValue(POWERED, Boolean.valueOf(true)), 3);
++ 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, this, 8);
+ level.levelEvent(3002, pos, state.getValue(FACING).getAxis().ordinal());
+@@ -129,16 +146,19 @@
+ }
+
+ @Override
+- public void onProjectileHit(Level level, BlockState state, BlockHitResult hit, Projectile projectile) {
+- if (level.isThundering() && projectile instanceof ThrownTrident && ((ThrownTrident)projectile).isChanneling()) {
+- BlockPos blockPos = hit.getBlockPos();
+- if (level.canSeeSky(blockPos)) {
+- LightningBolt lightningBolt = EntityType.LIGHTNING_BOLT.create(level);
+- if (lightningBolt != null) {
+- lightningBolt.moveTo(Vec3.atBottomCenterOf(blockPos.above()));
+- Entity owner = projectile.getOwner();
+- lightningBolt.setCause(owner instanceof ServerPlayer ? (ServerPlayer)owner : null);
+- level.addFreshEntity(lightningBolt);
++ public void onProjectileHit(Level level, IBlockData state, BlockHitResult hit, Projectile projectile) {
++ if (level.isThundering() && projectile instanceof ThrownTrident && ((ThrownTrident) projectile).isChanneling()) {
++ BlockPos blockposition = hit.getBlockPos();
++
++ if (level.canSeeSky(blockposition)) {
++ LightningBolt entitylightning = (LightningBolt) EntityType.LIGHTNING_BOLT.create(level);
++
++ if (entitylightning != null) {
++ entitylightning.moveTo(Vec3.atBottomCenterOf(blockposition.above()));
++ Entity entity = projectile.getOwner();
++
++ entitylightning.setCause(entity instanceof ServerPlayer ? (ServerPlayer) entity : null);
++ ((ServerLevel) level).strikeLightning(entitylightning, org.bukkit.event.weather.LightningStrikeEvent.Cause.TRIDENT); // CraftBukkit
+ }
+
+ level.playSound(null, blockPos, SoundEvents.TRIDENT_THUNDER, SoundSource.WEATHER, 5.0F, 1.0F);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/LiquidBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/LiquidBlock.java.patch
new file mode 100644
index 0000000000..9deb7262c9
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/LiquidBlock.java.patch
@@ -0,0 +1,29 @@
+--- a/net/minecraft/world/level/block/LiquidBlock.java
++++ b/net/minecraft/world/level/block/LiquidBlock.java
+@@ -161,14 +173,21 @@
+ BlockPos blockPos = pos.relative(direction.getOpposite());
+ if (level.getFluidState(blockPos).is(FluidTags.WATER)) {
+ Block block = level.getFluidState(pos).isSource() ? Blocks.OBSIDIAN : Blocks.COBBLESTONE;
+- level.setBlockAndUpdate(pos, block.defaultBlockState());
+- this.fizz(level, pos);
++
++ // CraftBukkit start
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(level, pos, block.defaultBlockState())) {
++ this.fizz(level, pos);
++ }
++ // CraftBukkit end
+ return false;
+ }
+
+- if (isSoulSoil && level.getBlockState(blockPos).is(Blocks.BLUE_ICE)) {
+- level.setBlockAndUpdate(pos, Blocks.BASALT.defaultBlockState());
+- this.fizz(level, pos);
++ 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-vineflower-stripped/net/minecraft/world/level/block/MagmaBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/MagmaBlock.java.patch
new file mode 100644
index 0000000000..24fc3f4695
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/MagmaBlock.java.patch
@@ -0,0 +1,16 @@
+--- a/net/minecraft/world/level/block/MagmaBlock.java
++++ b/net/minecraft/world/level/block/MagmaBlock.java
+@@ -27,9 +28,11 @@
+ }
+
+ @Override
+- public void stepOn(Level level, BlockPos pos, BlockState state, Entity entity) {
+- if (!entity.isSteppingCarefully() && entity instanceof LivingEntity && !EnchantmentHelper.hasFrostWalker((LivingEntity)entity)) {
++ public void stepOn(Level level, BlockPos pos, IBlockData state, 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, pos, state, entity);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/MultifaceSpreader.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/MultifaceSpreader.java.patch
new file mode 100644
index 0000000000..a2aa4aa01e
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/MultifaceSpreader.java.patch
@@ -0,0 +1,45 @@
+--- a/net/minecraft/world/level/block/MultifaceSpreader.java
++++ b/net/minecraft/world/level/block/MultifaceSpreader.java
+@@ -154,7 +156,7 @@
+ level.getChunk(pos.pos()).markPosForPostprocessing(pos.pos());
+ }
+
+- return level.setBlock(pos.pos(), stateForPlacement, 2);
++ return org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos.source(), pos.pos(), iblockdata1, 2); // CraftBukkit
+ } else {
+ return false;
+ }
+@@ -172,23 +173,27 @@
+ public static enum SpreadType {
+ SAME_POSITION {
+ @Override
+- public MultifaceSpreader.SpreadPos getSpreadPos(BlockPos pos, Direction face, Direction spreadDirection) {
+- return new MultifaceSpreader.SpreadPos(pos, face);
++ public MultifaceSpreader.c getSpreadPos(BlockPos pos, Direction face, Direction spreadDirection) {
++ return new MultifaceSpreader.c(pos, face, pos); // CraftBukkit
+ }
+ },
+ SAME_PLANE {
+ @Override
+- public MultifaceSpreader.SpreadPos getSpreadPos(BlockPos pos, Direction face, Direction spreadDirection) {
+- return new MultifaceSpreader.SpreadPos(pos.relative(face), spreadDirection);
++ public MultifaceSpreader.c getSpreadPos(BlockPos pos, Direction face, Direction spreadDirection) {
++ return new MultifaceSpreader.c(pos.relative(face), spreadDirection, pos); // CraftBukkit
+ }
+ },
+ WRAP_AROUND {
+ @Override
+- public MultifaceSpreader.SpreadPos getSpreadPos(BlockPos pos, Direction face, Direction spreadDirection) {
+- return new MultifaceSpreader.SpreadPos(pos.relative(face).relative(spreadDirection), face.getOpposite());
++ public MultifaceSpreader.c getSpreadPos(BlockPos pos, Direction face, Direction spreadDirection) {
++ return new MultifaceSpreader.c(pos.relative(face).relative(spreadDirection), face.getOpposite(), pos); // CraftBukkit
+ }
+ };
+
+ public abstract MultifaceSpreader.SpreadPos getSpreadPos(BlockPos pos, Direction face, Direction spreadDirection);
+ }
++
++ public static record c(BlockPos pos, Direction face, BlockPos source) { // CraftBukkit
++
++ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/MushroomBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/MushroomBlock.java.patch
new file mode 100644
index 0000000000..3bc07bbf21
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/MushroomBlock.java.patch
@@ -0,0 +1,33 @@
+--- 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 {
+ public static final MapCodec<MushroomBlock> CODEC = RecordCodecBuilder.mapCodec(
+@@ -70,8 +77,8 @@
+ blockPos1 = pos.offset(random.nextInt(3) - 1, random.nextInt(2) - random.nextInt(2), random.nextInt(3) - 1);
+ }
+
+- if (level.isEmptyBlock(blockPos1) && state.canSurvive(level, blockPos1)) {
+- level.setBlock(blockPos1, state, 2);
++ if (level.isEmptyBlock(blockposition2) && state.canSurvive(level, blockposition2)) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(level, pos, blockposition2, state, 2); // CraftBukkit
+ }
+ }
+ }
+@@ -96,7 +104,8 @@
+ return false;
+ } else {
+ level.removeBlock(pos, false);
+- if (holder.get().value().place(level, level.getChunkSource().getGenerator(), random, pos)) {
++ 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 {
+ level.setBlock(pos, state, 3);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/NetherPortalBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/NetherPortalBlock.java.patch
new file mode 100644
index 0000000000..9cddb7990f
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/NetherPortalBlock.java.patch
@@ -0,0 +1,34 @@
+--- 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 {
+ public static final MapCodec<NetherPortalBlock> CODEC = simpleCodec(NetherPortalBlock::new);
+@@ -64,7 +66,9 @@
+ }
+
+ if (level.getBlockState(pos).isValidSpawn(level, pos, EntityType.ZOMBIFIED_PIGLIN)) {
+- Entity entity = EntityType.ZOMBIFIED_PIGLIN.spawn(level, pos.above(), MobSpawnType.STRUCTURE);
++ // 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();
+ }
+@@ -85,6 +89,10 @@
+ @Override
+ public void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
+ if (entity.canChangeDimensions()) {
++ // 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-vineflower-stripped/net/minecraft/world/level/block/NetherWartBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/NetherWartBlock.java.patch
new file mode 100644
index 0000000000..bd1f6de6ec
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/NetherWartBlock.java.patch
@@ -0,0 +1,13 @@
+--- a/net/minecraft/world/level/block/NetherWartBlock.java
++++ b/net/minecraft/world/level/block/NetherWartBlock.java
+@@ -56,8 +53,8 @@
+ public void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+ int i = state.getValue(AGE);
+ if (i < 3 && random.nextInt(10) == 0) {
+- state = state.setValue(AGE, Integer.valueOf(i + 1));
+- level.setBlock(pos, state, 2);
++ 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-vineflower-stripped/net/minecraft/world/level/block/NoteBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/NoteBlock.java.patch
new file mode 100644
index 0000000000..9694e75e40
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/NoteBlock.java.patch
@@ -0,0 +1,37 @@
+--- a/net/minecraft/world/level/block/NoteBlock.java
++++ b/net/minecraft/world/level/block/NoteBlock.java
+@@ -77,19 +76,27 @@
+ }
+
+ @Override
+- public void neighborChanged(BlockState state, Level level, BlockPos pos, Block block, BlockPos fromPos, boolean isMoving) {
+- boolean hasNeighborSignal = level.hasNeighborSignal(pos);
+- if (hasNeighborSignal != state.getValue(POWERED)) {
+- if (hasNeighborSignal) {
+- this.playNote(null, state, level, pos);
++ public void neighborChanged(IBlockData state, Level level, BlockPos pos, Block block, BlockPos fromPos, boolean isMoving) {
++ boolean flag1 = level.hasNeighborSignal(pos);
++
++ if (flag1 != (Boolean) state.getValue(NoteBlock.POWERED)) {
++ if (flag1) {
++ this.playNote((Entity) null, state, level, pos);
++ state = level.getBlockState(pos); // CraftBukkit - SPIGOT-5617: update in case changed in event
+ }
+
+ level.setBlock(pos, state.setValue(POWERED, Boolean.valueOf(hasNeighborSignal)), 3);
+ }
+ }
+
+- private void playNote(@Nullable Entity entity, BlockState state, Level level, BlockPos pos) {
+- if (state.getValue(INSTRUMENT).worksAboveNoteBlock() || level.getBlockState(pos.above()).isAir()) {
++ 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-vineflower-stripped/net/minecraft/world/level/block/NyliumBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/NyliumBlock.java.patch
new file mode 100644
index 0000000000..0c4d9bac2e
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/NyliumBlock.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/world/level/block/NyliumBlock.java
++++ b/net/minecraft/world/level/block/NyliumBlock.java
+@@ -40,6 +41,11 @@
+ @Override
+ public void randomTick(BlockState 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-vineflower-stripped/net/minecraft/world/level/block/ObserverBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ObserverBlock.java.patch
new file mode 100644
index 0000000000..8d0c96f787
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ObserverBlock.java.patch
@@ -0,0 +1,38 @@
+--- 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 {
+ public static final MapCodec<ObserverBlock> CODEC = simpleCodec(ObserverBlock::new);
+@@ -45,12 +47,22 @@
+ }
+
+ @Override
+- public void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+- if (state.getValue(POWERED)) {
+- level.setBlock(pos, state.setValue(POWERED, Boolean.valueOf(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 {
+- level.setBlock(pos, state.setValue(POWERED, Boolean.valueOf(true)), 2);
+- level.scheduleTick(pos, 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(level, pos, state);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/PointedDripstoneBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/PointedDripstoneBlock.java.patch
new file mode 100644
index 0000000000..419d583826
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/PointedDripstoneBlock.java.patch
@@ -0,0 +1,92 @@
+--- 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 {
+ public static final MapCodec<PointedDripstoneBlock> CODEC = simpleCodec(PointedDripstoneBlock::new);
+@@ -132,20 +133,25 @@
+ @Override
+ public void onProjectileHit(Level level, BlockState state, BlockHitResult hit, Projectile projectile) {
+ if (!level.isClientSide) {
+- BlockPos blockPos = hit.getBlockPos();
+- if (projectile.mayInteract(level, blockPos)
+- && projectile.mayBreak(level)
+- && projectile instanceof ThrownTrident
+- && projectile.getDeltaMovement().length() > 0.6) {
+- level.destroyBlock(blockPos, true);
++ BlockPos blockposition = hit.getBlockPos();
++
++ 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
+- public void fallOn(Level level, BlockState state, BlockPos pos, Entity entity, float fallDistance) {
+- if (state.getValue(TIP_DIRECTION) == Direction.UP && state.getValue(THICKNESS) == DripstoneThickness.TIP) {
++ 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, state, pos, entity, fallDistance);
+ }
+@@ -365,22 +391,20 @@
+ }
+
+ private static void grow(ServerLevel server, BlockPos pos, Direction direction) {
+- BlockPos blockPos = pos.relative(direction);
+- BlockState blockState = server.getBlockState(blockPos);
+- if (isUnmergedTipWithDirection(blockState, direction.getOpposite())) {
+- createMergedTips(blockState, server, blockPos);
+- } else if (blockState.isAir() || blockState.is(Blocks.WATER)) {
+- createDripstone(server, blockPos, direction, DripstoneThickness.TIP);
++ BlockPos blockposition1 = pos.relative(direction);
++ IBlockData iblockdata = server.getBlockState(blockposition1);
++
++ 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 level, BlockPos pos, Direction direction, DripstoneThickness thickness) {
+- BlockState blockState = Blocks.POINTED_DRIPSTONE
+- .defaultBlockState()
+- .setValue(TIP_DIRECTION, direction)
+- .setValue(THICKNESS, thickness)
+- .setValue(WATERLOGGED, Boolean.valueOf(level.getFluidState(pos).getType() == Fluids.WATER));
+- level.setBlock(pos, blockState, 3);
++ 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);
++
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(generatoraccess, source, blockposition, iblockdata, 3); // CraftBukkit
+ }
+
+ private static void createMergedTips(BlockState state, LevelAccessor level, BlockPos pos) {
+@@ -394,8 +420,8 @@
+ blockPos = pos.below();
+ }
+
+- createDripstone(level, blockPos1, Direction.DOWN, DripstoneThickness.TIP_MERGE);
+- createDripstone(level, blockPos, 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 pos, BlockState state) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/PowderSnowBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/PowderSnowBlock.java.patch
new file mode 100644
index 0000000000..389faf8561
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -83,9 +77,12 @@
+
+ entity.setIsInPowderSnow(true);
+ if (!level.isClientSide) {
+- if (entity.isOnFire()
+- && (level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) || entity instanceof Player)
+- && entity.mayInteract(level, pos)) {
++ // 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);
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/PoweredRailBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/PoweredRailBlock.java.patch
new file mode 100644
index 0000000000..05f9229490
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/PoweredRailBlock.java.patch
@@ -0,0 +1,26 @@
+--- 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 {
+ public static final MapCodec<PoweredRailBlock> CODEC = simpleCodec(PoweredRailBlock::new);
+@@ -133,7 +121,14 @@
+ || this.findPoweredRailSignal(level, pos, state, true, 0)
+ || this.findPoweredRailSignal(level, pos, state, false, 0);
+ if (flag1 != flag) {
+- level.setBlock(pos, state.setValue(POWERED, Boolean.valueOf(flag1)), 3);
++ // 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 (state.getValue(SHAPE).isAscending()) {
+ level.updateNeighborsAt(pos.above(), this);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/PressurePlateBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/PressurePlateBlock.java.patch
new file mode 100644
index 0000000000..776a9aaf3d
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/PressurePlateBlock.java.patch
@@ -0,0 +1,63 @@
+--- a/net/minecraft/world/level/block/PressurePlateBlock.java
++++ b/net/minecraft/world/level/block/PressurePlateBlock.java
+@@ -13,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 {
+ public static final MapCodec<PressurePlateBlock> CODEC = RecordCodecBuilder.mapCodec(
+@@ -43,11 +47,46 @@
+
+ @Override
+ protected int getSignalStrength(Level level, BlockPos pos) {
+- Class<? extends Entity> clazz = switch (this.type.pressurePlateSensitivity()) {
+- case EVERYTHING -> Entity.class;
+- case MOBS -> LivingEntity.class;
+- };
+- return getEntityCount(level, TOUCH_AABB.move(pos), clazz) > 0 ? 15 : 0;
++ Class<? extends Entity> oclass; // CraftBukkit
++
++ switch (this.type.pressurePlateSensitivity()) {
++ case EVERYTHING:
++ oclass = Entity.class;
++ break;
++ case MOBS:
++ oclass = LivingEntity.class;
++ break;
++ default:
++ throw new IncompatibleClassChangeError();
++ }
++
++ Class<? extends Entity> oclass1 = oclass;
++
++ // 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-vineflower-stripped/net/minecraft/world/level/block/RedStoneOreBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/RedStoneOreBlock.java.patch
new file mode 100644
index 0000000000..3797f5f059
--- /dev/null
+++ b/patch-remap/mache-vineflower-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 {
+ public static final MapCodec<RedStoneOreBlock> CODEC = simpleCodec(RedStoneOreBlock::new);
+@@ -37,15 +42,28 @@
+ }
+
+ @Override
+- public void attack(BlockState state, Level level, BlockPos pos, Player player) {
+- interact(state, level, pos);
++ 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
+ public void stepOn(Level level, BlockPos pos, BlockState state, Entity entity) {
+ if (!entity.isSteppingCarefully()) {
+- interact(state, level, pos);
++ // 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, pos, state, entity);
+@@ -56,7 +74,7 @@
+ if (level.isClientSide) {
+ spawnParticles(level, pos);
+ } else {
+- interact(state, level, pos);
++ interact(state, level, pos, player); // CraftBukkit - add entityhuman
+ }
+
+ ItemStack itemInHand = player.getItemInHand(hand);
+@@ -65,10 +82,15 @@
+ : InteractionResult.SUCCESS;
+ }
+
+- private static void interact(BlockState state, Level level, BlockPos pos) {
+- spawnParticles(level, pos);
+- if (!state.getValue(LIT)) {
+- level.setBlock(pos, state.setValue(LIT, Boolean.valueOf(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);
+ }
+ }
+
+@@ -78,19 +101,34 @@
+ }
+
+ @Override
+- public void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+- if (state.getValue(LIT)) {
+- level.setBlock(pos, state.setValue(LIT, Boolean.valueOf(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(BlockState state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) {
+ super.spawnAfterBreak(state, level, pos, stack, dropExperience);
+- if (dropExperience && EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, stack) == 0) {
+- int i = 1 + level.random.nextInt(5);
+- this.popExperience(level, pos, i);
++ // CraftBukkit start - Delegated to getExpDrop
++ }
++
++ @Override
++ public int getExpDrop(IBlockData iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) {
++ if (flag && EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, itemstack) == 0) {
++ int i = 1 + worldserver.random.nextInt(5);
++
++ // this.popExperience(worldserver, blockposition, i);
++ return i;
+ }
++
++ return 0;
++ // CraftBukkit end
+ }
+
+ @Override
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/RedStoneWireBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/RedStoneWireBlock.java.patch
new file mode 100644
index 0000000000..6d81ae2cf8
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/RedStoneWireBlock.java.patch
@@ -0,0 +1,29 @@
+--- a/net/minecraft/world/level/block/RedStoneWireBlock.java
++++ b/net/minecraft/world/level/block/RedStoneWireBlock.java
+@@ -33,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 {
+ public static final MapCodec<RedStoneWireBlock> CODEC = simpleCodec(RedStoneWireBlock::new);
+@@ -278,7 +261,17 @@
+
+ private void updatePowerStrength(Level level, BlockPos pos, BlockState state) {
+ int i = this.calculateTargetStrength(level, pos);
+- if (state.getValue(POWER) != i) {
++
++ // 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, state.setValue(POWER, Integer.valueOf(i)), 2);
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/RedstoneLampBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/RedstoneLampBlock.java.patch
new file mode 100644
index 0000000000..78a6f44e9c
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/RedstoneLampBlock.java.patch
@@ -0,0 +1,41 @@
+--- 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 {
+ public static final MapCodec<RedstoneLampBlock> CODEC = simpleCodec(RedstoneLampBlock::new);
+@@ -40,16 +43,26 @@
+ if (flag) {
+ level.scheduleTick(pos, this, 4);
+ } else {
+- level.setBlock(pos, state.cycle(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);
+ }
+ }
+ }
+ }
+
+ @Override
+- public void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+- if (state.getValue(LIT) && !level.hasNeighborSignal(pos)) {
+- level.setBlock(pos, state.cycle(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-vineflower-stripped/net/minecraft/world/level/block/RedstoneTorchBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/RedstoneTorchBlock.java.patch
new file mode 100644
index 0000000000..1d61d6b50a
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/RedstoneTorchBlock.java.patch
@@ -0,0 +1,57 @@
+--- a/net/minecraft/world/level/block/RedstoneTorchBlock.java
++++ b/net/minecraft/world/level/block/RedstoneTorchBlock.java
+@@ -17,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 {
+ public static final MapCodec<RedstoneTorchBlock> CODEC = simpleCodec(RedstoneTorchBlock::new);
+@@ -71,16 +86,41 @@
+ list.remove(0);
+ }
+
+- if (state.getValue(LIT)) {
+- if (hasNeighborSignal) {
+- level.setBlock(pos, state.setValue(LIT, Boolean.valueOf(false)), 3);
++ // 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) {
++ // 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 (!hasNeighborSignal && !isToggledTooFrequently(level, pos, false)) {
+- level.setBlock(pos, state.setValue(LIT, Boolean.valueOf(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-vineflower-stripped/net/minecraft/world/level/block/RespawnAnchorBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/RespawnAnchorBlock.java.patch
new file mode 100644
index 0000000000..b603a260a7
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/RespawnAnchorBlock.java.patch
@@ -0,0 +1,27 @@
+--- a/net/minecraft/world/level/block/RespawnAnchorBlock.java
++++ b/net/minecraft/world/level/block/RespawnAnchorBlock.java
+@@ -93,19 +84,11 @@
+ return InteractionResult.sidedSuccess(level.isClientSide);
+ } else {
+ if (!level.isClientSide) {
+- ServerPlayer serverPlayer = (ServerPlayer)player;
+- if (serverPlayer.getRespawnDimension() != level.dimension() || !pos.equals(serverPlayer.getRespawnPosition())) {
+- serverPlayer.setRespawnPosition(level.dimension(), pos, 0.0F, false, true);
+- level.playSound(
+- null,
+- (double)pos.getX() + 0.5,
+- (double)pos.getY() + 0.5,
+- (double)pos.getZ() + 0.5,
+- SoundEvents.RESPAWN_ANCHOR_SET_SPAWN,
+- SoundSource.BLOCKS,
+- 1.0F,
+- 1.0F
+- );
++ ServerPlayer entityplayer = (ServerPlayer) player;
++
++ 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-vineflower-stripped/net/minecraft/world/level/block/RootedDirtBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/RootedDirtBlock.java.patch
new file mode 100644
index 0000000000..6ad22b5539
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/RootedDirtBlock.java.patch
@@ -0,0 +1,12 @@
+--- a/net/minecraft/world/level/block/RootedDirtBlock.java
++++ b/net/minecraft/world/level/block/RootedDirtBlock.java
+@@ -32,7 +33,7 @@
+ }
+
+ @Override
+- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
+- level.setBlockAndUpdate(pos.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-vineflower-stripped/net/minecraft/world/level/block/SaplingBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SaplingBlock.java.patch
new file mode 100644
index 0000000000..3e4dc6d7c8
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SaplingBlock.java.patch
@@ -0,0 +1,62 @@
+--- a/net/minecraft/world/level/block/SaplingBlock.java
++++ b/net/minecraft/world/level/block/SaplingBlock.java
+@@ -17,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 {
+ public static final MapCodec<SaplingBlock> CODEC = RecordCodecBuilder.mapCodec(
+@@ -25,8 +33,9 @@
+ );
+ public static final IntegerProperty STAGE = BlockStateProperties.STAGE;
+ protected static final float AABB_OFFSET = 6.0F;
+- protected static final VoxelShape SHAPE = Block.box(2.0, 0.0, 2.0, 14.0, 12.0, 14.0);
+- protected final TreeGrower treeGrower;
++ protected static final VoxelShape SHAPE = Block.box(2.0D, 0.0D, 2.0D, 14.0D, 12.0D, 14.0D);
++ protected final WorldGenTreeProvider treeGrower;
++ public static TreeType treeType; // CraftBukkit
+
+ @Override
+ public MapCodec<? extends SaplingBlock> codec() {
+@@ -55,7 +65,32 @@
+ if (state.getValue(STAGE) == 0) {
+ level.setBlock(pos, state.cycle(STAGE), 4);
+ } else {
+- this.treeGrower.growTree(level, level.getChunkSource().getGenerator(), pos, state, random);
++ // 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-vineflower-stripped/net/minecraft/world/level/block/ScaffoldingBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ScaffoldingBlock.java.patch
new file mode 100644
index 0000000000..980267e902
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/ScaffoldingBlock.java.patch
@@ -0,0 +1,22 @@
+--- a/net/minecraft/world/level/block/ScaffoldingBlock.java
++++ b/net/minecraft/world/level/block/ScaffoldingBlock.java
+@@ -107,12 +99,13 @@
+ }
+
+ @Override
+- public void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+- int distance = getDistance(level, pos);
+- BlockState blockState = state.setValue(DISTANCE, Integer.valueOf(distance)).setValue(BOTTOM, Boolean.valueOf(this.isBottom(level, pos, distance)));
+- if (blockState.getValue(DISTANCE) == 7) {
+- if (state.getValue(DISTANCE) == 7) {
+- FallingBlockEntity.fall(level, pos, blockState);
++ public void tick(IBlockData state, ServerLevel level, BlockPos pos, RandomSource random) {
++ int i = getDistance(level, pos);
++ IBlockData iblockdata1 = (IBlockData) ((IBlockData) state.setValue(ScaffoldingBlock.DISTANCE, i)).setValue(ScaffoldingBlock.BOTTOM, this.isBottom(level, pos, i));
++
++ 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 {
+ level.destroyBlock(pos, true);
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SculkBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SculkBlock.java.patch
new file mode 100644
index 0000000000..630c4ffcd1
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SculkBlock.java.patch
@@ -0,0 +1,42 @@
+--- a/net/minecraft/world/level/block/SculkBlock.java
++++ b/net/minecraft/world/level/block/SculkBlock.java
+@@ -25,20 +29,25 @@
+ }
+
+ @Override
+- public int attemptUseCharge(
+- SculkSpreader.ChargeCursor cursor, LevelAccessor level, BlockPos pos, RandomSource random, SculkSpreader spreader, boolean shouldConvertBlocks
+- ) {
+- int charge = cursor.getCharge();
+- if (charge != 0 && random.nextInt(spreader.chargeDecayRate()) == 0) {
+- BlockPos pos1 = cursor.getPos();
+- boolean flag = pos1.closerThan(pos, (double)spreader.noGrowthRadius());
+- if (!flag && canPlaceGrowth(level, pos1)) {
+- int i = spreader.growthSpawnCost();
+- if (random.nextInt(i) < charge) {
+- BlockPos blockPos = pos1.above();
+- BlockState randomGrowthState = this.getRandomGrowthState(level, blockPos, random, spreader.isWorldGeneration());
+- level.setBlock(blockPos, randomGrowthState, 3);
+- level.playSound(null, pos1, randomGrowthState.getSoundType().getPlaceSound(), SoundSource.BLOCKS, 1.0F, 1.0F);
++ public int attemptUseCharge(SculkSpreader.ChargeCursor cursor, LevelAccessor level, BlockPos pos, RandomSource random, SculkSpreader spreader, boolean shouldConvertBlocks) {
++ int i = cursor.getCharge();
++
++ if (i != 0 && random.nextInt(spreader.chargeDecayRate()) == 0) {
++ BlockPos blockposition1 = cursor.getPos();
++ boolean flag1 = blockposition1.closerThan(pos, (double) spreader.noGrowthRadius());
++
++ if (!flag1 && canPlaceGrowth(level, blockposition1)) {
++ int j = spreader.growthSpawnCost();
++
++ if (random.nextInt(j) < i) {
++ BlockPos blockposition2 = blockposition1.above();
++ IBlockData iblockdata = this.getRandomGrowthState(level, blockposition2, random, spreader.isWorldGeneration());
++
++ // 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, charge - i);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SculkCatalystBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SculkCatalystBlock.java.patch
new file mode 100644
index 0000000000..01d0c3dc99
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SculkCatalystBlock.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/level/block/SculkCatalystBlock.java
++++ b/net/minecraft/world/level/block/SculkCatalystBlock.java
+@@ -66,8 +68,16 @@
+ @Override
+ public void spawnAfterBreak(BlockState state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) {
+ super.spawnAfterBreak(state, level, pos, stack, dropExperience);
+- if (dropExperience) {
+- this.tryDropExperience(level, pos, stack, this.xpRange);
++ // CraftBukkit start - Delegate to getExpDrop
++ }
++
++ @Override
++ public int getExpDrop(IBlockData iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) {
++ if (flag) {
++ return this.tryDropExperience(worldserver, blockposition, itemstack, this.xpRange);
+ }
++
++ return 0;
++ // CraftBukkit end
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SculkSensorBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SculkSensorBlock.java.patch
new file mode 100644
index 0000000000..4a0459ff91
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SculkSensorBlock.java.patch
@@ -0,0 +1,114 @@
+--- a/net/minecraft/world/level/block/SculkSensorBlock.java
++++ b/net/minecraft/world/level/block/SculkSensorBlock.java
+@@ -39,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 {
+ public static final MapCodec<SculkSensorBlock> CODEC = simpleCodec(SculkSensorBlock::new);
+@@ -100,14 +104,33 @@
+ }
+
+ @Override
+- public void stepOn(Level level, BlockPos pos, BlockState state, Entity entity) {
+- if (!level.isClientSide()
+- && canActivate(state)
+- && entity.getType() != EntityType.WARDEN
+- && level.getBlockEntity(pos) instanceof SculkSensorBlockEntity sculkSensorBlockEntity
+- && level instanceof ServerLevel serverLevel
+- && sculkSensorBlockEntity.getVibrationUser().canReceiveVibration(serverLevel, pos, GameEvent.STEP, GameEvent.Context.of(state))) {
+- sculkSensorBlockEntity.getListener().forceScheduleVibration(serverLevel, GameEvent.STEP, GameEvent.Context.of(entity), entity.position());
++ 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 (tileentity instanceof SculkSensorBlockEntity) {
++ SculkSensorBlockEntity sculksensorblockentity = (SculkSensorBlockEntity) tileentity;
++
++ if (level instanceof ServerLevel) {
++ ServerLevel worldserver = (ServerLevel) level;
++
++ if (sculksensorblockentity.getVibrationUser().canReceiveVibration(worldserver, pos, GameEvent.STEP, GameEvent.Context.of(state))) {
++ sculksensorblockentity.getListener().forceScheduleVibration(worldserver, GameEvent.STEP, GameEvent.Context.of(entity), entity.position());
++ }
++ }
++ }
+ }
+
+ super.stepOn(level, pos, state, entity);
+@@ -199,8 +220,17 @@
+ return getPhase(state) == SculkSensorPhase.INACTIVE;
+ }
+
+- public static void deactivate(Level level, BlockPos pos, BlockState state) {
+- level.setBlock(pos, state.setValue(PHASE, SculkSensorPhase.COOLDOWN).setValue(POWER, Integer.valueOf(0)), 3);
++ 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);
+ }
+@@ -210,8 +240,17 @@
+ return 30;
+ }
+
+- public void activate(@Nullable Entity entity, Level level, BlockPos pos, BlockState state, int power, int frequency) {
+- level.setBlock(pos, state.setValue(PHASE, SculkSensorPhase.ACTIVE).setValue(POWER, Integer.valueOf(power)), 3);
++ 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);
+@@ -288,8 +332,16 @@
+ @Override
+ public void spawnAfterBreak(BlockState state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) {
+ super.spawnAfterBreak(state, level, pos, stack, dropExperience);
+- if (dropExperience) {
+- this.tryDropExperience(level, pos, stack, ConstantInt.of(5));
++ // CraftBukkit start - Delegate to getExpDrop
++ }
++
++ @Override
++ public int getExpDrop(IBlockData iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) {
++ if (flag) {
++ return this.tryDropExperience(worldserver, blockposition, itemstack, ConstantInt.of(5));
+ }
++
++ return 0;
++ // CraftBukkit end
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SculkShriekerBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SculkShriekerBlock.java.patch
new file mode 100644
index 0000000000..c4ad66e5af
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SculkShriekerBlock.java.patch
@@ -0,0 +1,44 @@
+--- a/net/minecraft/world/level/block/SculkShriekerBlock.java
++++ b/net/minecraft/world/level/block/SculkShriekerBlock.java
+@@ -61,11 +57,16 @@
+ }
+
+ @Override
+- public void stepOn(Level level, BlockPos pos, BlockState state, Entity entity) {
+- if (level instanceof ServerLevel serverLevel) {
+- ServerPlayer serverPlayer = SculkShriekerBlockEntity.tryGetPlayer(entity);
+- if (serverPlayer != null) {
+- serverLevel.getBlockEntity(pos, BlockEntityType.SCULK_SHRIEKER).ifPresent(sculkShrieker -> sculkShrieker.tryShriek(serverLevel, serverPlayer));
++ public void stepOn(Level level, BlockPos pos, IBlockData state, Entity entity) {
++ if (level instanceof ServerLevel) {
++ ServerLevel worldserver = (ServerLevel) level;
++ ServerPlayer entityplayer = SculkShriekerBlockEntity.tryGetPlayer(entity);
++
++ 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);
++ });
+ }
+ }
+
+@@ -139,9 +148,17 @@
+ @Override
+ public void spawnAfterBreak(BlockState state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) {
+ super.spawnAfterBreak(state, level, pos, stack, dropExperience);
+- if (dropExperience) {
+- this.tryDropExperience(level, pos, stack, ConstantInt.of(5));
++ // CraftBukkit start - Delegate to getExpDrop
++ }
++
++ @Override
++ public int getExpDrop(IBlockData iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) {
++ if (flag) {
++ return this.tryDropExperience(worldserver, blockposition, itemstack, ConstantInt.of(5));
+ }
++
++ return 0;
++ // CraftBukkit end
+ }
+
+ @Nullable
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SculkSpreader.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SculkSpreader.java.patch
new file mode 100644
index 0000000000..3db9c3c729
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SculkSpreader.java.patch
@@ -0,0 +1,41 @@
+--- a/net/minecraft/world/level/block/SculkSpreader.java
++++ b/net/minecraft/world/level/block/SculkSpreader.java
+@@ -36,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 {
+ public static final int MAX_GROWTH_RATE_RADIUS = 24;
+@@ -51,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 isWorldGeneration, TagKey<Block> replaceableBlocks, int growthSpawnCoat, int noGrowthRadius, int chargeDecayRate, int additionalDecayRate
+@@ -138,6 +153,19 @@
+
+ private void addCursor(SculkSpreader.ChargeCursor cursor) {
+ if (this.cursors.size() < 32) {
++ // 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-vineflower-stripped/net/minecraft/world/level/block/SculkVeinBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SculkVeinBlock.java.patch
new file mode 100644
index 0000000000..c32812a39f
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SculkVeinBlock.java.patch
@@ -0,0 +1,70 @@
+--- a/net/minecraft/world/level/block/SculkVeinBlock.java
++++ b/net/minecraft/world/level/block/SculkVeinBlock.java
+@@ -94,19 +107,15 @@
+ }
+
+ @Override
+- public int attemptUseCharge(
+- SculkSpreader.ChargeCursor cursor, LevelAccessor level, BlockPos pos, RandomSource random, SculkSpreader spreader, boolean shouldConvertBlocks
+- ) {
+- if (shouldConvertBlocks && this.attemptPlaceSculk(spreader, level, cursor.getPos(), random)) {
+- return cursor.getCharge() - 1;
+- } else {
+- return random.nextInt(spreader.chargeDecayRate()) == 0 ? Mth.floor((float)cursor.getCharge() * 0.5F) : cursor.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 spreader, LevelAccessor level, BlockPos pos, RandomSource random) {
+- BlockState blockState = level.getBlockState(pos);
+- TagKey<Block> tagKey = spreader.replaceableBlocks();
++ 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();
+
+ for (Direction direction : Direction.allShuffled(random)) {
+ if (hasFace(blockState, direction)) {
+@@ -120,12 +120,34 @@
+ this.veinSpreader.spreadAll(blockState2, level, blockPos, spreader.isWorldGeneration());
+ Direction opposite = direction.getOpposite();
+
+- for (Direction direction1 : DIRECTIONS) {
+- if (direction1 != opposite) {
+- BlockPos blockPos1 = blockPos.relative(direction1);
+- BlockState blockState3 = level.getBlockState(blockPos1);
+- if (blockState3.is(this)) {
+- this.onDischarged(level, blockState3, blockPos1, random);
++ if (hasFace(iblockdata, enumdirection)) {
++ BlockPos blockposition1 = blockposition.relative(enumdirection);
++ IBlockData iblockdata1 = generatoraccess.getBlockState(blockposition1);
++
++ if (iblockdata1.is(tagkey)) {
++ IBlockData iblockdata2 = Blocks.SCULK.defaultBlockState();
++
++ // 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 enumdirection2 = aenumdirection[j];
++
++ if (enumdirection2 != enumdirection1) {
++ BlockPos blockposition2 = blockposition1.relative(enumdirection2);
++ IBlockData iblockdata3 = generatoraccess.getBlockState(blockposition2);
++
++ if (iblockdata3.is((Block) this)) {
++ this.onDischarged(generatoraccess, iblockdata3, blockposition2, randomsource);
+ }
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SignBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SignBlock.java.patch
new file mode 100644
index 0000000000..88351e4710
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SignBlock.java.patch
@@ -0,0 +1,15 @@
+--- a/net/minecraft/world/level/block/SignBlock.java
++++ b/net/minecraft/world/level/block/SignBlock.java
+@@ -103,10 +117,8 @@
+ return InteractionResult.SUCCESS;
+ } else if (flag1) {
+ return InteractionResult.SUCCESS;
+- } else if (!this.otherPlayerIsEditingSign(player, signBlockEntity)
+- && player.mayBuild()
+- && this.hasEditableText(player, signBlockEntity, isFacingFrontText)) {
+- this.openTextEdit(player, signBlockEntity, isFacingFrontText);
++ } 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-vineflower-stripped/net/minecraft/world/level/block/SnowLayerBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SnowLayerBlock.java.patch
new file mode 100644
index 0000000000..0fb71b7e6c
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SnowLayerBlock.java.patch
@@ -0,0 +1,18 @@
+--- a/net/minecraft/world/level/block/SnowLayerBlock.java
++++ b/net/minecraft/world/level/block/SnowLayerBlock.java
+@@ -112,8 +97,13 @@
+ }
+
+ @Override
+- public void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+- if (level.getBrightness(LightLayer.BLOCK, pos) > 11) {
++ 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-vineflower-stripped/net/minecraft/world/level/block/SpawnerBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SpawnerBlock.java.patch
new file mode 100644
index 0000000000..d63a95061e
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SpawnerBlock.java.patch
@@ -0,0 +1,26 @@
+--- a/net/minecraft/world/level/block/SpawnerBlock.java
++++ b/net/minecraft/world/level/block/SpawnerBlock.java
+@@ -46,10 +45,20 @@
+ @Override
+ public void spawnAfterBreak(BlockState state, ServerLevel level, BlockPos pos, ItemStack stack, boolean dropExperience) {
+ super.spawnAfterBreak(state, level, pos, stack, dropExperience);
+- if (dropExperience) {
+- int i = 15 + level.random.nextInt(15) + level.random.nextInt(15);
+- this.popExperience(level, pos, i);
++ // CraftBukkit start - Delegate to getExpDrop
++ }
++
++ @Override
++ public int getExpDrop(IBlockData iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) {
++ if (flag) {
++ int i = 15 + worldserver.random.nextInt(15) + worldserver.random.nextInt(15);
++
++ // this.popExperience(worldserver, blockposition, i);
++ return i;
+ }
++
++ return 0;
++ // CraftBukkit end
+ }
+
+ @Override
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SpongeBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SpongeBlock.java.patch
new file mode 100644
index 0000000000..c32faa3473
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SpongeBlock.java.patch
@@ -0,0 +1,122 @@
+--- 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 {
+ public static final MapCodec<SpongeBlock> CODEC = simpleCodec(SpongeBlock::new);
+@@ -49,18 +57,41 @@
+ }
+
+ private boolean removeWaterBreadthFirstSearch(Level level, BlockPos pos) {
+- return BlockPos.breadthFirstTraversal(
+- pos,
+- 6,
+- 65,
+- (validPos, queueAdder) -> {
+- for (Direction direction : ALL_DIRECTIONS) {
+- queueAdder.accept(validPos.relative(direction));
++ 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 enumdirection = aenumdirection[j];
++
++ consumer.accept(blockposition1.relative(enumdirection));
++ }
++
++ }, (blockposition1) -> {
++ if (blockposition1.equals(pos)) {
++ return true;
++ } else {
++ // CraftBukkit start
++ IBlockData iblockdata = blockList.getBlockState(blockposition1);
++ FluidState fluid = blockList.getFluidState(blockposition1);
++ // CraftBukkit end
++
++ if (!fluid.is(FluidTags.WATER)) {
++ return false;
++ } else {
++ Block block = iblockdata.getBlock();
++
++ if (block instanceof BucketPickup) {
++ BucketPickup ifluidsource = (BucketPickup) block;
++
++ if (!ifluidsource.pickupBlock((Player) null, blockList, blockposition1, iblockdata).isEmpty()) { // CraftBukkit
++ return true;
++ }
+ }
+- },
+- currentPos -> {
+- if (currentPos.equals(pos)) {
+- return true;
++
++ if (iblockdata.getBlock() instanceof LiquidBlock) {
++ blockList.setBlock(blockposition1, Blocks.AIR.defaultBlockState(), 3); // CraftBukkit
+ } else {
+ BlockState blockState = level.getBlockState(currentPos);
+ FluidState fluidState = level.getFluidState(currentPos);
+@@ -89,9 +96,51 @@
+
+ return true;
+ }
++
++ // CraftBukkit start
++ // TileEntity tileentity = iblockdata.hasBlockEntity() ? world.getBlockEntity(blockposition1) : null;
++
++ // dropResources(iblockdata, world, blockposition1, tileentity);
++ blockList.setBlock(blockposition1, Blocks.AIR.defaultBlockState(), 3);
++ // CraftBukkit end
+ }
+ }
+- )
+- > 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-vineflower-stripped/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java.patch
new file mode 100644
index 0000000000..bab2dbe00e
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java.patch
@@ -0,0 +1,28 @@
+--- a/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java
++++ b/net/minecraft/world/level/block/SpreadingSnowyDirtBlock.java
+@@ -42,15 +44,21 @@
+ @Override
+ public void randomTick(BlockState 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 (level.getMaxLocalRawBrightness(pos.above()) >= 9) {
+ BlockState blockState = this.defaultBlockState();
+
+- for (int i = 0; i < 4; i++) {
+- BlockPos blockPos = pos.offset(random.nextInt(3) - 1, random.nextInt(5) - 3, random.nextInt(3) - 1);
+- if (level.getBlockState(blockPos).is(Blocks.DIRT) && canPropagate(blockState, level, blockPos)) {
+- level.setBlockAndUpdate(blockPos, blockState.setValue(SNOWY, Boolean.valueOf(level.getBlockState(blockPos.above()).is(Blocks.SNOW))));
++ for (int i = 0; i < 4; ++i) {
++ BlockPos blockposition1 = pos.offset(random.nextInt(3) - 1, random.nextInt(5) - 3, random.nextInt(3) - 1);
++
++ 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-vineflower-stripped/net/minecraft/world/level/block/StemBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/StemBlock.java.patch
new file mode 100644
index 0000000000..3664a93515
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/StemBlock.java.patch
@@ -0,0 +1,56 @@
+--- 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 {
+ public static final MapCodec<StemBlock> CODEC = RecordCodecBuilder.mapCodec(
+@@ -84,8 +79,8 @@
+ if (random.nextInt((int)(25.0F / growthSpeed) + 1) == 0) {
+ int i = state.getValue(AGE);
+ if (i < 7) {
+- state = state.setValue(AGE, Integer.valueOf(i + 1));
+- level.setBlock(pos, state, 2);
++ state = (IBlockData) state.setValue(StemBlock.AGE, i + 1);
++ CraftEventFactory.handleBlockGrowEvent(level, pos, state, 2); // CraftBukkit
+ } else {
+ Direction randomDirection = Direction.Plane.HORIZONTAL.getRandomDirection(random);
+ BlockPos blockPos = pos.relative(randomDirection);
+@@ -95,8 +92,12 @@
+ Optional<Block> optional = registry.getOptional(this.fruit);
+ Optional<Block> optional1 = registry.getOptional(this.attachedStem);
+ if (optional.isPresent() && optional1.isPresent()) {
+- level.setBlockAndUpdate(blockPos, optional.get().defaultBlockState());
+- level.setBlockAndUpdate(pos, optional1.get().defaultBlockState().setValue(HorizontalDirectionalBlock.FACING, randomDirection));
++ // 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));
+ }
+ }
+ }
+@@ -120,12 +122,13 @@
+ }
+
+ @Override
+- public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, BlockState state) {
+- int min = Math.min(7, state.getValue(AGE) + Mth.nextInt(level.random, 2, 5));
+- BlockState blockState = state.setValue(AGE, Integer.valueOf(min));
+- level.setBlock(pos, blockState, 2);
+- if (min == 7) {
+- blockState.randomTick(level, pos, level.random);
++ public void performBonemeal(ServerLevel level, RandomSource random, BlockPos pos, IBlockData state) {
++ int i = Math.min(7, (Integer) state.getValue(StemBlock.AGE) + Mth.nextInt(level.random, 2, 5));
++ IBlockData iblockdata1 = (IBlockData) state.setValue(StemBlock.AGE, i);
++
++ CraftEventFactory.handleBlockGrowEvent(level, pos, iblockdata1, 2); // CraftBukkit
++ if (i == 7) {
++ iblockdata1.randomTick(level, pos, level.random);
+ }
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SugarCaneBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SugarCaneBlock.java.patch
new file mode 100644
index 0000000000..519b55072d
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SugarCaneBlock.java.patch
@@ -0,0 +1,18 @@
+--- a/net/minecraft/world/level/block/SugarCaneBlock.java
++++ b/net/minecraft/world/level/block/SugarCaneBlock.java
+@@ -57,10 +60,11 @@
+ }
+
+ if (i < 3) {
+- int i1 = state.getValue(AGE);
+- if (i1 == 15) {
+- level.setBlockAndUpdate(pos.above(), this.defaultBlockState());
+- level.setBlock(pos, state.setValue(AGE, Integer.valueOf(0)), 4);
++ int j = (Integer) state.getValue(SugarCaneBlock.AGE);
++
++ if (j == 15) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(level, pos.above(), this.defaultBlockState()); // CraftBukkit
++ level.setBlock(pos, (IBlockData) state.setValue(SugarCaneBlock.AGE, 0), 4);
+ } else {
+ level.setBlock(pos, state.setValue(AGE, Integer.valueOf(i1 + 1)), 4);
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SweetBerryBushBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SweetBerryBushBlock.java.patch
new file mode 100644
index 0000000000..96f4c6b00e
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/SweetBerryBushBlock.java.patch
@@ -0,0 +1,79 @@
+--- 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 {
+ public static final MapCodec<SweetBerryBushBlock> CODEC = simpleCodec(SweetBerryBushBlock::new);
+@@ -69,21 +74,25 @@
+ public void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
+ int i = state.getValue(AGE);
+ if (i < 3 && random.nextInt(5) == 0 && level.getRawBrightness(pos.above(), 0) >= 9) {
+- BlockState blockState = state.setValue(AGE, Integer.valueOf(i + 1));
+- level.setBlock(pos, blockState, 2);
+- level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(blockState));
++ IBlockData iblockdata1 = (IBlockData) state.setValue(SweetBerryBushBlock.AGE, i + 1);
++
++ if (!CraftEventFactory.handleBlockGrowEvent(level, pos, iblockdata1, 2)) return; // CraftBukkit
++ level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(iblockdata1));
+ }
+ }
+
+ @Override
+ public void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
+ if (entity instanceof LivingEntity && entity.getType() != EntityType.FOX && entity.getType() != EntityType.BEE) {
+- entity.makeStuckInBlock(state, new Vec3(0.8F, 0.75, 0.8F));
+- if (!level.isClientSide && state.getValue(AGE) > 0 && (entity.xOld != entity.getX() || entity.zOld != entity.getZ())) {
+- double abs = Math.abs(entity.getX() - entity.xOld);
+- double abs1 = Math.abs(entity.getZ() - entity.zOld);
+- if (abs >= 0.003F || abs1 >= 0.003F) {
++ entity.makeStuckInBlock(state, new Vec3(0.800000011920929D, 0.75D, 0.800000011920929D));
++ if (!level.isClientSide && (Integer) state.getValue(SweetBerryBushBlock.AGE) > 0 && (entity.xOld != entity.getX() || entity.zOld != entity.getZ())) {
++ double d0 = Math.abs(entity.getX() - entity.xOld);
++ 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
+ }
+ }
+ }
+@@ -96,12 +108,22 @@
+ if (!flag && player.getItemInHand(hand).is(Items.BONE_MEAL)) {
+ return InteractionResult.PASS;
+ } else if (i > 1) {
+- int i1 = 1 + level.random.nextInt(2);
+- popResource(level, pos, new ItemStack(Items.SWEET_BERRIES, i1 + (flag ? 1 : 0)));
+- level.playSound(null, pos, SoundEvents.SWEET_BERRY_BUSH_PICK_BERRIES, SoundSource.BLOCKS, 1.0F, 0.8F + level.random.nextFloat() * 0.4F);
+- BlockState blockState = state.setValue(AGE, Integer.valueOf(1));
+- level.setBlock(pos, blockState, 2);
+- level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(player, blockState));
++ int j = 1 + level.random.nextInt(2);
++
++ // 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(pos, iblockdata1, 2);
++ level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(player, iblockdata1));
+ return InteractionResult.sidedSuccess(level.isClientSide);
+ } else {
+ return super.use(state, level, pos, player, hand, hit);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/TntBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/TntBlock.java.patch
new file mode 100644
index 0000000000..c2a97e5ea5
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/TntBlock.java.patch
@@ -0,0 +1,80 @@
+--- 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 {
+ public static final MapCodec<TntBlock> CODEC = simpleCodec(TntBlock::new);
+@@ -43,7 +48,7 @@
+ @Override
+ public void onPlace(BlockState state, Level level, BlockPos pos, BlockState oldState, boolean isMoving) {
+ if (!oldState.is(state.getBlock())) {
+- if (level.hasNeighborSignal(pos)) {
++ if (level.hasNeighborSignal(pos) && CraftEventFactory.callTNTPrimeEvent(level, pos, PrimeCause.REDSTONE, null, null)) { // CraftBukkit - TNTPrimeEvent
+ explode(level, pos);
+ level.removeBlock(pos, false);
+ }
+@@ -51,17 +57,17 @@
+ }
+
+ @Override
+- public void neighborChanged(BlockState state, Level level, BlockPos pos, Block block, BlockPos fromPos, boolean isMoving) {
+- if (level.hasNeighborSignal(pos)) {
++ 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
+- public BlockState playerWillDestroy(Level level, BlockPos blockPos, BlockState blockState, Player player) {
+- if (!level.isClientSide() && !player.isCreative() && blockState.getValue(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);
+@@ -98,6 +106,11 @@
+ if (!itemInHand.is(Items.FLINT_AND_STEEL) && !itemInHand.is(Items.FIRE_CHARGE)) {
+ return super.use(state, level, pos, player, hand, hit);
+ } else {
++ // 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 = itemInHand.getItem();
+@@ -117,11 +133,17 @@
+ @Override
+ public void onProjectileHit(Level level, BlockState state, BlockHitResult hit, Projectile projectile) {
+ if (!level.isClientSide) {
+- BlockPos blockPos = hit.getBlockPos();
+- Entity owner = projectile.getOwner();
+- if (projectile.isOnFire() && projectile.mayInteract(level, blockPos)) {
+- explode(level, blockPos, owner instanceof LivingEntity ? (LivingEntity)owner : null);
+- level.removeBlock(blockPos, false);
++ BlockPos blockposition = hit.getBlockPos();
++ Entity entity = projectile.getOwner();
++
++ 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-vineflower-stripped/net/minecraft/world/level/block/TrapDoorBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/TrapDoorBlock.java.patch
new file mode 100644
index 0000000000..d1c1cd8d07
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/TrapDoorBlock.java.patch
@@ -0,0 +1,42 @@
+--- a/net/minecraft/world/level/block/TrapDoorBlock.java
++++ b/net/minecraft/world/level/block/TrapDoorBlock.java
+@@ -32,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 {
+ public static final MapCodec<TrapDoorBlock> CODEC = RecordCodecBuilder.mapCodec(
+@@ -145,12 +137,26 @@
+ @Override
+ public void neighborChanged(BlockState state, Level level, BlockPos pos, Block block, BlockPos fromPos, boolean isMoving) {
+ if (!level.isClientSide) {
+- boolean hasNeighborSignal = level.hasNeighborSignal(pos);
+- if (hasNeighborSignal != state.getValue(POWERED)) {
+- if (state.getValue(OPEN) != hasNeighborSignal) {
+- state = state.setValue(OPEN, Boolean.valueOf(hasNeighborSignal));
+- this.playSound(null, level, pos, hasNeighborSignal);
++ boolean flag1 = level.hasNeighborSignal(pos);
++
++ 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(pos, state.setValue(POWERED, Boolean.valueOf(hasNeighborSignal)), 2);
+ if (state.getValue(WATERLOGGED)) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/TripWireBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/TripWireBlock.java.patch
new file mode 100644
index 0000000000..953ce1d85f
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -25,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 {
+ public static final MapCodec<TripWireBlock> CODEC = RecordCodecBuilder.mapCodec(
+@@ -161,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.setValue(POWERED, Boolean.valueOf(flag1));
+ level.setBlock(pos, blockState, 3);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/TripWireHookBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/TripWireHookBlock.java.patch
new file mode 100644
index 0000000000..f73c8995e6
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/TripWireHookBlock.java.patch
@@ -0,0 +1,31 @@
+--- a/net/minecraft/world/level/block/TripWireHookBlock.java
++++ b/net/minecraft/world/level/block/TripWireHookBlock.java
+@@ -26,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 {
+ public static final MapCodec<TripWireHookBlock> CODEC = simpleCodec(TripWireHookBlock::new);
+@@ -163,7 +177,16 @@
+ emitState(level, blockPosx, var21, var22, flag, flag1);
+ }
+
+- emitState(level, level1, var21, var22, flag, flag1);
++ // 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) {
+ level.setBlock(level1, blockState1.setValue(FACING, direction), 3);
+ if (attaching) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/TurtleEggBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/TurtleEggBlock.java.patch
new file mode 100644
index 0000000000..7336dd2cb8
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/TurtleEggBlock.java.patch
@@ -0,0 +1,77 @@
+--- 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 {
+ public static final MapCodec<TurtleEggBlock> CODEC = simpleCodec(TurtleEggBlock::new);
+@@ -71,6 +77,19 @@
+ private void destroyEgg(Level level, BlockState state, BlockPos pos, Entity entity, int chance) {
+ if (this.canDestroyEgg(level, entity)) {
+ 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);
+ }
+ }
+@@ -93,22 +116,33 @@
+ if (this.shouldUpdateHatchLevel(level) && onSand(level, pos)) {
+ int i = state.getValue(HATCH);
+ if (i < 2) {
+- level.playSound(null, pos, SoundEvents.TURTLE_EGG_CRACK, SoundSource.BLOCKS, 0.7F, 0.9F + random.nextFloat() * 0.2F);
+- level.setBlock(pos, state.setValue(HATCH, Integer.valueOf(i + 1)), 2);
++ // 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 {
+- level.playSound(null, pos, SoundEvents.TURTLE_EGG_HATCH, SoundSource.BLOCKS, 0.7F, 0.9F + random.nextFloat() * 0.2F);
++ // 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 i1 = 0; i1 < state.getValue(EGGS); i1++) {
+ level.levelEvent(2001, pos, Block.getId(state));
+- Turtle turtle = EntityType.TURTLE.create(level);
+- if (turtle != null) {
+- turtle.setAge(-24000);
+- turtle.setHomePos(pos);
+- turtle.moveTo((double)pos.getX() + 0.3 + (double)i1 * 0.2, (double)pos.getY(), (double)pos.getZ() + 0.3, 0.0F, 0.0F);
+- level.addFreshEntity(turtle);
++ Turtle entityturtle = (Turtle) EntityType.TURTLE.create(level);
++
++ 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-vineflower-stripped/net/minecraft/world/level/block/VineBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/VineBlock.java.patch
new file mode 100644
index 0000000000..7769430bf5
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/VineBlock.java.patch
@@ -0,0 +1,102 @@
+--- 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 {
+ public static final MapCodec<VineBlock> CODEC = simpleCodec(VineBlock::new);
+@@ -188,19 +194,23 @@
+ BlockPos blockPos = pos.above();
+ if (random1.getAxis().isHorizontal() && !state.getValue(getPropertyForFace(random1))) {
+ if (this.canSpread(level, pos)) {
+- BlockPos blockPos1 = pos.relative(random1);
+- BlockState blockState = level.getBlockState(blockPos1);
+- if (blockState.isAir()) {
+- Direction clockWise = random1.getClockWise();
+- Direction counterClockWise = random1.getCounterClockWise();
+- boolean flag = state.getValue(getPropertyForFace(clockWise));
+- boolean flag1 = state.getValue(getPropertyForFace(counterClockWise));
+- BlockPos blockPos2 = blockPos1.relative(clockWise);
+- BlockPos blockPos3 = blockPos1.relative(counterClockWise);
+- if (flag && isAcceptableNeighbour(level, blockPos2, clockWise)) {
+- level.setBlock(blockPos1, this.defaultBlockState().setValue(getPropertyForFace(clockWise), Boolean.valueOf(true)), 2);
+- } else if (flag1 && isAcceptableNeighbour(level, blockPos3, counterClockWise)) {
+- level.setBlock(blockPos1, this.defaultBlockState().setValue(getPropertyForFace(counterClockWise), Boolean.valueOf(true)), 2);
++ blockposition2 = pos.relative(enumdirection);
++ iblockdata1 = level.getBlockState(blockposition2);
++ if (iblockdata1.isAir()) {
++ enumdirection1 = enumdirection.getClockWise();
++ Direction enumdirection2 = enumdirection.getCounterClockWise();
++ boolean flag = (Boolean) state.getValue(getPropertyForFace(enumdirection1));
++ boolean flag1 = (Boolean) state.getValue(getPropertyForFace(enumdirection2));
++ BlockPos blockposition3 = blockposition2.relative(enumdirection1);
++ BlockPos blockposition4 = blockposition2.relative(enumdirection2);
++
++ // 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 opposite = random1.getOpposite();
+ if (flag && level.isEmptyBlock(blockPos2) && isAcceptableNeighbour(level, pos.relative(clockWise), opposite)) {
+@@ -210,15 +221,16 @@
+ } else if ((double)random.nextFloat() < 0.05 && isAcceptableNeighbour(level, blockPos1.above(), Direction.UP)) {
+ level.setBlock(blockPos1, this.defaultBlockState().setValue(UP, Boolean.valueOf(true)), 2);
+ }
++ // CraftBukkit end
+ }
+- } else if (isAcceptableNeighbour(level, blockPos1, random1)) {
+- level.setBlock(pos, state.setValue(getPropertyForFace(random1), Boolean.valueOf(true)), 2);
++ } else if (isAcceptableNeighbour(level, blockposition2, enumdirection)) {
++ CraftEventFactory.handleBlockGrowEvent(level, pos, (IBlockData) state.setValue(getPropertyForFace(enumdirection), true), 2); // CraftBukkit
+ }
+ }
+ } else {
+- if (random1 == Direction.UP && pos.getY() < level.getMaxBuildHeight() - 1) {
+- if (this.canSupportAtFace(level, pos, random1)) {
+- level.setBlock(pos, state.setValue(UP, Boolean.valueOf(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;
+ }
+
+@@ -235,8 +250,8 @@
+ }
+ }
+
+- if (this.hasHorizontalConnection(blockState1)) {
+- level.setBlock(blockPos, blockState1, 2);
++ if (this.hasHorizontalConnection(iblockdata2)) {
++ CraftEventFactory.handleBlockSpreadEvent(level, pos, blockposition1, iblockdata2, 2); // CraftBukkit
+ }
+
+ return;
+@@ -244,13 +259,14 @@
+ }
+
+ if (pos.getY() > level.getMinBuildHeight()) {
+- BlockPos blockPos1 = pos.below();
+- BlockState blockState = level.getBlockState(blockPos1);
+- if (blockState.isAir() || blockState.is(this)) {
+- BlockState blockState2 = blockState.isAir() ? this.defaultBlockState() : blockState;
+- BlockState blockState3 = this.copyRandomFaces(state, blockState2, random);
+- if (blockState2 != blockState3 && this.hasHorizontalConnection(blockState3)) {
+- level.setBlock(blockPos1, blockState3, 2);
++ blockposition2 = pos.below();
++ iblockdata1 = level.getBlockState(blockposition2);
++ if (iblockdata1.isAir() || iblockdata1.is((Block) this)) {
++ IBlockData iblockdata3 = iblockdata1.isAir() ? this.defaultBlockState() : iblockdata1;
++ IBlockData iblockdata4 = this.copyRandomFaces(state, iblockdata3, random);
++
++ if (iblockdata3 != iblockdata4 && this.hasHorizontalConnection(iblockdata4)) {
++ CraftEventFactory.handleBlockSpreadEvent(level, pos, blockposition2, iblockdata4, 2); // CraftBukkit
+ }
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/WallHangingSignBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/WallHangingSignBlock.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/WallHangingSignBlock.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/WaterlilyBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/WaterlilyBlock.java.patch
new file mode 100644
index 0000000000..5b7f2f89bb
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/WaterlilyBlock.java.patch
@@ -0,0 +1,24 @@
+--- 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 {
+ public static final MapCodec<WaterlilyBlock> CODEC = simpleCodec(WaterlilyBlock::new);
+@@ -31,6 +35,11 @@
+ public void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
+ super.entityInside(state, level, pos, entity);
+ if (level instanceof ServerLevel && entity instanceof Boat) {
++ // 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-vineflower-stripped/net/minecraft/world/level/block/WeightedPressurePlateBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/WeightedPressurePlateBlock.java.patch
new file mode 100644
index 0000000000..d5bbe02130
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/WeightedPressurePlateBlock.java.patch
@@ -0,0 +1,46 @@
+--- a/net/minecraft/world/level/block/WeightedPressurePlateBlock.java
++++ b/net/minecraft/world/level/block/WeightedPressurePlateBlock.java
+@@ -14,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 {
+ public static final MapCodec<WeightedPressurePlateBlock> CODEC = RecordCodecBuilder.mapCodec(
+@@ -40,9 +42,31 @@
+
+ @Override
+ protected int getSignalStrength(Level level, BlockPos pos) {
+- int min = Math.min(getEntityCount(level, TOUCH_AABB.move(pos), Entity.class), this.maxWeight);
+- if (min > 0) {
+- float f = (float)Math.min(this.maxWeight, min) / (float)this.maxWeight;
++ // 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;
++
+ return Mth.ceil(f * 15.0F);
+ } else {
+ return 0;
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/WitherRoseBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/WitherRoseBlock.java.patch
new file mode 100644
index 0000000000..732ee40145
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/WitherRoseBlock.java.patch
@@ -0,0 +1,17 @@
+--- a/net/minecraft/world/level/block/WitherRoseBlock.java
++++ b/net/minecraft/world/level/block/WitherRoseBlock.java
+@@ -69,8 +62,12 @@
+ @Override
+ public void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) {
+ if (!level.isClientSide && level.getDifficulty() != Difficulty.PEACEFUL) {
+- if (entity instanceof LivingEntity livingEntity && !livingEntity.isInvulnerableTo(level.damageSources().wither())) {
+- livingEntity.addEffect(new MobEffectInstance(MobEffects.WITHER, 40));
++ if (entity instanceof LivingEntity) {
++ LivingEntity entityliving = (LivingEntity) entity;
++
++ 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-vineflower-stripped/net/minecraft/world/level/block/WitherSkullBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/WitherSkullBlock.java.patch
new file mode 100644
index 0000000000..a4f62eda4d
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/WitherSkullBlock.java.patch
@@ -0,0 +1,58 @@
+--- a/net/minecraft/world/level/block/WitherSkullBlock.java
++++ b/net/minecraft/world/level/block/WitherSkullBlock.java
+@@ -23,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);
+ @Nullable
+@@ -49,6 +58,7 @@
+ }
+
+ public static void checkSpawn(Level level, BlockPos pos, SkullBlockEntity blockEntity) {
++ if (level.captureBlockStates) return; // CraftBukkit
+ if (!level.isClientSide) {
+ BlockState blockState = blockEntity.getBlockState();
+ boolean flag = blockState.is(Blocks.WITHER_SKELETON_SKULL) || blockState.is(Blocks.WITHER_SKELETON_WALL_SKULL);
+@@ -69,12 +66,32 @@
+ witherBoss.yBodyRot = blockPatternMatch.getForwards().getAxis() == Direction.Axis.X ? 0.0F : 90.0F;
+ witherBoss.makeInvulnerable();
+
+- for (ServerPlayer serverPlayer : level.getEntitiesOfClass(ServerPlayer.class, witherBoss.getBoundingBox().inflate(50.0))) {
+- CriteriaTriggers.SUMMONED_ENTITY.trigger(serverPlayer, witherBoss);
++ if (shapedetector_shapedetectorcollection != null) {
++ WitherBoss entitywither = (WitherBoss) EntityType.WITHER.create(level);
++
++ if (entitywither != null) {
++ // BlockPumpkinCarved.clearPatternBlocks(world, shapedetector_shapedetectorcollection); // CraftBukkit - move down
++ BlockPos blockposition1 = shapedetector_shapedetectorcollection.getBlock(1, 2, 0).getPos();
++
++ 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();
+
+- level.addFreshEntity(witherBoss);
+- CarvedPumpkinBlock.updatePatternBlocks(level, blockPatternMatch);
++ while (iterator.hasNext()) {
++ ServerPlayer entityplayer = (ServerPlayer) iterator.next();
++
++ CriteriaTriggers.SUMMONED_ENTITY.trigger(entityplayer, (Entity) entitywither);
++ }
++
++ // world.addFreshEntity(entitywither); // CraftBukkit - moved up
++ CarvedPumpkinBlock.updatePatternBlocks(level, shapedetector_shapedetectorcollection);
+ }
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch
new file mode 100644
index 0000000000..d879117ad3
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java.patch
@@ -0,0 +1,268 @@
+--- a/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
+@@ -44,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 {
+ protected static final int SLOT_INPUT = 0;
+@@ -175,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);
+ }
+@@ -252,30 +322,56 @@
+ recipeHolder = null;
+ }
+
+- int maxStackSize = blockEntity.getMaxStackSize();
+- if (!blockEntity.isLit() && canBurn(level.registryAccess(), recipeHolder, blockEntity.items, maxStackSize)) {
+- blockEntity.litTime = blockEntity.getBurnDuration(itemStack);
++ int i = blockEntity.getMaxStackSize();
++
++ 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()) {
+- flag = true;
+- if (flag2) {
+- Item item = itemStack.getItem();
+- itemStack.shrink(1);
+- if (itemStack.isEmpty()) {
+- Item craftingRemainingItem = item.getCraftingRemainingItem();
+- blockEntity.items.set(1, craftingRemainingItem == null ? ItemStack.EMPTY : new ItemStack(craftingRemainingItem));
++ if (blockEntity.isLit() && furnaceBurnEvent.isBurning()) {
++ // CraftBukkit end
++ flag1 = true;
++ if (flag3) {
++ Item item = itemstack.getItem();
++
++ itemstack.shrink(1);
++ if (itemstack.isEmpty()) {
++ Item item1 = item.getCraftingRemainingItem();
++
++ blockEntity.items.set(1, item1 == null ? ItemStack.EMPTY : new ItemStack(item1));
+ }
+ }
+ }
+ }
+
+- if (blockEntity.isLit() && canBurn(level.registryAccess(), recipeHolder, blockEntity.items, maxStackSize)) {
+- blockEntity.cookingProgress++;
++ 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(level.registryAccess(), recipeHolder, blockEntity.items, maxStackSize)) {
+- blockEntity.setRecipeUsed(recipeHolder);
++ if (burn(blockEntity.level, blockEntity.worldPosition, level.registryAccess(), recipeholder, blockEntity.items, i)) { // CraftBukkit
++ blockEntity.setRecipeUsed(recipeholder);
+ }
+
+ flag = true;
+@@ -317,22 +409,41 @@
+ }
+ }
+
+- private static boolean burn(RegistryAccess registryAccess, @Nullable RecipeHolder<?> recipeHolder, NonNullList<ItemStack> list, int i) {
+- if (recipeHolder != null && canBurn(registryAccess, recipeHolder, list, i)) {
+- ItemStack itemStack = list.get(0);
+- ItemStack resultItem = recipeHolder.value().getResultItem(registryAccess);
+- ItemStack itemStack1 = list.get(2);
+- if (itemStack1.isEmpty()) {
+- list.set(2, resultItem.copy());
+- } else if (itemStack1.is(resultItem.getItem())) {
+- itemStack1.grow(1);
++ 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(iregistrycustom);
++ 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;
+ }
+
+ if (itemStack.is(Blocks.WET_SPONGE.asItem()) && !list.get(1).isEmpty() && list.get(1).is(Items.BUCKET)) {
+ list.set(1, new ItemStack(Items.WATER_BUCKET));
+ }
+
+- itemStack.shrink(1);
++ /*
++ 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));
++ }
++
++ itemstack.shrink(1);
+ return true;
+ } else {
+ return false;
+@@ -349,7 +470,10 @@
+ }
+
+ private static int getTotalCookTime(Level level, AbstractFurnaceBlockEntity blockEntity) {
+- return blockEntity.quickCheck.getRecipeFor(blockEntity, level).map(recipeHolder -> recipeHolder.value().getCookingTime()).orElse(200);
++ if (level == null) return 200; // CraftBukkit - SPIGOT-4302
++ return (Integer) blockEntity.quickCheck.getRecipeFor(blockEntity, level).map((recipeholder) -> {
++ return ((AbstractCookingRecipe) recipeholder.value()).getCookingTime();
++ }).orElse(200);
+ }
+
+ public static boolean isFuel(ItemStack stack) {
+@@ -462,9 +592,8 @@
+ public void awardUsedRecipes(Player player, List<ItemStack> items) {
+ }
+
+- public void awardUsedRecipesAndPopExperience(ServerPlayer player) {
+- List<RecipeHolder<?>> recipesToAwardAndPopExperience = this.getRecipesToAwardAndPopExperience(player.serverLevel(), player.position());
+- player.awardRecipes(recipesToAwardAndPopExperience);
++ 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
+
+ for (RecipeHolder<?> recipeHolder : recipesToAwardAndPopExperience) {
+ if (recipeHolder != null) {
+@@ -476,26 +610,46 @@
+ }
+
+ 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();
+
+- for (Entry<ResourceLocation> entry : this.recipesUsed.object2IntEntrySet()) {
+- level.getRecipeManager().byKey(entry.getKey()).ifPresent(recipeHolder -> {
+- list.add((RecipeHolder<?>)recipeHolder);
+- createExperience(level, popVec, entry.getIntValue(), ((AbstractCookingRecipe)recipeHolder.value()).getExperience());
++ while (objectiterator.hasNext()) {
++ Entry<ResourceLocation> entry = (Entry) objectiterator.next();
++
++ worldserver.getRecipeManager().byKey((ResourceLocation) entry.getKey()).ifPresent((recipeholder) -> {
++ list.add(recipeholder);
++ createExperience(worldserver, vec3d, entry.getIntValue(), ((AbstractCookingRecipe) recipeholder.value()).getExperience(), blockposition, entityplayer, itemstack, amount); // CraftBukkit
+ });
+ }
+
+ return list;
+ }
+
+- private static void createExperience(ServerLevel level, Vec3 popVec, int recipeIndex, float experience) {
+- int floor = Mth.floor((float)recipeIndex * experience);
+- float fraction = Mth.frac((float)recipeIndex * experience);
+- if (fraction != 0.0F && Math.random() < (double)fraction) {
+- floor++;
++ 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);
++
++ if (f1 != 0.0F && Math.random() < (double) f1) {
++ ++j;
+ }
+
+- ExperienceOrb.award(level, popVec, floor);
++ // 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-vineflower-stripped/net/minecraft/world/level/block/entity/BannerBlockEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BannerBlockEntity.java.patch
new file mode 100644
index 0000000000..af12c497af
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BannerBlockEntity.java.patch
@@ -0,0 +1,14 @@
+--- a/net/minecraft/world/level/block/entity/BannerBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/BannerBlockEntity.java
+@@ -99,6 +102,11 @@
+ }
+
+ 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-vineflower-stripped/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch
new file mode 100644
index 0000000000..a78514a16d
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch
@@ -0,0 +1,63 @@
+--- a/net/minecraft/world/level/block/entity/BarrelBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/BarrelBlockEntity.java
+@@ -19,6 +20,13 @@
+ 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 {
+ private NonNullList<ItemStack> items = NonNullList.withSize(27, ItemStack.EMPTY);
+ private final ContainerOpenersCounter openersCounter = new ContainerOpenersCounter() {
+@@ -28,11 +29,9 @@
+ BarrelBlockEntity.this.updateBlockState(state, true);
+ }
+
+- @Override
+- protected void onClose(Level level, BlockPos pos, BlockState state) {
+- BarrelBlockEntity.this.playSound(state, SoundEvents.BARREL_CLOSE);
+- BarrelBlockEntity.this.updateBlockState(state, false);
+- }
++ // CraftBukkit start - add fields and methods
++ public List<HumanEntity> transaction = new ArrayList<>();
++ private int maxStack = MAX_STACK;
+
+ @Override
+ protected void openerCountChanged(Level level, BlockPos pos, BlockState state, int count, int openCount) {
+@@ -49,7 +43,30 @@
+ }
+ };
+
+- public BarrelBlockEntity(BlockPos pos, BlockState blockState) {
++ @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;
++ public final ContainerOpenersCounter openersCounter;
++
++ public BarrelBlockEntity(BlockPos pos, IBlockData blockState) {
+ super(BlockEntityType.BARREL, pos, blockState);
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java.patch
new file mode 100644
index 0000000000..5ae4f7ec74
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -85,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-vineflower-stripped/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch
new file mode 100644
index 0000000000..7df6f57821
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BeaconBlockEntity.java.patch
@@ -0,0 +1,123 @@
+--- a/net/minecraft/world/level/block/entity/BeaconBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/BeaconBlockEntity.java
+@@ -38,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 {
+ private static final int MAX_LEVELS = 4;
+@@ -65,29 +66,18 @@
+ @Nullable
+ MobEffect secondaryPower;
+ @Nullable
+- private Component name;
+- private LockCode lockKey = LockCode.NO_LOCK;
+- private final ContainerData dataAccess = new ContainerData() {
+- @Override
+- public int get(int index) {
+- return switch (index) {
+- case 0 -> BeaconBlockEntity.this.levels;
+- case 1 -> BeaconMenu.encodeEffect(BeaconBlockEntity.this.primaryPower);
+- case 2 -> BeaconMenu.encodeEffect(BeaconBlockEntity.this.secondaryPower);
+- default -> 0;
+- };
+- }
++ public Component name;
++ public 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;
++ }
+
+- @Override
+- public void set(int index, int value) {
+- switch (index) {
+- case 0:
+- BeaconBlockEntity.this.levels = value;
+- break;
+- case 1:
+- if (!BeaconBlockEntity.this.level.isClientSide && !BeaconBlockEntity.this.beamSections.isEmpty()) {
+- BeaconBlockEntity.playSound(BeaconBlockEntity.this.level, BeaconBlockEntity.this.worldPosition, SoundEvents.BEACON_POWER_SELECT);
+- }
++ 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
+
+ BeaconBlockEntity.this.primaryPower = BeaconBlockEntity.filterEffect(BeaconMenu.decodeEffect(value));
+ break;
+@@ -232,12 +258,13 @@
+ super.setRemoved();
+ }
+
+- private static void applyEffects(Level level, BlockPos pos, int levels, @Nullable MobEffect primary, @Nullable MobEffect secondary) {
+- if (!level.isClientSide && primary != null) {
+- double d = (double)(levels * 10 + 10);
+- int i = 0;
+- if (levels >= 4 && primary == secondary) {
+- i = 1;
++ // CraftBukkit start - split into components
++ private static byte getAmplification(int i, @Nullable MobEffect mobeffectlist, @Nullable MobEffect mobeffectlist1) {
++ {
++ byte b0 = 0;
++
++ if (i >= 4 && mobeffectlist == mobeffectlist1) {
++ b0 = 1;
+ }
+
+ int i1 = (9 + levels * 2) * 20;
+@@ -256,6 +312,24 @@
+ }
+ }
+
++ private static void applyEffects(Level level, BlockPos pos, int levels, @Nullable MobEffect primary, @Nullable MobEffect secondary) {
++ if (!level.isClientSide && primary != null) {
++ double d0 = (double) (levels * 10 + 10);
++ byte b0 = getAmplification(levels, primary, secondary);
++
++ int j = getLevel(levels);
++ List list = getHumansInRange(level, pos, levels);
++
++ applyEffect(list, primary, j, b0);
++
++ if (hasSecondaryEffect(levels, primary, secondary)) {
++ applyEffect(list, secondary, j, 0);
++ }
++ }
++
++ }
++ // CraftBukkit end
++
+ public static void playSound(Level level, BlockPos pos, SoundEvent sound) {
+ level.playSound(null, pos, sound, SoundSource.BLOCKS, 1.0F, 1.0F);
+ }
+@@ -284,10 +360,11 @@
+ }
+
+ @Nullable
+- private static MobEffect loadEffect(CompoundTag compoundTag, String string) {
+- if (compoundTag.contains(string, 8)) {
+- ResourceLocation resourceLocation = ResourceLocation.tryParse(compoundTag.getString(string));
+- return filterEffect(BuiltInRegistries.MOB_EFFECT.get(resourceLocation));
++ private static MobEffect loadEffect(CompoundTag nbttagcompound, String s) {
++ if (nbttagcompound.contains(s, 8)) {
++ ResourceLocation minecraftkey = ResourceLocation.tryParse(nbttagcompound.getString(s));
++
++ return (MobEffect) BuiltInRegistries.MOB_EFFECT.get(minecraftkey); // CraftBukkit - persist manually set non-default beacon effects (SPIGOT-3598)
+ } else {
+ return null;
+ }
+@@ -298,6 +375,7 @@
+ 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"));
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java.patch
new file mode 100644
index 0000000000..50c6c909d1
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java.patch
@@ -0,0 +1,186 @@
+--- a/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java
+@@ -70,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 pos, BlockState blockState) {
+ super(BlockEntityType.BEEHIVE, pos, blockState);
+@@ -104,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 state, BeehiveBlockEntity.BeeReleaseStatus releaseStatus) {
+@@ -115,7 +100,7 @@
+ Bee bee = (Bee)entity;
+ if (player.position().distanceToSqr(entity.position()) <= 16.0) {
+ if (!this.isSedated()) {
+- bee.setTarget(player);
++ entitybee.setTarget(player, org.bukkit.event.entity.EntityTargetEvent.TargetReason.CLOSEST_PLAYER, true); // CraftBukkit
+ } else {
+ bee.setStayOutOfHiveCountdown(400);
+ }
+@@ -125,9 +111,18 @@
+ }
+ }
+
+- private List<Entity> releaseAllOccupants(BlockState state, BeehiveBlockEntity.BeeReleaseStatus releaseStatus) {
++ 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(data -> releaseOccupant(this.level, this.worldPosition, state, data, list, releaseStatus, 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();
+ }
+@@ -154,7 +149,19 @@
+ }
+
+ public void addOccupantWithPresetTicks(Entity occupant, boolean hasNectar, int ticksInHive) {
+- if (this.stored.size() < 3) {
++ 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 compoundTag = new CompoundTag();
+@@ -189,16 +192,14 @@
+ this.stored.add(new BeehiveBlockEntity.BeeData(entityData, ticksInHive, hasNectar ? 2400 : 600));
+ }
+
+- private static boolean releaseOccupant(
+- Level level,
+- BlockPos pos,
+- BlockState state,
+- BeehiveBlockEntity.BeeData data,
+- @Nullable List<Entity> storedInHives,
+- BeehiveBlockEntity.BeeReleaseStatus releaseStatus,
+- @Nullable BlockPos savedFlowerPos
+- ) {
+- if ((level.isNight() || level.isRaining()) && releaseStatus != 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 = data.entityData.copy();
+@@ -216,9 +222,23 @@
+ if (!entity.getType().is(EntityTypeTags.BEEHIVE_INHABITORS)) {
+ return false;
+ } else {
+- if (entity instanceof Bee bee) {
+- if (savedFlowerPos != null && !bee.hasSavedFlowerPos() && level.random.nextFloat() < 0.9F) {
+- bee.setSavedFlowerPos(savedFlowerPos);
++ // CraftBukkit start
++ if (entity instanceof Bee) {
++ float f = entity.getBbWidth();
++ double d0 = flag ? 0.0D : 0.55D + (double) (f / 2.0F);
++ double d1 = (double) blockposition.getX() + 0.5D + d0 * (double) enumdirection.getStepX();
++ double d2 = (double) blockposition.getY() + 0.5D - (double) (entity.getBbHeight() / 2.0F);
++ double d3 = (double) blockposition.getZ() + 0.5D + d0 * (double) enumdirection.getStepZ();
++
++ 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 (releaseStatus == BeehiveBlockEntity.BeeReleaseStatus.HONEY_DELIVERED) {
+@@ -241,17 +265,20 @@
+ storedInHives.add(bee);
+ }
+
+- float bbWidth = entity.getBbWidth();
+- double d = flag ? 0.0 : 0.55 + (double)(bbWidth / 2.0F);
+- double d1 = (double)pos.getX() + 0.5 + d * (double)direction.getStepX();
+- double d2 = (double)pos.getY() + 0.5 - (double)(entity.getBbHeight() / 2.0F);
+- double d3 = (double)pos.getZ() + 0.5 + d * (double)direction.getStepZ();
++ /* // CraftBukkit start
++ float f = entity.getBbWidth();
++ double d0 = flag ? 0.0D : 0.55D + (double) (f / 2.0F);
++ double d1 = (double) blockposition.getX() + 0.5D + d0 * (double) enumdirection.getStepX();
++ double d2 = (double) blockposition.getY() + 0.5D - (double) (entity.getBbHeight() / 2.0F);
++ double d3 = (double) blockposition.getZ() + 0.5D + d0 * (double) enumdirection.getStepZ();
++
+ entity.moveTo(d1, d2, d3, entity.getYRot(), entity.getXRot());
++ */ // CraftBukkit end
+ }
+
+- level.playSound(null, pos, SoundEvents.BEEHIVE_EXIT, SoundSource.BLOCKS, 1.0F, 1.0F);
+- level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(entity, level.getBlockState(pos)));
+- 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;
+@@ -294,6 +327,10 @@
+ if (releaseOccupant(level, pos, state, beeData, null, beeReleaseStatus, savedFlowerPos)) {
+ 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
+ }
+ }
+
+@@ -335,6 +371,12 @@
+ if (tag.contains("FlowerPos")) {
+ this.savedFlowerPos = NbtUtils.readBlockPos(tag.getCompound("FlowerPos"));
+ }
++
++ // CraftBukkit start
++ if (tag.contains("Bukkit.MaxEntities")) {
++ this.maxBees = tag.getInt("Bukkit.MaxEntities");
++ }
++ // CraftBukkit end
+ }
+
+ @Override
+@@ -344,6 +386,8 @@
+ if (this.hasSavedFlowerPos()) {
+ tag.put("FlowerPos", NbtUtils.writeBlockPos(this.savedFlowerPos));
+ }
++ tag.putInt("Bukkit.MaxEntities", this.maxBees); // CraftBukkit
++
+ }
+
+ public ListTag writeBees() {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BellBlockEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BellBlockEntity.java.patch
new file mode 100644
index 0000000000..7395d0f362
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BellBlockEntity.java.patch
@@ -0,0 +1,17 @@
+--- a/net/minecraft/world/level/block/entity/BellBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/BellBlockEntity.java
+@@ -129,7 +144,13 @@
+ }
+
+ private static void makeRaidersGlow(Level level, BlockPos pos, List<LivingEntity> raiders) {
+- raiders.stream().filter(raider -> isRaiderWithinRange(pos, raider)).forEach(BellBlockEntity::glow);
++ 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 pos, List<LivingEntity> raiders) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BlockEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BlockEntity.java.patch
new file mode 100644
index 0000000000..8d9c62e9fe
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BlockEntity.java.patch
@@ -0,0 +1,68 @@
+--- a/net/minecraft/world/level/block/entity/BlockEntity.java
++++ b/net/minecraft/world/level/block/entity/BlockEntity.java
+@@ -15,7 +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
+@@ -47,11 +58,13 @@
+ return this.level != null;
+ }
+
++ // CraftBukkit start - read container
+ public void load(CompoundTag tag) {
+ }
+
+ protected void saveAdditional(CompoundTag tag) {
+ }
++ // CraftBukkit end
+
+ public final CompoundTag saveWithFullMetadata() {
+ CompoundTag compoundTag = this.saveWithoutMetadata();
+@@ -66,9 +86,15 @@
+ }
+
+ public final CompoundTag saveWithoutMetadata() {
+- CompoundTag compoundTag = new CompoundTag();
+- this.saveAdditional(compoundTag);
+- return compoundTag;
++ CompoundTag nbttagcompound = new 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 tag) {
+@@ -191,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-vineflower-stripped/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch
new file mode 100644
index 0000000000..5f4c563bd4
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java.patch
@@ -0,0 +1,189 @@
+--- a/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java
+@@ -24,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;
+ private static final int FUEL_SLOT = 4;
+@@ -38,19 +54,12 @@
+ int brewTime;
+ private boolean[] lastPotionCount;
+ private Item ingredient;
+- int fuel;
+- protected final ContainerData dataAccess = new ContainerData() {
+- @Override
+- public int get(int index) {
+- switch (index) {
+- case 0:
+- return BrewingStandBlockEntity.this.brewTime;
+- case 1:
+- return BrewingStandBlockEntity.this.fuel;
+- default:
+- return 0;
+- }
+- }
++ public 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;
+
+ @Override
+ public void set(int index, int value) {
+@@ -69,7 +69,25 @@
+ }
+ };
+
+- public BrewingStandBlockEntity(BlockPos pos, BlockState state) {
++ 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);
+ }
+
+@@ -94,31 +149,54 @@
+ return true;
+ }
+
+- public static void serverTick(Level level, BlockPos pos, BlockState state, BrewingStandBlockEntity blockEntity) {
+- ItemStack itemStack = blockEntity.items.get(4);
+- if (blockEntity.fuel <= 0 && itemStack.is(Items.BLAZE_POWDER)) {
+- blockEntity.fuel = 20;
+- itemStack.shrink(1);
++ public static void serverTick(Level level, BlockPos pos, IBlockData state, BrewingStandBlockEntity blockEntity) {
++ ItemStack itemstack = (ItemStack) blockEntity.items.get(4);
++
++ 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 isBrewable = isBrewable(blockEntity.items);
+- boolean flag = blockEntity.brewTime > 0;
+- ItemStack itemStack1 = blockEntity.items.get(3);
+- if (flag) {
+- blockEntity.brewTime--;
+- boolean flag1 = blockEntity.brewTime == 0;
+- if (flag1 && isBrewable) {
+- doBrew(level, pos, blockEntity.items);
++ boolean flag = isBrewable(blockEntity.items);
++ boolean flag1 = blockEntity.brewTime > 0;
++ ItemStack itemstack1 = (ItemStack) blockEntity.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) {
++ blockEntity.brewTime -= elapsedTicks;
++ boolean flag2 = blockEntity.brewTime <= 0; // == -> <=
++ // CraftBukkit end
++
++ if (flag2 && flag) {
++ doBrew(level, pos, blockEntity.items, blockEntity); // CraftBukkit
+ setChanged(level, pos, state);
+ } else if (!isBrewable || !itemStack1.is(blockEntity.ingredient)) {
+ blockEntity.brewTime = 0;
+ setChanged(level, pos, state);
+ }
+- } else if (isBrewable && blockEntity.fuel > 0) {
+- blockEntity.fuel--;
+- blockEntity.brewTime = 400;
+- blockEntity.ingredient = itemStack1.getItem();
++ } 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);
+ }
+
+@@ -168,21 +251,33 @@
+ }
+ }
+
+- private static void doBrew(Level level, BlockPos pos, NonNullList<ItemStack> items) {
+- ItemStack itemStack = items.get(3);
++ // CraftBukkit start
++ private static void doBrew(Level world, BlockPos blockposition, NonNullList<ItemStack> nonnulllist, BrewingStandBlockEntity tileentitybrewingstand) {
++ ItemStack itemstack = (ItemStack) nonnulllist.get(3);
++ InventoryHolder owner = tileentitybrewingstand.getOwner();
++ List<org.bukkit.inventory.ItemStack> brewResults = new ArrayList<>(3);
+
+ for (int i = 0; i < 3; i++) {
+ items.set(i, PotionBrewing.mix(itemStack, items.get(i)));
+ }
+
+- itemStack.shrink(1);
+- if (itemStack.getItem().hasCraftingRemainingItem()) {
+- ItemStack itemStack1 = new ItemStack(itemStack.getItem().getCraftingRemainingItem());
+- if (itemStack.isEmpty()) {
+- itemStack = itemStack1;
++ 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 {
+ Containers.dropItemStack(level, (double)pos.getX(), (double)pos.getY(), (double)pos.getZ(), itemStack1);
+ }
++ // CraftBukkit end
+ }
+
+ items.set(3, itemStack);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BrushableBlockEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BrushableBlockEntity.java.patch
new file mode 100644
index 0000000000..57849c0e54
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/BrushableBlockEntity.java.patch
@@ -0,0 +1,56 @@
+--- 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();
+ private static final String LOOT_TABLE_TAG = "LootTable";
+@@ -127,17 +148,21 @@
+ if (this.level != null && this.level.getServer() != null) {
+ this.unpackLootTable(player);
+ if (!this.item.isEmpty()) {
+- double d = (double)EntityType.ITEM.getWidth();
+- double d1 = 1.0 - d;
+- double d2 = d / 2.0;
+- Direction direction = Objects.requireNonNullElse(this.hitDirection, Direction.UP);
+- BlockPos blockPos = this.worldPosition.relative(direction, 1);
+- double d3 = (double)blockPos.getX() + 0.5 * d1 + d2;
+- double d4 = (double)blockPos.getY() + 0.5 + (double)(EntityType.ITEM.getHeight() / 2.0F);
+- double d5 = (double)blockPos.getZ() + 0.5 * 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);
++ double d0 = (double) EntityType.ITEM.getWidth();
++ double d1 = 1.0D - d0;
++ double d2 = d0 / 2.0D;
++ Direction enumdirection = (Direction) Objects.requireNonNullElse(this.hitDirection, Direction.UP);
++ BlockPos blockposition = this.worldPosition.relative(enumdirection, 1);
++ double d3 = (double) blockposition.getX() + 0.5D * d1 + d2;
++ double d4 = (double) blockposition.getY() + 0.5D + (double) (EntityType.ITEM.getHeight() / 2.0F);
++ double d5 = (double) blockposition.getZ() + 0.5D * d1 + d2;
++ ItemEntity entityitem = new ItemEntity(this.level, d3, d4, d5, this.item.split(this.level.random.nextInt(21) + 10));
++
++ 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;
+ }
+ }
+@@ -208,6 +239,7 @@
+
+ @Override
+ 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"));
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/CampfireBlockEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/CampfireBlockEntity.java.patch
new file mode 100644
index 0000000000..ae5d69ea43
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/CampfireBlockEntity.java.patch
@@ -0,0 +1,76 @@
+--- 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;
+ private static final int NUM_SLOTS = 4;
+@@ -47,13 +62,27 @@
+ flag = true;
+ blockEntity.cookingProgress[i]++;
+ if (blockEntity.cookingProgress[i] >= blockEntity.cookingTime[i]) {
+- Container container = new SimpleContainer(itemStack);
+- ItemStack itemStack1 = blockEntity.quickCheck
+- .getRecipeFor(container, level)
+- .map(recipeHolder -> recipeHolder.value().assemble(container, level.registryAccess()))
+- .orElse(itemStack);
+- if (itemStack1.isItemEnabled(level.enabledFeatures())) {
+- Containers.dropItemStack(level, (double)pos.getX(), (double)pos.getY(), (double)pos.getZ(), itemStack1);
++ SimpleContainer inventorysubcontainer = new SimpleContainer(new ItemStack[]{itemstack});
++ ItemStack itemstack1 = (ItemStack) blockEntity.quickCheck.getRecipeFor(inventorysubcontainer, level).map((recipeholder) -> {
++ return ((CampfireCookingRecipe) recipeholder.value()).assemble(inventorysubcontainer, level.registryAccess());
++ }).orElse(itemstack);
++
++ if (itemstack1.isItemEnabled(level.enabledFeatures())) {
++ // 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));
+@@ -158,12 +190,17 @@
+ }
+
+ public boolean placeFood(@Nullable Entity entity, ItemStack stack, int cookTime) {
+- for (int i = 0; i < this.items.size(); i++) {
+- ItemStack itemStack = this.items.get(i);
+- if (itemStack.isEmpty()) {
+- this.cookingTime[i] = cookTime;
+- this.cookingProgress[i] = 0;
+- this.items.set(i, stack.split(1));
++ for (int j = 0; j < this.items.size(); ++j) {
++ ItemStack itemstack1 = (ItemStack) this.items.get(j);
++
++ if (itemstack1.isEmpty()) {
++ // 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, stack.split(1));
+ this.level.gameEvent(GameEvent.BLOCK_CHANGE, this.getBlockPos(), GameEvent.Context.of(entity, this.getBlockState()));
+ this.markUpdated();
+ return true;
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/ChestBlockEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/ChestBlockEntity.java.patch
new file mode 100644
index 0000000000..fad929d5b4
--- /dev/null
+++ b/patch-remap/mache-vineflower-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 {
+ private static final int EVENT_SET_OPEN_COUNT = 1;
+@@ -32,10 +35,9 @@
+ ChestBlockEntity.playSound(level, pos, state, SoundEvents.CHEST_OPEN);
+ }
+
+- @Override
+- protected void onClose(Level level, BlockPos pos, BlockState state) {
+- ChestBlockEntity.playSound(level, pos, state, SoundEvents.CHEST_CLOSE);
+- }
++ // CraftBukkit start - add fields and methods
++ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
++ private int maxStack = MAX_STACK;
+
+ @Override
+ protected void openerCountChanged(Level level, BlockPos pos, BlockState state, int count, int openCount) {
+@@ -55,7 +47,25 @@
+ };
+ private final ChestLidController chestLidController = new ChestLidController();
+
+- protected ChestBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState blockState) {
++ 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);
+ }
+
+@@ -182,4 +233,11 @@
+ Block block = state.getBlock();
+ level.blockEvent(pos, block, 1, eventParam);
+ }
++
++ // CraftBukkit start
++ @Override
++ public boolean onlyOpCanSetNbt() {
++ return true;
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch
new file mode 100644
index 0000000000..ede2f8b490
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch
@@ -0,0 +1,98 @@
+--- a/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java
+@@ -17,13 +18,55 @@
+ 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 = NonNullList.withSize(6, ItemStack.EMPTY);
+- private int lastInteractedSlot = -1;
++ private final NonNullList<ItemStack> items;
++ public int lastInteractedSlot;
++ // CraftBukkit start - add fields and methods
++ public List<HumanEntity> transaction = new java.util.ArrayList<>();
++ private int maxStack = 1;
+
+- public ChiseledBookShelfBlockEntity(BlockPos pos, BlockState state) {
++ @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);
+ }
+
+@@ -47,6 +94,7 @@
+
+ @Override
+ public void load(CompoundTag tag) {
++ super.load(tag); // CraftBukkit - SPIGOT-7393: Load super Bukkit data
+ this.items.clear();
+ ContainerHelper.loadAllItems(tag, this.items);
+ this.lastInteractedSlot = tag.getInt("last_interacted_slot");
+@@ -86,8 +135,8 @@
+ public ItemStack removeItem(int slot, int amount) {
+ ItemStack itemStack = Objects.requireNonNullElse(this.items.get(slot), ItemStack.EMPTY);
+ this.items.set(slot, ItemStack.EMPTY);
+- if (!itemStack.isEmpty()) {
+- this.updateState(slot);
++ if (!itemstack.isEmpty()) {
++ if (level != null) this.updateState(slot); // CraftBukkit - SPIGOT-7381: check for null world
+ }
+
+ return itemStack;
+@@ -102,7 +151,7 @@
+ public void setItem(int slot, ItemStack stack) {
+ if (stack.is(ItemTags.BOOKSHELF_BOOKS)) {
+ this.items.set(slot, stack);
+- this.updateState(slot);
++ if (level != null) this.updateState(slot); // CraftBukkit - SPIGOT-7381: check for null world
+ } else if (stack.isEmpty()) {
+ this.removeItem(slot, 1);
+ }
+@@ -119,7 +167,7 @@
+
+ @Override
+ public int getMaxStackSize() {
+- return 1;
++ return maxStack; // CraftBukkit
+ }
+
+ @Override
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/CommandBlockEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/CommandBlockEntity.java.patch
new file mode 100644
index 0000000000..08d8c11523
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/CommandBlockEntity.java.patch
@@ -0,0 +1,17 @@
+--- a/net/minecraft/world/level/block/entity/CommandBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/CommandBlockEntity.java
+@@ -18,7 +20,14 @@
+ 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 command) {
+ super.setCommand(command);
+ CommandBlockEntity.this.setChanged();
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch
new file mode 100644
index 0000000000..7f39d91040
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/ConduitBlockEntity.java.patch
@@ -0,0 +1,74 @@
+--- a/net/minecraft/world/level/block/entity/ConduitBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/ConduitBlockEntity.java
+@@ -25,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 {
+ private static final int BLOCK_REFRESH_RATE = 2;
+@@ -167,19 +190,22 @@
+ }
+
+ private static void applyEffects(Level level, BlockPos pos, List<BlockPos> positions) {
+- int size = positions.size();
+- int i = size / 7 * 16;
+- int x = pos.getX();
+- int y = pos.getY();
+- int z = pos.getZ();
+- AABB aABB = new AABB((double)x, (double)y, (double)z, (double)(x + 1), (double)(y + 1), (double)(z + 1))
+- .inflate((double)i)
+- .expandTowards(0.0, (double)level.getHeight(), 0.0);
+- List<Player> entitiesOfClass = level.getEntitiesOfClass(Player.class, aABB);
+- if (!entitiesOfClass.isEmpty()) {
+- for (Player player : entitiesOfClass) {
+- if (pos.closerThan(player.blockPosition(), (double)i) && player.isInWaterOrRain()) {
+- player.addEffect(new MobEffectInstance(MobEffects.CONDUIT_POWER, 260, 0, true, true));
++ int i = positions.size();
++ int j = i / 7 * 16;
++ int k = pos.getX();
++ int l = pos.getY();
++ int i1 = pos.getZ();
++ AABB axisalignedbb = (new AABB((double) k, (double) l, (double) i1, (double) (k + 1), (double) (l + 1), (double) (i1 + 1))).inflate((double) j).expandTowards(0.0D, (double) level.getHeight(), 0.0D);
++ List<Player> list1 = level.getEntitiesOfClass(Player.class, axisalignedbb);
++
++ if (!list1.isEmpty()) {
++ Iterator iterator = list1.iterator();
++
++ while (iterator.hasNext()) {
++ Player entityhuman = (Player) iterator.next();
++
++ 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
+ }
+ }
+ }
+@@ -205,17 +234,13 @@
+ }
+
+ if (blockEntity.destroyTarget != null) {
+- level.playSound(
+- null,
+- blockEntity.destroyTarget.getX(),
+- blockEntity.destroyTarget.getY(),
+- blockEntity.destroyTarget.getZ(),
+- SoundEvents.CONDUIT_ATTACK_TARGET,
+- SoundSource.BLOCKS,
+- 1.0F,
+- 1.0F
+- );
+- blockEntity.destroyTarget.hurt(level.damageSources().magic(), 4.0F);
++ // 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 != blockEntity.destroyTarget) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch
new file mode 100644
index 0000000000..3c36db0c9e
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java.patch
@@ -0,0 +1,94 @@
+--- a/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java
++++ b/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java
+@@ -11,6 +13,7 @@
+ public abstract class ContainerOpenersCounter {
+ private static final int CHECK_TICK_DELAY = 5;
+ private int openCount;
++ public boolean opened; // CraftBukkit
+
+ protected abstract void onOpen(Level level, BlockPos pos, BlockState state);
+
+@@ -18,10 +21,38 @@
+
+ protected abstract void openerCountChanged(Level level, BlockPos pos, BlockState state, int count, int openCount);
+
++ protected abstract void openerCountChanged(Level level, BlockPos pos, IBlockData 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 pos, BlockState state) {
++ 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, pos, state);
+ level.gameEvent(player, GameEvent.CONTAINER_OPEN, pos);
+@@ -31,8 +62,20 @@
+ this.openerCountChanged(level, pos, state, i, this.openCount);
+ }
+
+- public void decrementOpeners(Player player, Level level, BlockPos pos, BlockState state) {
++ 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, pos, state);
+ level.gameEvent(player, GameEvent.CONTAINER_CLOSE, pos);
+@@ -57,12 +94,15 @@
+ return level.getEntities(EntityTypeTest.forClass(Player.class), aABB, this::isOwnContainer).size();
+ }
+
+- public void recheckOpeners(Level level, BlockPos pos, BlockState state) {
+- int openCount = this.getOpenCount(level, pos);
+- int i = this.openCount;
+- if (i != openCount) {
+- boolean flag = openCount != 0;
+- boolean flag1 = i != 0;
++ 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) {
++ boolean flag = i != 0;
++ boolean flag1 = j != 0;
++
+ if (flag && !flag1) {
+ this.onOpen(level, pos, state);
+ level.gameEvent(null, GameEvent.CONTAINER_OPEN, pos);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/CrafterBlockEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/CrafterBlockEntity.java.patch
new file mode 100644
index 0000000000..8735859759
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/CrafterBlockEntity.java.patch
@@ -0,0 +1,91 @@
+--- a/net/minecraft/world/level/block/entity/CrafterBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/CrafterBlockEntity.java
+@@ -20,7 +21,14 @@
+ 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 class CrafterBlockEntity extends RandomizableContainerBlockEntity implements InventoryCrafting {
++
+ public static final int CONTAINER_WIDTH = 3;
+ public static final int CONTAINER_HEIGHT = 3;
+ public static final int CONTAINER_SIZE = 9;
+@@ -28,23 +36,61 @@
+ public static final int SLOT_ENABLED = 0;
+ public static final int DATA_TRIGGERED = 9;
+ public static final int NUM_DATA = 10;
+- private NonNullList<ItemStack> items = NonNullList.withSize(9, ItemStack.EMPTY);
+- private int craftingTicksRemaining = 0;
+- protected final ContainerData containerData = new ContainerData() {
+- private final int[] slotStates = new int[9];
+- private int triggered = 0;
++ private NonNullList<ItemStack> items;
++ public int craftingTicksRemaining;
++ protected final ContainerData containerData;
++ // CraftBukkit start - add fields and methods
++ public List<HumanEntity> transaction = new java.util.ArrayList<>();
++ private int maxStack = 1;
+
+ @Override
+ public int get(int index) {
+ return index == 9 ? this.triggered : this.slotStates[index];
+ }
+
+- @Override
+- public void set(int index, int value) {
+- if (index == 9) {
+- this.triggered = value;
+- } else {
+- this.slotStates[index] = value;
++ @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() {
++ private final int[] slotStates = new int[9];
++ private int triggered = 0;
++
++ @Override
++ public int get(int index) {
++ return index == 9 ? this.triggered : this.slotStates[index];
+ }
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/DecoratedPotBlockEntity.java.patch
new file mode 100644
index 0000000000..29c15bb99a
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -20,7 +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-vineflower-stripped/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch
new file mode 100644
index 0000000000..d341480175
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/DispenserBlockEntity.java.patch
@@ -0,0 +1,51 @@
+--- a/net/minecraft/world/level/block/entity/DispenserBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/DispenserBlockEntity.java
+@@ -12,11 +13,47 @@
+ 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 = NonNullList.withSize(9, ItemStack.EMPTY);
+
+- protected DispenserBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState 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);
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/HopperBlockEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/HopperBlockEntity.java.patch
new file mode 100644
index 0000000000..becf7c3ca2
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/HopperBlockEntity.java.patch
@@ -0,0 +1,231 @@
+--- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java
+@@ -29,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 {
+ public static final int MOVE_ITEM_SPEED = 8;
+@@ -37,7 +51,37 @@
+ private int cooldownTime = -1;
+ private long tickedGameTime;
+
+- public HopperBlockEntity(BlockPos pos, BlockState 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);
+ }
+
+@@ -103,7 +154,7 @@
+ if (!blockEntity.isOnCooldown() && state.getValue(HopperBlock.ENABLED)) {
+ boolean flag = false;
+ if (!blockEntity.isEmpty()) {
+- flag = ejectItems(level, pos, state, blockEntity);
++ flag = ejectItems(level, pos, state, (Container) blockEntity, blockEntity); // CraftBukkit
+ }
+
+ if (!blockEntity.inventoryFull()) {
+@@ -131,21 +188,46 @@
+ return true;
+ }
+
+- private static boolean ejectItems(Level level, BlockPos pos, BlockState state, Container sourceContainer) {
+- Container attachedContainer = getAttachedContainer(level, pos, state);
+- if (attachedContainer == null) {
++ private static boolean ejectItems(Level world, BlockPos blockposition, IBlockData iblockdata, Container iinventory, HopperBlockEntity hopper) { // CraftBukkit
++ Container iinventory1 = getAttachedContainer(world, blockposition, iblockdata);
++
++ if (iinventory1 == null) {
+ return false;
+ } else {
+ Direction opposite = state.getValue(HopperBlock.FACING).getOpposite();
+ if (isFullContainer(attachedContainer, opposite)) {
+ return false;
+ } else {
+- for (int i = 0; i < sourceContainer.getContainerSize(); i++) {
+- if (!sourceContainer.getItem(i).isEmpty()) {
+- ItemStack itemStack = sourceContainer.getItem(i).copy();
+- ItemStack itemStack1 = addItem(sourceContainer, attachedContainer, sourceContainer.removeItem(i, 1), opposite);
+- if (itemStack1.isEmpty()) {
+- attachedContainer.setChanged();
++ for (int i = 0; i < iinventory.getContainerSize(); ++i) {
++ if (!iinventory.getItem(i).isEmpty()) {
++ ItemStack itemstack = iinventory.getItem(i).copy();
++ // ItemStack itemstack1 = addItem(iinventory, iinventory1, iinventory.removeItem(i, 1), enumdirection);
++
++ // 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()) {
++ iinventory1.setChanged();
+ return true;
+ }
+
+@@ -193,11 +286,40 @@
+ }
+
+ private static boolean tryTakeInItemFromSlot(Hopper hopper, Container container, int slot, Direction direction) {
+- ItemStack item = container.getItem(slot);
+- if (!item.isEmpty() && canTakeItemFromContainer(hopper, container, item, slot, direction)) {
+- ItemStack itemStack = item.copy();
+- ItemStack itemStack1 = addItem(container, hopper, container.removeItem(slot, 1), null);
+- if (itemStack1.isEmpty()) {
++ ItemStack itemstack = container.getItem(slot);
++
++ if (!itemstack.isEmpty() && canTakeItemFromContainer(hopper, container, itemstack, slot, direction)) {
++ ItemStack itemstack1 = itemstack.copy();
++ // 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;
+ }
+@@ -210,9 +332,17 @@
+
+ public static boolean addItem(Container container, ItemEntity item) {
+ boolean flag = false;
+- ItemStack itemStack = item.getItem().copy();
+- ItemStack itemStack1 = addItem(null, container, itemStack, null);
+- if (itemStack1.isEmpty()) {
++ // 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()) {
+ flag = true;
+ item.setItem(ItemStack.EMPTY);
+ item.discard();
+@@ -301,15 +465,38 @@
+ return stack;
+ }
+
++ // CraftBukkit start
+ @Nullable
+ private static Container getAttachedContainer(Level level, BlockPos pos, BlockState state) {
+ Direction direction = state.getValue(HopperBlock.FACING);
+ return getContainerAt(level, pos.relative(direction));
+ }
++ // CraftBukkit end
+
+ @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.0, 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-vineflower-stripped/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch
new file mode 100644
index 0000000000..7c87f0c2bd
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java.patch
@@ -0,0 +1,88 @@
+--- a/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/JukeboxBlockEntity.java
+@@ -21,15 +22,59 @@
+ 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 = ItemStack.EMPTY;
+ 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 pos, BlockState blockState) {
++ @Override
++ public List<ItemStack> getContents() {
++ return Collections.singletonList(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 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 JukeboxBlockEntity(BlockPos pos, IBlockData blockState) {
+ super(BlockEntityType.JUKEBOX, pos, blockState);
+ }
+
+@@ -138,7 +194,7 @@
+
+ @Override
+ public int getMaxStackSize() {
+- return 1;
++ return maxStack; // CraftBukkit
+ }
+
+ @Override
+@@ -186,7 +247,11 @@
+ @VisibleForTesting
+ public void setRecordWithoutPlaying(ItemStack stack) {
+ this.item = stack;
+- this.level.updateNeighborsAt(this.getBlockPos(), this.getBlockState().getBlock());
++ // 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-vineflower-stripped/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch
new file mode 100644
index 0000000000..e75ceb1d54
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/LecternBlockEntity.java.patch
@@ -0,0 +1,158 @@
+--- a/net/minecraft/world/level/block/entity/LecternBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/LecternBlockEntity.java
+@@ -23,14 +24,73 @@
+ 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
++ public List<ItemStack> getContents() {
++ return Arrays.asList(book);
++ }
++
++ @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;
+ }
+@@ -72,12 +134,14 @@
+ }
+
+ @Override
++ // CraftBukkit start
+ public void setItem(int slot, ItemStack stack) {
+ }
++ // CraftBukkit end
+
+ @Override
+ public int getMaxStackSize() {
+- return 1;
++ return maxStack; // CraftBukkit
+ }
+
+ @Override
+@@ -155,7 +227,7 @@
+ if (i != this.page) {
+ this.page = i;
+ this.setChanged();
+- LecternBlock.signalPageChange(this.getLevel(), this.getBlockPos(), this.getBlockState());
++ if (this.level != null) LecternBlock.signalPageChange(this.getLevel(), this.getBlockPos(), this.getBlockState()); // CraftBukkit
+ }
+ }
+
+@@ -176,6 +250,32 @@
+ return stack;
+ }
+
++ // 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 string;
+ Component component;
+@@ -187,8 +288,10 @@
+ component = player.getDisplayName();
+ }
+
+- Vec3 vec3 = Vec3.atCenterOf(this.worldPosition);
+- return new CommandSourceStack(CommandSource.NULL, vec3, Vec2.ZERO, (ServerLevel)this.level, 2, string, component, this.level.getServer(), player);
++ Vec3 vec3d = Vec3.atCenterOf(this.worldPosition);
++
++ // CraftBukkit - this
++ return new CommandSourceStack(this, vec3d, Vec2.ZERO, (ServerLevel) this.level, 2, s, (Component) object, this.level.getServer(), player);
+ }
+
+ @Override
+@@ -225,7 +329,7 @@
+
+ @Override
+ public AbstractContainerMenu createMenu(int containerId, Inventory playerInventory, Player player) {
+- return new LecternMenu(containerId, this.bookAccess, this.dataAccess);
++ return new LecternMenu(containerId, this.bookAccess, this.dataAccess, playerInventory); // CraftBukkit
+ }
+
+ @Override
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java.patch
new file mode 100644
index 0000000000..0412fb5179
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java.patch
@@ -0,0 +1,36 @@
+--- a/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java
+@@ -29,14 +32,18 @@
+ public SculkCatalystBlockEntity(BlockPos pos, BlockState 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 pos, BlockState state, SculkCatalystBlockEntity sculkCatalyst) {
++ 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
+ public void load(CompoundTag tag) {
++ super.load(tag); // CraftBukkit - SPIGOT-7393: Load super Bukkit data
+ this.catalystListener.sculkSpreader.load(tag);
+ }
+
+@@ -56,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-vineflower-stripped/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch
new file mode 100644
index 0000000000..ca6c1febb6
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java.patch
@@ -0,0 +1,73 @@
+--- a/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java
+@@ -30,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 {
+ public static final int COLUMNS = 9;
+@@ -49,7 +55,38 @@
+ @Nullable
+ private final DyeColor color;
+
+- public ShulkerBoxBlockEntity(@Nullable DyeColor color, BlockPos pos, BlockState 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.color = color;
+ }
+@@ -165,7 +206,8 @@
+ this.openCount = 0;
+ }
+
+- this.openCount++;
++ ++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(player, GameEvent.CONTAINER_OPEN, this.worldPosition);
+@@ -178,7 +220,8 @@
+ @Override
+ public void stopOpen(Player player) {
+ if (!this.remove && !player.isSpectator()) {
+- this.openCount--;
++ --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(player, GameEvent.CONTAINER_CLOSE, this.worldPosition);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/SignBlockEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/SignBlockEntity.java.patch
new file mode 100644
index 0000000000..91afbab194
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/SignBlockEntity.java.patch
@@ -0,0 +1,154 @@
+--- a/net/minecraft/world/level/block/entity/SignBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/SignBlockEntity.java
+@@ -29,8 +33,15 @@
+ 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;
+ private static final int TEXT_LINE_HEIGHT = 10;
+@@ -142,11 +177,14 @@
+
+ public void updateSignText(Player player, boolean isFrontText, List<FilteredText> filteredText) {
+ if (!this.isWaxed() && player.getUUID().equals(this.getPlayerWhoMayEdit()) && this.level != null) {
+- this.updateText(signText -> this.setMessages(player, filteredText, signText), isFrontText);
+- this.setAllowedPlayerEditor(null);
++ this.updateText((signtext) -> {
++ return this.setMessages(player, filteredText, signtext, isFrontText); // CraftBukkit
++ }, isFrontText);
++ this.setAllowedPlayerEditor((UUID) null);
+ this.level.sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), 3);
+ } else {
+- LOGGER.warn("Player {} just tried to change non-editable sign", player.getName().getString());
++ SignBlockEntity.LOGGER.warn("Player {} just tried to change non-editable sign", player.getName().getString());
++ ((ServerPlayer) player).connection.send(this.getUpdatePacket()); // CraftBukkit
+ }
+ }
+
+@@ -155,12 +194,14 @@
+ return this.setText(updater.apply(text), isFrontText);
+ }
+
+- private SignText setMessages(Player player, List<FilteredText> filteredText, SignText text) {
+- for (int i = 0; i < filteredText.size(); i++) {
+- FilteredText filteredText1 = filteredText.get(i);
+- Style style = text.getMessage(i, player.isTextFilteringEnabled()).getStyle();
+- if (player.isTextFilteringEnabled()) {
+- text = text.setMessage(i, Component.literal(filteredText1.filteredOrEmpty()).setStyle(style));
++ 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 chatmodifier = signtext.getMessage(i, entityhuman.isTextFilteringEnabled()).getStyle();
++
++ if (entityhuman.isTextFilteringEnabled()) {
++ signtext = signtext.setMessage(i, Component.literal(filteredtext.filteredOrEmpty()).setStyle(chatmodifier));
+ } else {
+ text = text.setMessage(
+ i, Component.literal(filteredText1.raw()).setStyle(style), Component.literal(filteredText1.filteredOrEmpty()).setStyle(style)
+@@ -168,7 +207,30 @@
+ }
+ }
+
+- return text;
++ // 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;
+ }
+
+ public boolean setText(SignText text, boolean isFrontText) {
+@@ -214,13 +280,40 @@
+ return flag;
+ }
+
+- private static CommandSourceStack createCommandSourceStack(@Nullable Player player, Level level, BlockPos pos) {
+- String string = player == null ? "Sign" : player.getName().getString();
+- Component component = (Component)(player == null ? Component.literal("Sign") : player.getDisplayName());
+- return new CommandSourceStack(CommandSource.NULL, Vec3.atCenterOf(pos), Vec2.ZERO, (ServerLevel)level, 2, string, component, level.getServer(), player);
++ // 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;
++ }
++
++ 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);
+ }
+@@ -241,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-vineflower-stripped/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch
new file mode 100644
index 0000000000..5faaf60c6f
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch
@@ -0,0 +1,28 @@
+--- a/net/minecraft/world/level/block/entity/SkullBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/SkullBlockEntity.java
+@@ -184,10 +189,21 @@
+ }
+ }
+
+- public static void resolveGameProfile(CompoundTag compoundTag) {
+- String string = compoundTag.getString("SkullOwner");
+- if (!Util.isBlank(string)) {
+- resolveGameProfile(compoundTag, string);
++ public static void resolveGameProfile(CompoundTag nbttagcompound) {
++ String s = nbttagcompound.getString("SkullOwner");
++
++ if (!Util.isBlank(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-vineflower-stripped/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java.patch
new file mode 100644
index 0000000000..2d316c42ef
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java.patch
@@ -0,0 +1,71 @@
+--- 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 {
+ private static final Logger LOGGER = LogUtils.getLogger();
+@@ -158,12 +173,14 @@
+ if (level instanceof ServerLevel && !blockEntity.isCoolingDown()) {
+ ServerLevel serverLevel = (ServerLevel)level;
+ blockEntity.teleportCooldown = 100;
+- if (blockEntity.exitPortal == null && level.dimension() == Level.END) {
+- BlockPos blockPos = findOrCreateValidTeleportPos(serverLevel, pos);
+- blockPos = blockPos.above(10);
+- LOGGER.debug("Creating portal at {}", blockPos);
+- spawnGatewayPortal(serverLevel, blockPos, EndGatewayConfiguration.knownExit(pos, false));
+- blockEntity.exitPortal = blockPos;
++ BlockPos blockposition1;
++
++ 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 (blockEntity.exitPortal != null) {
+@@ -185,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)blockPos.getX() + 0.5, (double)blockPos.getY(), (double)blockPos.getZ() + 0.5);
++ entity1.teleportToWithTicket(teleEvent.getTo().getX(), teleEvent.getTo().getY(), teleEvent.getTo().getZ());
++ // CraftBukkit end
+ }
+
+ triggerCooldown(level, pos, state, blockEntity);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch
new file mode 100644
index 0000000000..e4ab11fddd
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/piston/PistonBaseBlock.java.patch
@@ -0,0 +1,54 @@
+--- a/net/minecraft/world/level/block/piston/PistonBaseBlock.java
++++ b/net/minecraft/world/level/block/piston/PistonBaseBlock.java
+@@ -39,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 {
+ public static final MapCodec<PistonBaseBlock> CODEC = RecordCodecBuilder.mapCodec(
+@@ -143,7 +158,19 @@
+ i = 2;
+ }
+
+- level.blockEvent(pos, this, i, 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());
+ }
+ }
+
+@@ -303,6 +350,8 @@
+ BlockState[] blockStates = new BlockState[toPush.size() + toDestroy.size()];
+ Direction direction = extending ? facing : facing.getOpposite();
+ int i = 0;
++ // CraftBukkit start
++ final org.bukkit.block.Block bblock = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
+
+ for (int i1 = toDestroy.size() - 1; i1 >= 0; i1--) {
+ BlockPos blockPos2 = toDestroy.get(i1);
+@@ -328,6 +391,7 @@
+ level.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(blockPos2, blockState2, list.get(i1), facing, extending, false));
+ blockStates[i++] = blockState1;
+ }
++ // CraftBukkit end
+
+ if (extending) {
+ PistonType pistonType = this.isSticky ? PistonType.STICKY : PistonType.DEFAULT;
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/state/BlockBehaviour.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/state/BlockBehaviour.java.patch
new file mode 100644
index 0000000000..9179d48363
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/block/state/BlockBehaviour.java.patch
@@ -0,0 +1,28 @@
+--- a/net/minecraft/world/level/block/state/BlockBehaviour.java
++++ b/net/minecraft/world/level/block/state/BlockBehaviour.java
+@@ -175,15 +182,16 @@
+ if (!blockState.isAir() && explosion.getBlockInteraction() != Explosion.BlockInteraction.TRIGGER_BLOCK) {
+ Block block = blockState.getBlock();
+ boolean flag = explosion.getIndirectSourceEntity() instanceof Player;
+- if (block.dropFromExplosion(explosion) && level instanceof ServerLevel serverLevel) {
+- BlockEntity blockEntity = blockState.hasBlockEntity() ? level.getBlockEntity(blockPos) : null;
+- LootParams.Builder 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) {
+- builder.withParameter(LootContextParams.EXPLOSION_RADIUS, explosion.radius());
++
++ if (block.dropFromExplosion(explosion) && world instanceof ServerLevel) {
++ ServerLevel worldserver = (ServerLevel) world;
++ BlockEntity tileentity = iblockdata.hasBlockEntity() ? world.getBlockEntity(blockposition) : null;
++ LootParams.Builder lootparams_a = (new LootParams.Builder(worldserver)).withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(blockposition)).withParameter(LootContextParams.TOOL, ItemStack.EMPTY).withOptionalParameter(LootContextParams.BLOCK_ENTITY, tileentity).withOptionalParameter(LootContextParams.THIS_ENTITY, explosion.getDirectSourceEntity());
++
++ // 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-vineflower-stripped/net/minecraft/world/level/border/WorldBorder.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/border/WorldBorder.java.patch
new file mode 100644
index 0000000000..2a18eb1563
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -25,8 +27,9 @@
+ private double centerX;
+ private double centerZ;
+ int absoluteMaxSize = 29999984;
+- private WorldBorder.BorderExtent extent = new WorldBorder.StaticBorderExtent(5.999997E7F);
+- public static final WorldBorder.Settings DEFAULT_SETTINGS = new WorldBorder.Settings(0.0, 0.0, 0.2, 5.0, 5, 15, 5.999997E7F, 0L, 0.0);
++ private WorldBorder.a 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 boolean isWithinBounds(BlockPos pos) {
+ return (double)(pos.getX() + 1) > this.getMinX()
+@@ -154,6 +165,7 @@
+ }
+
+ public void addListener(BorderChangeListener listener) {
++ if (listeners.contains(listener)) return; // CraftBukkit
+ this.listeners.add(listener);
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/ChunkAccess.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/ChunkAccess.java.patch
new file mode 100644
index 0000000000..b8de6931b4
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/ChunkAccess.java.patch
@@ -0,0 +1,77 @@
+--- a/net/minecraft/world/level/chunk/ChunkAccess.java
++++ b/net/minecraft/world/level/chunk/ChunkAccess.java
+@@ -80,15 +82,12 @@
+ protected final LevelHeightAccessor levelHeightAccessor;
+ protected final LevelChunkSection[] sections;
+
+- public ChunkAccess(
+- ChunkPos chunkPos,
+- UpgradeData upgradeData,
+- LevelHeightAccessor levelHeightAccessor,
+- Registry<Biome> biomeRegistry,
+- long inhabitedTime,
+- @Nullable LevelChunkSection[] sections,
+- @Nullable BlendingData blendingData
+- ) {
++ // 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;
+@@ -106,7 +105,11 @@
+ }
+
+ replaceMissingSections(biomeRegistry, this.sections);
++ // CraftBukkit start
++ this.biomeRegistry = biomeRegistry;
+ }
++ public final Registry<Biome> biomeRegistry;
++ // CraftBukkit end
+
+ private static void replaceMissingSections(Registry<Biome> biomeRegistry, LevelChunkSection[] sections) {
+ for (int i = 0; i < sections.length; i++) {
+@@ -263,10 +272,11 @@
+
+ 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();
+@@ -430,6 +452,27 @@
+ }
+ }
+
++ // 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 resolver, Climate.Sampler sampler) {
+ ChunkPos pos = this.getPos();
+ int i = QuartPos.fromBlock(pos.getMinBlockX());
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/ChunkGenerator.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/ChunkGenerator.java.patch
new file mode 100644
index 0000000000..6e1646322f
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/ChunkGenerator.java.patch
@@ -0,0 +1,104 @@
+--- a/net/minecraft/world/level/chunk/ChunkGenerator.java
++++ b/net/minecraft/world/level/chunk/ChunkGenerator.java
+@@ -314,19 +306,8 @@
+ }
+ }
+
+- public void applyBiomeDecoration(WorldGenLevel level, ChunkAccess chunk, StructureManager structureManager) {
+- ChunkPos pos = chunk.getPos();
+- if (!SharedConstants.debugVoidTerrain(pos)) {
+- SectionPos sectionPos = SectionPos.of(pos, level.getMinSection());
+- BlockPos blockPos = sectionPos.origin();
+- Registry<Structure> registry = level.registryAccess().registryOrThrow(Registries.STRUCTURE);
+- Map<Integer, List<Structure>> map = registry.stream().collect(Collectors.groupingBy(structure1 -> structure1.step().ordinal()));
+- List<FeatureSorter.StepFeatureData> list = this.featuresPerStep.get();
+- WorldgenRandom worldgenRandom = new WorldgenRandom(new XoroshiroRandomSource(RandomSupport.generateUniqueSeed()));
+- long l = worldgenRandom.setDecorationSeed(level.getSeed(), blockPos.getX(), blockPos.getZ());
+- Set<Holder<Biome>> set = new ObjectArraySet<>();
+- ChunkPos.rangeClosed(sectionPos.chunk(), 1).forEach(chunkPos -> {
+- ChunkAccess chunk1 = level.getChunk(chunkPos.x, chunkPos.z);
++ public void addVanillaDecorations(WorldGenLevel generatoraccessseed, ChunkAccess ichunkaccess, StructureManager structuremanager) { // CraftBukkit
++ ChunkPos chunkcoordintpair = ichunkaccess.getPos();
+
+ for (LevelChunkSection levelChunkSection : chunk1.getSections()) {
+ levelChunkSection.getBiomes().getAll(set::add);
+@@ -410,6 +439,33 @@
+ }
+ }
+
++ // 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 pos = chunk.getPos();
+ int minBlockX = pos.getMinBlockX();
+@@ -545,26 +593,25 @@
+ );
+ }
+
+- private boolean tryGenerateStructure(
+- StructureSet.StructureSelectionEntry structureSelectionEntry,
+- StructureManager structureManager,
+- RegistryAccess registryAccess,
+- RandomState random,
+- StructureTemplateManager structureTemplateManager,
+- long seed,
+- ChunkAccess chunk,
+- ChunkPos chunkPos,
+- SectionPos sectionPos
+- ) {
+- Structure structure = structureSelectionEntry.structure().value();
+- int i = fetchReferences(structureManager, chunk, sectionPos, structure);
+- HolderSet<Biome> holderSet = structure.biomes();
+- Predicate<Holder<Biome>> predicate = holderSet::contains;
+- StructureStart structureStart = structure.generate(
+- registryAccess, this, this.biomeSource, random, structureTemplateManager, seed, chunkPos, i, chunk, predicate
+- );
+- if (structureStart.isValid()) {
+- structureManager.setStartForStructure(sectionPos, structure, structureStart, chunk);
++ private boolean tryGenerateStructure(StructureSet.a structureSelectionEntry, StructureManager structureManager, RegistryAccess registryAccess, RandomState random, StructureTemplateManager structureTemplateManager, long seed, ChunkAccess ichunkaccess, ChunkPos chunk, SectionPos chunkPos) {
++ Structure structure = (Structure) structureSelectionEntry.structure().value();
++ int j = fetchReferences(structureManager, ichunkaccess, chunkPos, structure);
++ HolderSet<Biome> holderset = structure.biomes();
++
++ Objects.requireNonNull(holderset);
++ Predicate<Holder<Biome>> predicate = holderset::contains;
++ StructureStart structurestart = structure.generate(registryAccess, this, this.biomeSource, random, structureTemplateManager, seed, chunk, j, ichunkaccess, predicate);
++
++ if (structurestart.isValid()) {
++ // 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-vineflower-stripped/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/ChunkStatus.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/ChunkStatus.java.patch
new file mode 100644
index 0000000000..0127726c74
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/ChunkStatus.java.patch
@@ -0,0 +1,26 @@
+--- a/net/minecraft/world/level/chunk/ChunkStatus.java
++++ b/net/minecraft/world/level/chunk/ChunkStatus.java
+@@ -31,14 +32,15 @@
+ public class ChunkStatus {
+ public static final int MAX_STRUCTURE_DISTANCE = 8;
+ private static final EnumSet<Heightmap.Types> PRE_FEATURES = EnumSet.of(Heightmap.Types.OCEAN_FLOOR_WG, Heightmap.Types.WORLD_SURFACE_WG);
+- public static final EnumSet<Heightmap.Types> POST_FEATURES = EnumSet.of(
+- Heightmap.Types.OCEAN_FLOOR, Heightmap.Types.WORLD_SURFACE, Heightmap.Types.MOTION_BLOCKING, Heightmap.Types.MOTION_BLOCKING_NO_LEAVES
+- );
+- private static final ChunkStatus.LoadingTask PASSTHROUGH_LOAD_TASK = (status, level, structureTemplateManager, lightEngine, task, chunk) -> CompletableFuture.completedFuture(
+- Either.left(chunk)
+- );
+- public static final ChunkStatus EMPTY = registerSimple(
+- "empty", null, -1, PRE_FEATURES, ChunkStatus.ChunkType.PROTOCHUNK, (status, level, chunkGenerator, neighboringChunks, loadingChunk) -> {
++ public static final EnumSet<Heightmap.Types> POST_FEATURES = EnumSet.of(Heightmap.Types.OCEAN_FLOOR, Heightmap.Types.WORLD_SURFACE, Heightmap.Types.MOTION_BLOCKING, Heightmap.Types.MOTION_BLOCKING_NO_LEAVES);
++ private static final ChunkStatus.LoadingTask PASSTHROUGH_LOAD_TASK = (chunkstatus, worldserver, structuretemplatemanager, lightenginethreaded, function, ichunkaccess) -> {
++ return CompletableFuture.completedFuture(Either.left(ichunkaccess));
++ };
++ public static final ChunkStatus EMPTY = registerSimple("empty", (ChunkStatus) null, -1, ChunkStatus.PRE_FEATURES, ChunkStatus.Type.PROTOCHUNK, (chunkstatus, worldserver, chunkgenerator, list, ichunkaccess) -> {
++ });
++ 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);
+ }
+ );
+ public static final ChunkStatus STRUCTURE_STARTS = register(
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/DataLayer.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/DataLayer.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/DataLayer.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/LevelChunk.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/LevelChunk.java.patch
new file mode 100644
index 0000000000..5cda59a305
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/LevelChunk.java.patch
@@ -0,0 +1,235 @@
+--- a/net/minecraft/world/level/chunk/LevelChunk.java
++++ b/net/minecraft/world/level/chunk/LevelChunk.java
+@@ -70,9 +73,9 @@
+ return "<null>";
+ }
+ };
+- private final Map<BlockPos, LevelChunk.RebindableTickingBlockEntityWrapper> tickersInLevel = Maps.newHashMap();
+- private boolean loaded;
+- final Level level;
++ private final Map<BlockPos, LevelChunk.RebindableTickingBlockEntityWrapper> tickersInLevel;
++ public boolean loaded;
++ public final ServerLevel level; // CraftBukkit - type
+ @Nullable
+ private Supplier<FullChunkStatus> fullStatus;
+ @Nullable
+@@ -85,20 +88,13 @@
+ this(level, pos, UpgradeData.EMPTY, new LevelChunkTicks<>(), new LevelChunkTicks<>(), 0L, null, null, null);
+ }
+
+- public LevelChunk(
+- Level level,
+- ChunkPos pos,
+- UpgradeData data,
+- LevelChunkTicks<Block> blockTicks,
+- LevelChunkTicks<Fluid> fluidTicks,
+- long inhabitedTime,
+- @Nullable LevelChunkSection[] sections,
+- @Nullable LevelChunk.PostLoadProcessor postLoad,
+- @Nullable BlendingData blendingData
+- ) {
+- super(pos, data, level, level.registryAccess().registryOrThrow(Registries.BIOME), inhabitedTime, sections, blendingData);
+- this.level = level;
+- this.gameEventListenerRegistrySections = new Int2ObjectOpenHashMap<>();
++ public LevelChunk(Level level, ChunkPos pos, UpgradeData data, LevelChunkTicks<Block> blockTicks, LevelChunkTicks<Fluid> fluidTicks, long inhabitedTime, @Nullable LevelChunkSection[] achunksection, @Nullable LevelChunk.PostLoadProcessor sections, @Nullable BlendingData postLoad) {
++ super(pos, data, level, level.registryAccess().registryOrThrow(Registries.BIOME), inhabitedTime, achunksection, postLoad);
++ this.tickersInLevel = Maps.newHashMap();
++ this.level = (ServerLevel) level; // CraftBukkit - type
++ this.gameEventListenerRegistrySections = new Int2ObjectOpenHashMap();
++ Heightmap.Types[] aheightmap_type = Heightmap.Types.values();
++ int j = aheightmap_type.length;
+
+ for (Heightmap.Types types : Heightmap.Types.values()) {
+ if (ChunkStatus.FULL.heightmapsAfter().contains(types)) {
+@@ -111,6 +109,11 @@
+ this.fluidTicks = fluidTicks;
+ }
+
++ // CraftBukkit start
++ public boolean mustNotSave;
++ public boolean needsDecoration;
++ // CraftBukkit end
++
+ public LevelChunk(ServerLevel level, ProtoChunk chunk, @Nullable LevelChunk.PostLoadProcessor postLoad) {
+ this(
+ level,
+@@ -146,6 +145,10 @@
+ this.skyLightSources = chunk.skyLightSources;
+ this.setLightCorrect(chunk.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
+@@ -231,13 +253,21 @@
+ }
+ }
+
++ // CraftBukkit start
+ @Nullable
+ @Override
+- public BlockState setBlockState(BlockPos pos, BlockState state, boolean isMoving) {
+- int y = pos.getY();
+- LevelChunkSection section = this.getSection(this.getSectionIndex(y));
+- boolean hasOnlyAir = section.hasOnlyAir();
+- if (hasOnlyAir && state.isAir()) {
++ public IBlockData setBlockState(BlockPos pos, IBlockData state, boolean isMoving) {
++ return this.setBlockState(pos, state, isMoving, true);
++ }
++
++ @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 i = pos.getX() & 15;
+@@ -276,8 +311,9 @@
+ if (!section.getBlockState(i, i1, i2).is(block)) {
+ return null;
+ } else {
+- if (!this.level.isClientSide) {
+- state.onPlace(this.level, pos, blockState, isMoving);
++ // 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 (state.hasBlockEntity()) {
+@@ -318,14 +356,22 @@
+ }
+
+ @Nullable
+- public BlockEntity getBlockEntity(BlockPos pos, LevelChunk.EntityCreationType creationType) {
+- BlockEntity blockEntity = this.blockEntities.get(pos);
+- if (blockEntity == null) {
+- CompoundTag compoundTag = this.pendingBlockEntities.remove(pos);
+- if (compoundTag != null) {
+- BlockEntity blockEntity1 = this.promotePendingBlockEntity(pos, compoundTag);
+- if (blockEntity1 != null) {
+- return blockEntity1;
++ 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 (tileentity == null) {
++ CompoundTag nbttagcompound = (CompoundTag) this.pendingBlockEntities.remove(pos);
++
++ if (nbttagcompound != null) {
++ BlockEntity tileentity1 = this.promotePendingBlockEntity(pos, nbttagcompound);
++
++ if (tileentity1 != null) {
++ return tileentity1;
+ }
+ }
+ }
+@@ -378,6 +439,14 @@
+ if (blockEntity1 != null && blockEntity1 != blockEntity) {
+ 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
+ }
+ }
+
+@@ -403,10 +474,21 @@
+ @Override
+ public void removeBlockEntity(BlockPos pos) {
+ if (this.isInLevel()) {
+- BlockEntity blockEntity = this.blockEntities.remove(pos);
+- if (blockEntity != null) {
+- if (this.level instanceof ServerLevel serverLevel) {
+- this.removeGameEventListener(blockEntity, serverLevel);
++ BlockEntity tileentity = (BlockEntity) this.blockEntities.remove(pos);
++
++ // CraftBukkit start - SPIGOT-5561: Also remove from pending map
++ if (!pendingBlockEntities.isEmpty()) {
++ pendingBlockEntities.remove(pos);
++ }
++ // CraftBukkit end
++
++ if (tileentity != null) {
++ Level world = this.level;
++
++ if (world instanceof ServerLevel) {
++ ServerLevel worldserver = (ServerLevel) world;
++
++ this.removeGameEventListener(tileentity, worldserver);
+ }
+
+ blockEntity.setRemoved();
+@@ -446,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-vineflower-stripped/net/minecraft/world/level/chunk/LevelChunkSection.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/LevelChunkSection.java.patch
new file mode 100644
index 0000000000..704eb84a2a
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/LevelChunkSection.java.patch
@@ -0,0 +1,35 @@
+--- a/net/minecraft/world/level/chunk/LevelChunkSection.java
++++ b/net/minecraft/world/level/chunk/LevelChunkSection.java
+@@ -21,12 +22,14 @@
+ 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> states, PalettedContainerRO<Holder<Biome>> biomes) {
+- this.states = states;
+- this.biomes = biomes;
++ public LevelChunkSection(PalettedContainer<IBlockData> datapaletteblock, PalettedContainer<Holder<Biome>> palettedcontainerro) {
++ // CraftBukkit end
++ this.states = datapaletteblock;
++ this.biomes = palettedcontainerro;
+ this.recalcBlockCounts();
+ }
+
+@@ -180,6 +190,12 @@
+ return this.biomes.get(x, y, z);
+ }
+
++ // 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>> palettedContainer = this.biomes.recreate();
+ int i = 4;
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/storage/ChunkSerializer.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/storage/ChunkSerializer.java.patch
new file mode 100644
index 0000000000..13e6943d3a
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/storage/ChunkSerializer.java.patch
@@ -0,0 +1,117 @@
+--- a/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
++++ b/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
+@@ -86,17 +93,17 @@
+ LOGGER.error("Chunk file at {} is in the wrong location; relocating. (Expected {}, got {})", pos, pos, chunkPos);
+ }
+
+- UpgradeData upgradeData = tag.contains("UpgradeData", 10) ? new UpgradeData(tag.getCompound("UpgradeData"), level) : UpgradeData.EMPTY;
+- boolean _boolean = tag.getBoolean("isLightOn");
+- ListTag list = tag.getList("sections", 10);
+- int sectionsCount = level.getSectionsCount();
+- LevelChunkSection[] levelChunkSections = new LevelChunkSection[sectionsCount];
+- boolean hasSkyLight = level.dimensionType().hasSkyLight();
+- ChunkSource chunkSource = level.getChunkSource();
+- LevelLightEngine lightEngine = chunkSource.getLightEngine();
+- Registry<Biome> registry = level.registryAccess().registryOrThrow(Registries.BIOME);
+- Codec<PalettedContainerRO<Holder<Biome>>> codec = makeBiomeCodec(registry);
+- boolean flag = false;
++ 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;
+
+ for (int i = 0; i < list.size(); i++) {
+ CompoundTag compound = list.getCompound(i);
+@@ -114,21 +127,27 @@
+ );
+ }
+
+- PalettedContainerRO<Holder<Biome>> palettedContainerRO;
+- if (compound.contains("biomes", 10)) {
+- palettedContainerRO = codec.parse(NbtOps.INSTANCE, compound.getCompound("biomes"))
+- .promotePartial(errorMessage -> logErrors(pos, _byte, errorMessage))
+- .getOrThrow(false, LOGGER::error);
++ PalettedContainer object; // CraftBukkit - read/write
++
++ if (nbttagcompound1.contains("biomes", 10)) {
++ dataresult = codec.parse(NbtOps.INSTANCE, nbttagcompound1.getCompound("biomes")).promotePartial((s) -> {
++ logErrors(pos, b0, s);
++ });
++ logger = ChunkSerializer.LOGGER;
++ Objects.requireNonNull(logger);
++ object = ((DataResult<PalettedContainer<Holder<Biome>>>) dataresult).getOrThrow(false, logger::error); // CraftBukkit - decompile error
+ } else {
+ palettedContainerRO = new PalettedContainer<>(
+ registry.asHolderIdMap(), registry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES
+ );
+ }
+
+- LevelChunkSection levelChunkSection = new LevelChunkSection(palettedContainer, palettedContainerRO);
+- levelChunkSections[sectionIndexFromSectionY] = levelChunkSection;
+- SectionPos sectionPos = SectionPos.of(pos, _byte);
+- poiManager.checkConsistencyWithBlocks(sectionPos, levelChunkSection);
++ LevelChunkSection chunksection = new LevelChunkSection(datapaletteblock, (PalettedContainer) object); // CraftBukkit - read/write
++
++ achunksection[k] = chunksection;
++ SectionPos sectionposition = SectionPos.of(pos, b0);
++
++ poiManager.checkConsistencyWithBlocks(sectionposition, chunksection);
+ }
+
+ boolean flag1 = compound.contains("BlockLight", 7);
+@@ -196,9 +221,12 @@
+ }
+ }
+
+- chunkAccess.setLightCorrect(_boolean);
+- CompoundTag compound1 = tag.getCompound("Heightmaps");
+- EnumSet<Heightmap.Types> set = EnumSet.noneOf(Heightmap.Types.class);
++ // 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
+
+ for (Heightmap.Types types : chunkAccess.getStatus().heightmapsAfter()) {
+ String serializationKey = types.getSerializationKey();
+@@ -268,6 +307,12 @@
+ );
+ }
+
++ // 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
++
+ public static CompoundTag write(ServerLevel level, ChunkAccess chunk) {
+ ChunkPos pos = chunk.getPos();
+ CompoundTag compoundTag = NbtUtils.addCurrentDataVersion(new CompoundTag());
+@@ -377,11 +452,14 @@
+ }
+ }
+
+- compoundTag.put("Heightmaps", compoundTag2);
+- compoundTag.put(
+- "structures", packStructureData(StructurePieceSerializationContext.fromLevel(level), pos, chunk.getAllStarts(), chunk.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 level, CompoundTag tag, ChunkAccess.TicksToSave ticksToSave) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch
new file mode 100644
index 0000000000..71479e6a9a
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/storage/ChunkStorage.java.patch
@@ -0,0 +1,110 @@
+--- 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;
+
+@@ -35,18 +42,13 @@
+ return this.worker.isOldChunkAround(pos, radius);
+ }
+
+- public CompoundTag upgradeChunkTag(
+- ResourceKey<Level> levelKey,
+- Supplier<DimensionDataStorage> storage,
+- CompoundTag chunkData,
+- Optional<ResourceKey<Codec<? extends ChunkGenerator>>> chunkGeneratorKey
+- ) {
+- int version = getVersion(chunkData);
+- if (version < 1493) {
+- chunkData = DataFixTypes.CHUNK.update(this.fixerUpper, chunkData, version, 1493);
+- if (chunkData.getCompound("Level").getBoolean("hasLegacyStructureData")) {
+- LegacyStructureDataHandler legacyStructureHandler = this.getLegacyStructureHandler(levelKey, storage);
+- chunkData = legacyStructureHandler.updateFromLegacy(chunkData);
++ // 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;
+ }
+ }
+
+@@ -60,9 +73,45 @@
+ return var7;
+ }
+
+- private LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey<Level> level, Supplier<DimensionDataStorage> storage) {
+- LegacyStructureDataHandler legacyStructureDataHandler = this.legacyStructureHandler;
+- if (legacyStructureDataHandler == null) {
++ 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) {
++ nbttagcompound = DataFixTypes.CHUNK.update(this.fixerUpper, nbttagcompound, i, 1493);
++ if (nbttagcompound.getCompound("Level").getBoolean("hasLegacyStructureData")) {
++ LegacyStructureDataHandler persistentstructurelegacy = this.getLegacyStructureHandler(resourcekey, supplier);
++
++ nbttagcompound = persistentstructurelegacy.updateFromLegacy(nbttagcompound);
++ }
++ }
++
++ injectDatafixingContext(nbttagcompound, resourcekey, optional);
++ nbttagcompound = DataFixTypes.CHUNK.updateToCurrentVersion(this.fixerUpper, nbttagcompound, Math.max(1493, i));
++ if (i < SharedConstants.getCurrentVersion().getDataVersion().getVersion()) {
++ NbtUtils.addCurrentDataVersion(nbttagcompound);
++ }
++
++ nbttagcompound.remove("__context");
++ return nbttagcompound;
++ }
++
++ private LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey<LevelStem> level, Supplier<DimensionDataStorage> storage) { // CraftBukkit
++ LegacyStructureDataHandler persistentstructurelegacy = this.legacyStructureHandler;
++
++ if (persistentstructurelegacy == null) {
+ synchronized (this) {
+ legacyStructureDataHandler = this.legacyStructureHandler;
+ if (legacyStructureDataHandler == null) {
+@@ -74,13 +123,14 @@
+ return legacyStructureDataHandler;
+ }
+
+- public static void injectDatafixingContext(
+- CompoundTag chunkData, ResourceKey<Level> levelKey, Optional<ResourceKey<Codec<? extends ChunkGenerator>>> chunkGeneratorKey
+- ) {
+- CompoundTag compoundTag = new CompoundTag();
+- compoundTag.putString("dimension", levelKey.location().toString());
+- chunkGeneratorKey.ifPresent(generator -> compoundTag.putString("generator", generator.location().toString()));
+- chunkData.put("__context", compoundTag);
++ public static void injectDatafixingContext(CompoundTag chunkData, ResourceKey<LevelStem> levelKey, Optional<ResourceKey<Codec<? extends ChunkGenerator>>> chunkGeneratorKey) { // CraftBukkit
++ CompoundTag nbttagcompound1 = new CompoundTag();
++
++ nbttagcompound1.putString("dimension", levelKey.location().toString());
++ chunkGeneratorKey.ifPresent((resourcekey1) -> {
++ nbttagcompound1.putString("generator", resourcekey1.location().toString());
++ });
++ chunkData.put("__context", nbttagcompound1);
+ }
+
+ public static int getVersion(CompoundTag chunkData) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/storage/RegionFile.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/storage/RegionFile.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/storage/RegionFile.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch
new file mode 100644
index 0000000000..637d17ecdc
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/chunk/storage/RegionFileStorage.java.patch
@@ -0,0 +1,76 @@
+--- a/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
++++ b/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
+@@ -26,27 +30,38 @@
+ this.sync = sync;
+ }
+
+- private RegionFile getRegionFile(ChunkPos chunkPos) throws IOException {
+- long _long = ChunkPos.asLong(chunkPos.getRegionX(), chunkPos.getRegionZ());
+- RegionFile regionFile = this.regionCache.getAndMoveToFirst(_long);
+- if (regionFile != null) {
+- return regionFile;
++ 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) {
++ return regionfile;
+ } else {
+ if (this.regionCache.size() >= 256) {
+ this.regionCache.removeLast().close();
+ }
+
+ FileUtil.createDirectoriesSafe(this.folder);
+- Path path = this.folder.resolve("r." + chunkPos.getRegionX() + "." + chunkPos.getRegionZ() + ".mca");
+- RegionFile regionFile1 = new RegionFile(path, this.folder, this.sync);
+- this.regionCache.putAndMoveToFirst(_long, regionFile1);
+- return regionFile1;
++ Path path = this.folder;
++ int j = chunkcoordintpair.getRegionX();
++ Path path1 = path.resolve("r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca");
++ if (existingOnly && !java.nio.file.Files.exists(path1)) return null; // CraftBukkit
++ RegionFile regionfile1 = new RegionFile(path1, this.folder, this.sync);
++
++ this.regionCache.putAndMoveToFirst(i, regionfile1);
++ return regionfile1;
+ }
+ }
+
+ @Nullable
+ public CompoundTag read(ChunkPos chunkPos) throws IOException {
+- RegionFile regionFile = this.getRegionFile(chunkPos);
++ // 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 var4;
+ try (DataInputStream chunkDataInputStream = regionFile.getChunkDataInputStream(chunkPos)) {
+@@ -61,7 +100,13 @@
+ }
+
+ public void scanChunk(ChunkPos chunkPos, StreamTagVisitor visitor) throws IOException {
+- RegionFile regionFile = this.getRegionFile(chunkPos);
++ // 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 (DataInputStream chunkDataInputStream = regionFile.getChunkDataInputStream(chunkPos)) {
+ if (chunkDataInputStream != null) {
+@@ -71,7 +131,8 @@
+ }
+
+ protected void write(ChunkPos chunkPos, @Nullable CompoundTag chunkData) throws IOException {
+- RegionFile regionFile = this.getRegionFile(chunkPos);
++ RegionFile regionfile = this.getRegionFile(chunkPos, false); // CraftBukkit
++
+ if (chunkData == null) {
+ regionFile.clear(chunkPos);
+ } else {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch
new file mode 100644
index 0000000000..466735910a
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/dimension/end/EndDragonFight.java.patch
@@ -0,0 +1,55 @@
+--- a/net/minecraft/world/level/dimension/end/EndDragonFight.java
++++ b/net/minecraft/world/level/dimension/end/EndDragonFight.java
+@@ -476,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;
+ if (blockPos == null) {
+@@ -495,21 +532,24 @@
+ List<EndCrystal> list = Lists.newArrayList();
+ BlockPos blockPos1 = blockPos.above(1);
+
+- for (Direction direction : Direction.Plane.HORIZONTAL) {
+- List<EndCrystal> entitiesOfClass = this.level.getEntitiesOfClass(EndCrystal.class, new AABB(blockPos1.relative(direction, 2)));
+- if (entitiesOfClass.isEmpty()) {
+- return;
++ while (iterator.hasNext()) {
++ Direction enumdirection = (Direction) iterator.next();
++ List<EndCrystal> list1 = this.level.getEntitiesOfClass(EndCrystal.class, new AABB(blockposition1.relative(enumdirection, 2)));
++
++ if (list1.isEmpty()) {
++ return false; // CraftBukkit - return value
+ }
+
+ list.addAll(entitiesOfClass);
+ }
+
+- LOGGER.debug("Found all crystals, respawning dragon.");
+- this.respawnDragon(list);
++ EndDragonFight.LOGGER.debug("Found all crystals, respawning dragon.");
++ return this.respawnDragon(list); // CraftBukkit - return value
+ }
++ return false; // CraftBukkit - return value
+ }
+
+- private void respawnDragon(List<EndCrystal> crystals) {
++ public boolean respawnDragon(List<EndCrystal> list) { // CraftBukkit - return boolean
+ if (this.dragonKilled && this.respawnStage == null) {
+ for (BlockPattern.BlockPatternMatch blockPatternMatch = this.findExitPortal(); blockPatternMatch != null; blockPatternMatch = this.findExitPortal()) {
+ for (int i = 0; i < this.exitPortalPattern.getWidth(); i++) {
+@@ -527,8 +568,10 @@
+ this.respawnStage = DragonRespawnAnimation.START;
+ this.respawnTime = 0;
+ this.spawnExitPortal(false);
+- this.respawnCrystals = crystals;
++ this.respawnCrystals = list;
++ return true; // CraftBukkit - return value
+ }
++ return false; // CraftBukkit - return value
+ }
+
+ public void resetSpikeCrystals() {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/entity/PersistentEntitySectionManager.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/entity/PersistentEntitySectionManager.java.patch
new file mode 100644
index 0000000000..c646ec5ff8
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/entity/PersistentEntitySectionManager.java.patch
@@ -0,0 +1,130 @@
+--- a/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
++++ b/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
+@@ -28,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 {
+ static final Logger LOGGER = LogUtils.getLogger();
+@@ -52,8 +59,18 @@
+ this.entityGetter = new LevelEntityGetterAdapter<>(this.visibleEntityStorage, this.sectionStorage);
+ }
+
+- void removeSectionIfEmpty(long sectionKey, EntitySection<T> section) {
+- if (section.isEmpty()) {
++ // 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(sectionKey);
+ }
+ }
+@@ -171,9 +209,16 @@
+ }
+ }
+
+- private boolean storeChunkSections(long chunkPosValue, Consumer<T> entityAction) {
+- PersistentEntitySectionManager.ChunkLoadStatus chunkLoadStatus = this.chunkLoadStatuses.get(chunkPosValue);
+- if (chunkLoadStatus == PersistentEntitySectionManager.ChunkLoadStatus.PENDING) {
++ private boolean storeChunkSections(long chunkPosValue, Consumer<T> consumer) {
++ // CraftBukkit start - add boolean for event call
++ return storeChunkSections(chunkPosValue, consumer, false);
++ }
++
++ 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 = this.sectionStorage
+@@ -181,8 +226,9 @@
+ .flatMap(entitySection -> entitySection.getEntities().filter(EntityAccess::shouldBeSaved))
+ .collect(Collectors.toList());
+ if (list.isEmpty()) {
+- if (chunkLoadStatus == PersistentEntitySectionManager.ChunkLoadStatus.LOADED) {
+- this.permanentStorage.storeEntities(new ChunkEntities<>(new ChunkPos(chunkPosValue), ImmutableList.of()));
++ 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()));
+ }
+
+ return true;
+@@ -190,8 +236,9 @@
+ this.requestChunkLoad(chunkPosValue);
+ return false;
+ } else {
+- this.permanentStorage.storeEntities(new ChunkEntities<>(new ChunkPos(chunkPosValue), list));
+- list.forEach(entityAction);
++ 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;
+ }
+ }
+@@ -207,7 +258,10 @@
+ }
+
+ private boolean processChunkUnload(long chunkPosValue) {
+- boolean flag = this.storeChunkSections(chunkPosValue, entity -> entity.getPassengersAndSelf().forEach(this::unloadEntity));
++ boolean flag = this.storeChunkSections(chunkPosValue, (entityaccess) -> {
++ entityaccess.getPassengersAndSelf().forEach(this::unloadEntity);
++ }, true); // CraftBukkit - add boolean for event call
++
+ if (!flag) {
+ return false;
+ } else {
+@@ -227,10 +282,17 @@
+ }
+
+ private void processPendingLoads() {
+- ChunkEntities<T> chunkEntities;
+- while ((chunkEntities = this.loadingInbox.poll()) != null) {
+- chunkEntities.getEntities().forEach(entity -> this.addEntity((T)entity, true));
+- this.chunkLoadStatuses.put(chunkEntities.getPos().toLong(), PersistentEntitySectionManager.ChunkLoadStatus.LOADED);
++ ChunkEntities<T> chunkentities; // CraftBukkit - decompile error
++
++ while ((chunkentities = (ChunkEntities) this.loadingInbox.poll()) != null) {
++ chunkentities.getEntities().forEach((entityaccess) -> {
++ this.addEntity(entityaccess, true);
++ });
++ 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
+ }
+ }
+
+@@ -281,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-vineflower-stripped/net/minecraft/world/level/gameevent/GameEventDispatcher.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/gameevent/GameEventDispatcher.java.patch
new file mode 100644
index 0000000000..cf7dcc844a
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/gameevent/GameEventDispatcher.java.patch
@@ -0,0 +1,54 @@
+--- a/net/minecraft/world/level/gameevent/GameEventDispatcher.java
++++ b/net/minecraft/world/level/gameevent/GameEventDispatcher.java
+@@ -9,6 +10,12 @@
+ import net.minecraft.server.level.ServerLevel;
+ import net.minecraft.world.level.chunk.ChunkAccess;
+ 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 {
+ private final ServerLevel level;
+@@ -18,18 +26,26 @@
+ }
+
+ public void post(GameEvent event, Vec3 pos, GameEvent.Context context) {
+- int notificationRadius = event.getNotificationRadius();
+- BlockPos blockPos = BlockPos.containing(pos);
+- int i = SectionPos.blockToSectionCoord(blockPos.getX() - notificationRadius);
+- int i1 = SectionPos.blockToSectionCoord(blockPos.getY() - notificationRadius);
+- int i2 = SectionPos.blockToSectionCoord(blockPos.getZ() - notificationRadius);
+- int i3 = SectionPos.blockToSectionCoord(blockPos.getX() + notificationRadius);
+- int i4 = SectionPos.blockToSectionCoord(blockPos.getY() + notificationRadius);
+- int i5 = SectionPos.blockToSectionCoord(blockPos.getZ() + notificationRadius);
+- List<GameEvent.ListenerInfo> list = new ArrayList<>();
+- GameEventListenerRegistry.ListenerVisitor listenerVisitor = (listener, pos1) -> {
+- if (listener.getDeliveryMode() == GameEventListener.DeliveryMode.BY_DISTANCE) {
+- list.add(new GameEvent.ListenerInfo(event, pos, context, listener, pos1));
++ 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_a = (gameeventlistener, vec3d1) -> {
++ if (gameeventlistener.getDeliveryMode() == GameEventListener.a.BY_DISTANCE) {
++ list.add(new GameEvent.ListenerInfo(event, pos, context, gameeventlistener, vec3d1));
+ } else {
+ listener.handleGameEvent(this.level, event, context, pos);
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java.patch
new file mode 100644
index 0000000000..f1fabc54f0
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java.patch
@@ -0,0 +1,38 @@
+--- a/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java
++++ b/net/minecraft/world/level/gameevent/vibrations/VibrationSystem.java
+@@ -29,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 {
+ GameEvent[] RESONANCE_EVENTS = new GameEvent[]{
+@@ -389,11 +284,18 @@
+ return false;
+ }
+
+- if (entity.isSteppingCarefully() && gameEvent.is(GameEventTags.IGNORE_VIBRATIONS_SNEAKING)) {
+- if (this.canTriggerAvoidVibration() && entity instanceof ServerPlayer serverPlayer) {
+- CriteriaTriggers.AVOID_VIBRATION.trigger(serverPlayer);
+- }
+-
++ if (optional.isEmpty()) {
++ return false;
++ } else {
++ 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;
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/FlatLevelSource.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/FlatLevelSource.java.patch
new file mode 100644
index 0000000000..c8d944bd9f
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/FlatLevelSource.java.patch
@@ -0,0 +1,24 @@
+--- a/net/minecraft/world/level/levelgen/FlatLevelSource.java
++++ b/net/minecraft/world/level/levelgen/FlatLevelSource.java
+@@ -35,10 +35,19 @@
+ private final FlatLevelGeneratorSettings settings;
+
+ public FlatLevelSource(FlatLevelGeneratorSettings settings) {
+- super(new FixedBiomeSource(settings.getBiome()), Util.memoize(settings::adjustGenerationSettings));
+- this.settings = settings;
++ // CraftBukkit start
++ // WorldChunkManagerHell worldchunkmanagerhell = new WorldChunkManagerHell(generatorsettingsflat.getBiome());
++
++ // Objects.requireNonNull(generatorsettingsflat);
++ this(settings, new FixedBiomeSource(settings.getBiome()));
+ }
+
++ public FlatLevelSource(FlatLevelGeneratorSettings generatorsettingsflat, net.minecraft.world.level.biome.BiomeSource worldchunkmanager) {
++ super(worldchunkmanager, Util.memoize(generatorsettingsflat::adjustGenerationSettings));
++ // CraftBukkit end
++ this.settings = generatorsettingsflat;
++ }
++
+ @Override
+ public ChunkGeneratorStructureState createState(HolderLookup<StructureSet> structureSetLookup, RandomState randomState, long seed) {
+ Stream<Holder<StructureSet>> stream = this.settings
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java.patch
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java.patch
@@ -0,0 +1 @@
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/PatrolSpawner.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/PatrolSpawner.java.patch
new file mode 100644
index 0000000000..820b8fc893
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -102,9 +115,9 @@
+ patrollingMonster.findPatrolTarget();
+ }
+
+- patrollingMonster.setPos((double)pos.getX(), (double)pos.getY(), (double)pos.getZ());
+- patrollingMonster.finalizeSpawn(level, level.getCurrentDifficultyAt(pos), MobSpawnType.PATROL, null, null);
+- level.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-vineflower-stripped/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java.patch
new file mode 100644
index 0000000000..e6492444e1
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java.patch
@@ -0,0 +1,38 @@
+--- a/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java
++++ b/net/minecraft/world/level/levelgen/structure/LegacyStructureDataHandler.java
+@@ -221,21 +236,21 @@
+ }
+ }
+
+- public static LegacyStructureDataHandler getLegacyStructureHandler(ResourceKey<Level> level, @Nullable DimensionDataStorage storage) {
+- if (level == Level.OVERWORLD) {
+- 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 if (level == Level.NETHER) {
+- List<String> list = ImmutableList.of("Fortress");
+- return new LegacyStructureDataHandler(storage, list, list);
+- } else if (level == Level.END) {
+- List<String> list = ImmutableList.of("EndCity");
+- return new LegacyStructureDataHandler(storage, list, list);
++ 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 {
+- throw new RuntimeException(String.format(Locale.ROOT, "Unknown dimension type : %s", level));
++ ImmutableList immutablelist;
++
++ if (level == LevelStem.NETHER) { // CraftBukkit
++ immutablelist = ImmutableList.of("Fortress");
++ return new LegacyStructureDataHandler(storage, immutablelist, immutablelist);
++ } else if (level == LevelStem.END) { // CraftBukkit
++ immutablelist = ImmutableList.of("EndCity");
++ return new LegacyStructureDataHandler(storage, immutablelist, immutablelist);
++ } else {
++ throw new RuntimeException(String.format(Locale.ROOT, "Unknown dimension type : %s", level));
++ }
+ }
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/StructurePiece.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/StructurePiece.java.patch
new file mode 100644
index 0000000000..e65fdfeae4
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/StructurePiece.java.patch
@@ -0,0 +1,173 @@
+--- a/net/minecraft/world/level/levelgen/structure/StructurePiece.java
++++ b/net/minecraft/world/level/levelgen/structure/StructurePiece.java
+@@ -68,16 +57,14 @@
+ }
+
+ public StructurePiece(StructurePieceType type, CompoundTag tag) {
+- this(
+- type,
+- tag.getInt("GD"),
+- BoundingBox.CODEC
+- .parse(NbtOps.INSTANCE, tag.get("BB"))
+- .resultOrPartial(LOGGER::error)
+- .orElseThrow(() -> new IllegalArgumentException("Invalid boundingbox"))
+- );
+- int _int = tag.getInt("O");
+- this.setOrientation(_int == -1 ? null : Direction.from2DDataValue(_int));
++ // CraftBukkit start - decompile error
++ this(type, tag.getInt("GD"), (BoundingBox) BoundingBox.CODEC.parse(NbtOps.INSTANCE, tag.get("BB")).resultOrPartial(Objects.requireNonNull(StructurePiece.LOGGER)::error).orElseThrow(() -> {
++ return new IllegalArgumentException("Invalid boundingbox");
++ }));
++ // CraftBukkit end
++ int j = tag.getInt("O");
++
++ this.setOrientation(j == -1 ? null : Direction.from2DDataValue(j));
+ }
+
+ protected static BoundingBox makeBoundingBox(int x, int y, int z, Direction direction, int offsetX, int offsetY, int offsetZ) {
+@@ -91,14 +76,20 @@
+ }
+
+ public final CompoundTag createTag(StructurePieceSerializationContext context) {
+- CompoundTag compoundTag = new CompoundTag();
+- compoundTag.putString("id", BuiltInRegistries.STRUCTURE_PIECE.getKey(this.getType()).toString());
+- BoundingBox.CODEC.encodeStart(NbtOps.INSTANCE, this.boundingBox).resultOrPartial(LOGGER::error).ifPresent(tag -> compoundTag.put("BB", tag));
+- Direction orientation = this.getOrientation();
+- compoundTag.putInt("O", orientation == null ? -1 : orientation.get2DDataValue());
+- compoundTag.putInt("GD", this.genDepth);
+- this.addAdditionalSaveData(context, compoundTag);
+- return compoundTag;
++ CompoundTag nbttagcompound = new CompoundTag();
++
++ nbttagcompound.putString("id", BuiltInRegistries.STRUCTURE_PIECE.getKey(this.getType()).toString());
++ // CraftBukkit start - decompile error
++ BoundingBox.CODEC.encodeStart(NbtOps.INSTANCE, this.boundingBox).resultOrPartial(Objects.requireNonNull(StructurePiece.LOGGER)::error).ifPresent((nbtbase) -> {
++ nbttagcompound.put("BB", nbtbase);
++ });
++ // CraftBukkit end
++ Direction enumdirection = this.getOrientation();
++
++ nbttagcompound.putInt("O", enumdirection == null ? -1 : enumdirection.get2DDataValue());
++ nbttagcompound.putInt("GD", this.genDepth);
++ this.addAdditionalSaveData(context, nbttagcompound);
++ return nbttagcompound;
+ }
+
+ protected abstract void addAdditionalSaveData(StructurePieceSerializationContext context, CompoundTag tag);
+@@ -190,11 +182,13 @@
+ blockstate = blockstate.rotate(this.rotation);
+ }
+
+- level.setBlock(worldPos, blockstate, 2);
+- FluidState fluidState = level.getFluidState(worldPos);
+- if (!fluidState.isEmpty()) {
+- level.scheduleTick(worldPos, fluidState.getType(), 0);
++ 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 (SHAPE_CHECK_BLOCKS.contains(blockstate.getBlock())) {
+ level.getChunk(worldPos).markPosForPostprocessing(worldPos);
+@@ -203,6 +202,38 @@
+ }
+ }
+
++ // 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;
+ }
+@@ -431,11 +427,20 @@
+ state = reorient(level, pos, Blocks.CHEST.defaultBlockState());
+ }
+
+- level.setBlock(pos, state, 2);
+- BlockEntity blockEntity = level.getBlockEntity(pos);
+- if (blockEntity instanceof ChestBlockEntity) {
+- ((ChestBlockEntity)blockEntity).setLootTable(lootTable, random.nextLong());
++ // CraftBukkit start
++ /*
++ worldaccess.setBlock(blockposition, iblockdata, 2);
++ TileEntity tileentity = worldaccess.getBlockEntity(blockposition);
++
++ if (tileentity instanceof TileEntityChest) {
++ ((TileEntityChest) tileentity).setLootTable(minecraftkey, 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 {
+@@ -443,17 +448,25 @@
+ }
+ }
+
+- protected boolean createDispenser(
+- WorldGenLevel level, BoundingBox box, RandomSource random, int x, int y, int z, Direction facing, ResourceLocation lootTable
+- ) {
+- BlockPos worldPos = this.getWorldPos(x, y, z);
+- if (box.isInside(worldPos) && !level.getBlockState(worldPos).is(Blocks.DISPENSER)) {
+- this.placeBlock(level, Blocks.DISPENSER.defaultBlockState().setValue(DispenserBlock.FACING, facing), x, y, z, box);
+- BlockEntity blockEntity = level.getBlockEntity(worldPos);
+- if (blockEntity instanceof DispenserBlockEntity) {
+- ((DispenserBlockEntity)blockEntity).setLootTable(lootTable, random.nextLong());
++ protected boolean createDispenser(WorldGenLevel level, BoundingBox box, RandomSource random, int x, int y, int z, Direction facing, ResourceLocation lootTable) {
++ BlockPos.MutableBlockPos blockposition_mutableblockposition = this.getWorldPos(x, y, z);
++
++ 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 (tileentity instanceof TileEntityDispenser) {
++ ((TileEntityDispenser) tileentity).setLootTable(minecraftkey, 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-vineflower-stripped/net/minecraft/world/level/levelgen/structure/StructureStart.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/StructureStart.java.patch
new file mode 100644
index 0000000000..589645b182
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -29,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 references, PiecesContainer pieceContainer) {
+ this.structure = structure;
+@@ -83,15 +89,30 @@
+ ) {
+ List<StructurePiece> list = this.pieceContainer.pieces();
+ if (!list.isEmpty()) {
+- BoundingBox boundingBox = list.get(0).boundingBox;
+- BlockPos center = boundingBox.getCenter();
+- BlockPos blockPos = new BlockPos(center.getX(), boundingBox.minY(), center.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();
+
+ for (StructurePiece structurePiece : list) {
+ if (structurePiece.getBoundingBox().intersects(box)) {
+ structurePiece.postProcess(level, structureManager, generator, random, box, chunkPos, blockPos);
+ }
+ }
++ */
++ 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(level, structureManager, generator, random, box, chunkPos, this.pieceContainer);
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java.patch
new file mode 100644
index 0000000000..ab9ce00134
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java.patch
@@ -0,0 +1,18 @@
+--- a/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java
++++ b/net/minecraft/world/level/levelgen/structure/structures/DesertPyramidStructure.java
+@@ -66,6 +70,15 @@
+
+ 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-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java.patch
new file mode 100644
index 0000000000..b863c0c2bb
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java
++++ b/net/minecraft/world/level/levelgen/structure/structures/EndCityPieces.java
+@@ -387,9 +281,15 @@
+ @Override
+ protected void handleDataMarker(String name, BlockPos pos, ServerLevelAccessor level, RandomSource random, BoundingBox box) {
+ if (name.startsWith("Chest")) {
+- BlockPos blockPos = pos.below();
+- if (box.isInside(blockPos)) {
+- RandomizableContainer.setBlockEntityLootTable(level, random, blockPos, BuiltInLootTables.END_CITY_TREASURE);
++ BlockPos blockposition1 = pos.below();
++
++ 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 (box.isInside(pos) && Level.isInSpawnableBounds(pos)) {
+ if (name.startsWith("Sentry")) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java.patch
new file mode 100644
index 0000000000..83e942700f
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java.patch
@@ -0,0 +1,23 @@
+--- a/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java
++++ b/net/minecraft/world/level/levelgen/structure/structures/IglooPieces.java
+@@ -100,10 +83,17 @@
+ protected void handleDataMarker(String name, BlockPos pos, ServerLevelAccessor level, RandomSource random, BoundingBox box) {
+ if ("chest".equals(name)) {
+ level.setBlock(pos, Blocks.AIR.defaultBlockState(), 3);
+- BlockEntity blockEntity = level.getBlockEntity(pos.below());
+- if (blockEntity instanceof ChestBlockEntity) {
+- ((ChestBlockEntity)blockEntity).setLootTable(BuiltInLootTables.IGLOO_CHEST, random.nextLong());
++ // CraftBukkit start - ensure block transformation
++ /*
++ TileEntity tileentity = worldaccess.getBlockEntity(blockposition.below());
++
++ if (tileentity instanceof TileEntityChest) {
++ ((TileEntityChest) tileentity).setLootTable(LootTables.IGLOO_CHEST, randomsource.nextLong());
+ }
++ */
++ setCraftLootTable(level, pos.below(), random, BuiltInLootTables.IGLOO_CHEST);
++ // CraftBukkit end
++
+ }
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java.patch
new file mode 100644
index 0000000000..99fa2b5342
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java.patch
@@ -0,0 +1,25 @@
+--- a/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java
++++ b/net/minecraft/world/level/levelgen/structure/structures/MineshaftPieces.java
+@@ -398,10 +512,19 @@
+ BlockPos worldPos = this.getWorldPos(1, 0, i8);
+ if (box.isInside(worldPos) && this.isInterior(level, 1, 0, i8, box)) {
+ this.hasPlacedSpider = true;
+- level.setBlock(worldPos, Blocks.SPAWNER.defaultBlockState(), 2);
+- if (level.getBlockEntity(worldPos) instanceof SpawnerBlockEntity spawnerBlockEntity) {
+- spawnerBlockEntity.setEntityId(EntityType.CAVE_SPIDER, random);
++ // CraftBukkit start
++ /*
++ generatoraccessseed.setBlock(blockposition_mutableblockposition, Blocks.SPAWNER.defaultBlockState(), 2);
++ TileEntity tileentity = generatoraccessseed.getBlockEntity(blockposition_mutableblockposition);
++
++ if (tileentity instanceof TileEntityMobSpawner) {
++ TileEntityMobSpawner tileentitymobspawner = (TileEntityMobSpawner) tileentity;
++
++ tileentitymobspawner.setEntityId(EntityTypes.CAVE_SPIDER, randomsource);
+ }
++ */
++ placeCraftSpawner(level, blockposition_mutableblockposition, org.bukkit.entity.EntityType.CAVE_SPIDER, 2);
++ // CraftBukkit end
+ }
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java.patch
new file mode 100644
index 0000000000..08716c3519
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java.patch
@@ -0,0 +1,37 @@
+--- a/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java
++++ b/net/minecraft/world/level/levelgen/structure/structures/NetherFortressPieces.java
+@@ -369,20 +423,21 @@
+ this.placeBlock(level, blockState, 3, max + 1, i, box);
+ }
+
+- this.generateBox(level, box, 0, min, i, 4, min, i, Blocks.NETHER_BRICKS.defaultBlockState(), Blocks.NETHER_BRICKS.defaultBlockState(), false);
+- this.generateBox(
+- level, box, 0, max + 1, i, 0, min - 1, i, Blocks.NETHER_BRICKS.defaultBlockState(), Blocks.NETHER_BRICKS.defaultBlockState(), false
+- );
+- this.generateBox(
+- level, box, 4, max + 1, i, 4, min - 1, i, Blocks.NETHER_BRICKS.defaultBlockState(), Blocks.NETHER_BRICKS.defaultBlockState(), false
+- );
+- if ((i & 1) == 0) {
+- this.generateBox(level, box, 0, max + 2, i, 0, max + 3, i, blockState1, blockState1, false);
+- this.generateBox(level, box, 4, max + 2, i, 4, max + 3, i, blockState1, blockState1, false);
+- }
++ if (box.isInside(blockposition_mutableblockposition)) {
++ this.hasPlacedSpawner = true;
++ // CraftBukkit start
++ /*
++ generatoraccessseed.setBlock(blockposition_mutableblockposition, Blocks.SPAWNER.defaultBlockState(), 2);
++ TileEntity tileentity = generatoraccessseed.getBlockEntity(blockposition_mutableblockposition);
+
+- for (int i2 = 0; i2 <= 4; i2++) {
+- this.fillColumnDown(level, Blocks.NETHER_BRICKS.defaultBlockState(), i2, -1, i1, box);
++ if (tileentity instanceof TileEntityMobSpawner) {
++ TileEntityMobSpawner tileentitymobspawner = (TileEntityMobSpawner) tileentity;
++
++ tileentitymobspawner.setEntityId(EntityTypes.BLAZE, randomsource);
++ }
++ */
++ placeCraftSpawner(level, blockposition_mutableblockposition, org.bukkit.entity.EntityType.BLAZE, 2);
++ // CraftBukkit end
+ }
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java.patch
new file mode 100644
index 0000000000..9fc76fa2d8
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java.patch
@@ -0,0 +1,30 @@
+--- a/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java
++++ b/net/minecraft/world/level/levelgen/structure/structures/OceanRuinPieces.java
+@@ -308,14 +196,20 @@
+ @Override
+ protected void handleDataMarker(String name, BlockPos pos, ServerLevelAccessor level, RandomSource random, BoundingBox box) {
+ if ("chest".equals(name)) {
+- level.setBlock(
+- pos, Blocks.CHEST.defaultBlockState().setValue(ChestBlock.WATERLOGGED, Boolean.valueOf(level.getFluidState(pos).is(FluidTags.WATER))), 2
+- );
+- BlockEntity blockEntity = level.getBlockEntity(pos);
+- if (blockEntity instanceof ChestBlockEntity) {
+- ((ChestBlockEntity)blockEntity)
+- .setLootTable(this.isLarge ? BuiltInLootTables.UNDERWATER_RUIN_BIG : BuiltInLootTables.UNDERWATER_RUIN_SMALL, random.nextLong());
++ // 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 (tileentity instanceof TileEntityChest) {
++ ((TileEntityChest) tileentity).setLootTable(this.isLarge ? LootTables.UNDERWATER_RUIN_BIG : LootTables.UNDERWATER_RUIN_SMALL, randomsource.nextLong());
+ }
++ */
++ 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 drowned = EntityType.DROWNED.create(level.getLevel());
+ if (drowned != null) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java.patch
new file mode 100644
index 0000000000..d6a57416bd
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java.patch
@@ -0,0 +1,150 @@
+--- a/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java
++++ b/net/minecraft/world/level/levelgen/structure/structures/StrongholdPieces.java
+@@ -40,29 +40,17 @@
+ private static final int LOWEST_Y_POSITION = 10;
+ private static final boolean CHECK_AIR = true;
+ public static final int MAGIC_START_Y = 64;
+- private static final StrongholdPieces.PieceWeight[] STRONGHOLD_PIECE_WEIGHTS = new StrongholdPieces.PieceWeight[]{
+- new StrongholdPieces.PieceWeight(StrongholdPieces.Straight.class, 40, 0),
+- new StrongholdPieces.PieceWeight(StrongholdPieces.PrisonHall.class, 5, 5),
+- new StrongholdPieces.PieceWeight(StrongholdPieces.LeftTurn.class, 20, 0),
+- new StrongholdPieces.PieceWeight(StrongholdPieces.RightTurn.class, 20, 0),
+- new StrongholdPieces.PieceWeight(StrongholdPieces.RoomCrossing.class, 10, 6),
+- new StrongholdPieces.PieceWeight(StrongholdPieces.StraightStairsDown.class, 5, 5),
+- new StrongholdPieces.PieceWeight(StrongholdPieces.StairsDown.class, 5, 5),
+- new StrongholdPieces.PieceWeight(StrongholdPieces.FiveCrossing.class, 5, 4),
+- new StrongholdPieces.PieceWeight(StrongholdPieces.ChestCorridor.class, 5, 4),
+- new StrongholdPieces.PieceWeight(StrongholdPieces.Library.class, 10, 2) {
+- @Override
+- public boolean doPlace(int genDepth) {
+- return super.doPlace(genDepth) && genDepth > 4;
+- }
+- },
+- new StrongholdPieces.PieceWeight(StrongholdPieces.PortalRoom.class, 20, 1) {
+- @Override
+- public boolean doPlace(int genDepth) {
+- return super.doPlace(genDepth) && genDepth > 5;
+- }
+- }
+- };
++ private static final StrongholdPieces.PieceWeight[] STRONGHOLD_PIECE_WEIGHTS = new StrongholdPieces.PieceWeight[]{new StrongholdPieces.PieceWeight(StrongholdPieces.Straight.class, 40, 0), new StrongholdPieces.PieceWeight(StrongholdPieces.PrisonHall.class, 5, 5), new StrongholdPieces.PieceWeight(StrongholdPieces.LeftTurn.class, 20, 0), new StrongholdPieces.PieceWeight(StrongholdPieces.RightTurn.class, 20, 0), new StrongholdPieces.PieceWeight(StrongholdPieces.RoomCrossing.class, 10, 6), new StrongholdPieces.PieceWeight(StrongholdPieces.StraightStairsDown.class, 5, 5), new StrongholdPieces.PieceWeight(StrongholdPieces.StairsDown.class, 5, 5), new StrongholdPieces.PieceWeight(StrongholdPieces.FiveCrossing.class, 5, 4), new StrongholdPieces.PieceWeight(StrongholdPieces.ChestCorridor.class, 5, 4), new StrongholdPieces.PieceWeight(StrongholdPieces.Library.class, 10, 2) {
++ @Override
++ public boolean doPlace(int genDepth) {
++ return super.doPlace(genDepth) && genDepth > 4;
++ }
++ }, new StrongholdPieces.PieceWeight(StrongholdPieces.PortalRoom.class, 20, 1) {
++ @Override
++ public boolean doPlace(int genDepth) {
++ return super.doPlace(genDepth) && genDepth > 5;
++ }
++ } }; // CraftBukkit - fix decompile styling
+ private static List<StrongholdPieces.PieceWeight> currentPieces;
+ static Class<? extends StrongholdPieces.StrongholdPiece> imposedPiece;
+ private static int totalWeight;
+@@ -1460,14 +1054,98 @@
+ this.generateSmallDoor(level, random, box, StrongholdPieces.StrongholdPiece.SmallDoorType.OPENING, 1, 1, 7);
+ BlockState blockState = Blocks.COBBLESTONE_STAIRS.defaultBlockState().setValue(StairBlock.FACING, Direction.SOUTH);
+
+- for (int i = 0; i < 6; i++) {
+- this.placeBlock(level, blockState, 1, 6 - i, 1 + i, box);
+- this.placeBlock(level, blockState, 2, 6 - i, 1 + i, box);
+- this.placeBlock(level, blockState, 3, 6 - i, 1 + i, box);
+- if (i < 5) {
+- this.placeBlock(level, Blocks.STONE_BRICKS.defaultBlockState(), 1, 5 - i, 1 + i, box);
+- this.placeBlock(level, Blocks.STONE_BRICKS.defaultBlockState(), 2, 5 - i, 1 + i, box);
+- this.placeBlock(level, Blocks.STONE_BRICKS.defaultBlockState(), 3, 5 - i, 1 + i, box);
++ this.generateBox(level, box, 1, 6, 1, 1, 6, 14, false, random, StrongholdPieces.SMOOTH_STONE_SELECTOR);
++ this.generateBox(level, box, 9, 6, 1, 9, 6, 14, false, random, StrongholdPieces.SMOOTH_STONE_SELECTOR);
++ this.generateBox(level, box, 2, 6, 1, 8, 6, 2, false, random, StrongholdPieces.SMOOTH_STONE_SELECTOR);
++ this.generateBox(level, box, 2, 6, 14, 8, 6, 14, false, random, StrongholdPieces.SMOOTH_STONE_SELECTOR);
++ this.generateBox(level, box, 1, 1, 1, 2, 1, 4, false, random, StrongholdPieces.SMOOTH_STONE_SELECTOR);
++ this.generateBox(level, box, 8, 1, 1, 9, 1, 4, false, random, StrongholdPieces.SMOOTH_STONE_SELECTOR);
++ this.generateBox(level, box, 1, 1, 1, 1, 1, 3, Blocks.LAVA.defaultBlockState(), Blocks.LAVA.defaultBlockState(), false);
++ this.generateBox(level, box, 9, 1, 1, 9, 1, 3, Blocks.LAVA.defaultBlockState(), Blocks.LAVA.defaultBlockState(), false);
++ this.generateBox(level, box, 3, 1, 8, 7, 1, 12, false, random, StrongholdPieces.SMOOTH_STONE_SELECTOR);
++ this.generateBox(level, box, 4, 1, 9, 6, 1, 11, Blocks.LAVA.defaultBlockState(), Blocks.LAVA.defaultBlockState(), false);
++ IBlockData iblockdata = (IBlockData) ((IBlockData) Blocks.IRON_BARS.defaultBlockState().setValue(IronBarsBlock.NORTH, true)).setValue(IronBarsBlock.SOUTH, true);
++ IBlockData iblockdata1 = (IBlockData) ((IBlockData) Blocks.IRON_BARS.defaultBlockState().setValue(IronBarsBlock.WEST, true)).setValue(IronBarsBlock.EAST, true);
++
++ int i;
++
++ for (i = 3; i < 14; i += 2) {
++ this.generateBox(level, box, 0, 3, i, 0, 4, i, iblockdata, iblockdata, false);
++ this.generateBox(level, box, 10, 3, i, 10, 4, i, iblockdata, iblockdata, false);
++ }
++
++ for (i = 2; i < 9; i += 2) {
++ this.generateBox(level, box, i, 3, 15, i, 4, 15, iblockdata1, iblockdata1, false);
++ }
++
++ IBlockData iblockdata2 = (IBlockData) Blocks.STONE_BRICK_STAIRS.defaultBlockState().setValue(StairBlock.FACING, Direction.NORTH);
++
++ this.generateBox(level, box, 4, 1, 5, 6, 1, 7, false, random, StrongholdPieces.SMOOTH_STONE_SELECTOR);
++ this.generateBox(level, box, 4, 2, 6, 6, 2, 7, false, random, StrongholdPieces.SMOOTH_STONE_SELECTOR);
++ this.generateBox(level, box, 4, 3, 7, 6, 3, 7, false, random, StrongholdPieces.SMOOTH_STONE_SELECTOR);
++
++ for (int j = 4; j <= 6; ++j) {
++ this.placeBlock(level, iblockdata2, j, 1, 4, box);
++ this.placeBlock(level, iblockdata2, j, 2, 5, box);
++ this.placeBlock(level, iblockdata2, j, 3, 6, box);
++ }
++
++ IBlockData iblockdata3 = (IBlockData) Blocks.END_PORTAL_FRAME.defaultBlockState().setValue(EndPortalFrameBlock.FACING, Direction.NORTH);
++ IBlockData iblockdata4 = (IBlockData) Blocks.END_PORTAL_FRAME.defaultBlockState().setValue(EndPortalFrameBlock.FACING, Direction.SOUTH);
++ IBlockData iblockdata5 = (IBlockData) Blocks.END_PORTAL_FRAME.defaultBlockState().setValue(EndPortalFrameBlock.FACING, Direction.EAST);
++ IBlockData iblockdata6 = (IBlockData) Blocks.END_PORTAL_FRAME.defaultBlockState().setValue(EndPortalFrameBlock.FACING, Direction.WEST);
++ boolean flag1 = true;
++ boolean[] aboolean = new boolean[12];
++
++ for (int k = 0; k < aboolean.length; ++k) {
++ aboolean[k] = random.nextFloat() > 0.9F;
++ flag1 &= aboolean[k];
++ }
++
++ this.placeBlock(level, (IBlockData) iblockdata3.setValue(EndPortalFrameBlock.HAS_EYE, aboolean[0]), 4, 3, 8, box);
++ this.placeBlock(level, (IBlockData) iblockdata3.setValue(EndPortalFrameBlock.HAS_EYE, aboolean[1]), 5, 3, 8, box);
++ this.placeBlock(level, (IBlockData) iblockdata3.setValue(EndPortalFrameBlock.HAS_EYE, aboolean[2]), 6, 3, 8, box);
++ this.placeBlock(level, (IBlockData) iblockdata4.setValue(EndPortalFrameBlock.HAS_EYE, aboolean[3]), 4, 3, 12, box);
++ this.placeBlock(level, (IBlockData) iblockdata4.setValue(EndPortalFrameBlock.HAS_EYE, aboolean[4]), 5, 3, 12, box);
++ this.placeBlock(level, (IBlockData) iblockdata4.setValue(EndPortalFrameBlock.HAS_EYE, aboolean[5]), 6, 3, 12, box);
++ this.placeBlock(level, (IBlockData) iblockdata5.setValue(EndPortalFrameBlock.HAS_EYE, aboolean[6]), 3, 3, 9, box);
++ this.placeBlock(level, (IBlockData) iblockdata5.setValue(EndPortalFrameBlock.HAS_EYE, aboolean[7]), 3, 3, 10, box);
++ this.placeBlock(level, (IBlockData) iblockdata5.setValue(EndPortalFrameBlock.HAS_EYE, aboolean[8]), 3, 3, 11, box);
++ this.placeBlock(level, (IBlockData) iblockdata6.setValue(EndPortalFrameBlock.HAS_EYE, aboolean[9]), 7, 3, 9, box);
++ this.placeBlock(level, (IBlockData) iblockdata6.setValue(EndPortalFrameBlock.HAS_EYE, aboolean[10]), 7, 3, 10, box);
++ this.placeBlock(level, (IBlockData) iblockdata6.setValue(EndPortalFrameBlock.HAS_EYE, aboolean[11]), 7, 3, 11, box);
++ if (flag1) {
++ IBlockData iblockdata7 = Blocks.END_PORTAL.defaultBlockState();
++
++ this.placeBlock(level, iblockdata7, 4, 3, 9, box);
++ this.placeBlock(level, iblockdata7, 5, 3, 9, box);
++ this.placeBlock(level, iblockdata7, 6, 3, 9, box);
++ this.placeBlock(level, iblockdata7, 4, 3, 10, box);
++ this.placeBlock(level, iblockdata7, 5, 3, 10, box);
++ this.placeBlock(level, iblockdata7, 6, 3, 10, box);
++ this.placeBlock(level, iblockdata7, 4, 3, 11, box);
++ this.placeBlock(level, iblockdata7, 5, 3, 11, box);
++ this.placeBlock(level, iblockdata7, 6, 3, 11, box);
++ }
++
++ if (!this.hasPlacedSpawner) {
++ BlockPos.MutableBlockPos blockposition_mutableblockposition = this.getWorldPos(5, 3, 6);
++
++ if (box.isInside(blockposition_mutableblockposition)) {
++ this.hasPlacedSpawner = true;
++ // CraftBukkit start
++ /*
++ generatoraccessseed.setBlock(blockposition_mutableblockposition, Blocks.SPAWNER.defaultBlockState(), 2);
++ TileEntity tileentity = generatoraccessseed.getBlockEntity(blockposition_mutableblockposition);
++
++ if (tileentity instanceof TileEntityMobSpawner) {
++ TileEntityMobSpawner tileentitymobspawner = (TileEntityMobSpawner) tileentity;
++
++ tileentitymobspawner.setEntityId(EntityTypes.SILVERFISH, randomsource);
++ }
++ */
++ placeCraftSpawner(level, blockposition_mutableblockposition, org.bukkit.entity.EntityType.SILVERFISH, 2);
++ // CraftBukkit end
+ }
+ }
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java.patch
new file mode 100644
index 0000000000..1849c3b3c4
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java.patch
@@ -0,0 +1,42 @@
+--- a/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java
++++ b/net/minecraft/world/level/levelgen/structure/structures/SwampHutPiece.java
+@@ -92,12 +94,13 @@
+ BlockPos worldPos = this.getWorldPos(2, 2, 5);
+ if (box.isInside(worldPos)) {
+ this.spawnedWitch = true;
+- Witch witch = EntityType.WITCH.create(level.getLevel());
+- if (witch != null) {
+- witch.setPersistenceRequired();
+- witch.moveTo((double)worldPos.getX() + 0.5, (double)worldPos.getY(), (double)worldPos.getZ() + 0.5, 0.0F, 0.0F);
+- witch.finalizeSpawn(level, level.getCurrentDifficultyAt(worldPos), MobSpawnType.STRUCTURE, null, null);
+- level.addFreshEntityWithPassengers(witch);
++ Witch entitywitch = (Witch) EntityType.WITCH.create(level.getLevel());
++
++ 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
+ }
+ }
+ }
+@@ -111,12 +115,13 @@
+ BlockPos worldPos = this.getWorldPos(2, 2, 5);
+ if (box.isInside(worldPos)) {
+ this.spawnedCat = true;
+- Cat cat = EntityType.CAT.create(level.getLevel());
+- if (cat != null) {
+- cat.setPersistenceRequired();
+- cat.moveTo((double)worldPos.getX() + 0.5, (double)worldPos.getY(), (double)worldPos.getZ() + 0.5, 0.0F, 0.0F);
+- cat.finalizeSpawn(level, level.getCurrentDifficultyAt(worldPos), MobSpawnType.STRUCTURE, null, null);
+- level.addFreshEntityWithPassengers(cat);
++ Cat entitycat = (Cat) EntityType.CAT.create(level.getLevel());
++
++ 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-vineflower-stripped/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java.patch
new file mode 100644
index 0000000000..2552b5982c
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java.patch
@@ -0,0 +1,27 @@
+--- a/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java
++++ b/net/minecraft/world/level/levelgen/structure/templatesystem/StructurePlaceSettings.java
+@@ -21,8 +22,8 @@
+ private boolean keepLiquids = true;
+ @Nullable
+ private RandomSource random;
+- private int palette;
+- private final List<StructureProcessor> processors = Lists.newArrayList();
++ 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;
+
+@@ -142,6 +149,13 @@
+ int size = palettes.size();
+ if (size == 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 palettes.get(this.getRandom(pos).nextInt(size));
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java.patch
new file mode 100644
index 0000000000..b8119871ce
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java.patch
@@ -0,0 +1,158 @@
+--- a/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java
++++ b/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java
+@@ -50,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 {
+ public static final String PALETTE_TAG = "palette";
+@@ -68,6 +74,16 @@
+ private Vec3i size = Vec3i.ZERO;
+ 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 = "?";
++ }
++
+ public Vec3i getSize() {
+ return this.size;
+ }
+@@ -216,6 +238,19 @@
+ if (this.palettes.isEmpty()) {
+ return false;
+ } else {
++ // 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() || !settings.isIgnoreEntities() && !this.entityInfoList.isEmpty())
+ && this.size.getX() >= 1
+@@ -242,20 +282,34 @@
+ Clearable.tryClear(blockEntity);
+ serverLevel.setBlock(blockPos, 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 (serverLevel.setBlock(blockPos, blockState, flags)) {
+- i = Math.min(i, blockPos.getX());
+- i1 = Math.min(i1, blockPos.getY());
+- i2 = Math.min(i2, blockPos.getZ());
+- i3 = Math.max(i3, blockPos.getX());
+- i4 = Math.max(i4, blockPos.getY());
+- i5 = Math.max(i5, blockPos.getZ());
+- list3.add(Pair.of(blockPos, structureBlockInfo.nbt));
+- if (structureBlockInfo.nbt != null) {
+- BlockEntity blockEntity = serverLevel.getBlockEntity(blockPos);
+- if (blockEntity != null) {
+- if (blockEntity instanceof RandomizableContainer) {
+- structureBlockInfo.nbt.putLong("LootTableSeed", random.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(structureBlockInfo.nbt);
+@@ -344,15 +413,7 @@
+ }
+
+ if (!settings.isIgnoreEntities()) {
+- this.placeEntities(
+- serverLevel,
+- offset,
+- settings.getMirror(),
+- settings.getRotation(),
+- settings.getRotationPivot(),
+- boundingBox,
+- settings.shouldFinalizeEntities()
+- );
++ this.placeEntities(wrappedAccess, offset, settings.getMirror(), settings.getRotation(), settings.getRotationPivot(), structureboundingbox, settings.shouldFinalizeEntities()); // CraftBukkit
+ }
+
+ return true;
+@@ -452,11 +508,13 @@
+ }
+
+ private static Optional<Entity> createEntityIgnoreException(ServerLevelAccessor level, CompoundTag tag) {
+- try {
++ // CraftBukkit start
++ // try {
+ return EntityType.create(tag, level.getLevel());
+- } catch (Exception var3) {
+- return Optional.empty();
+- }
++ // } catch (Exception exception) {
++ // return Optional.empty();
++ // }
++ // CraftBukkit end
+ }
+
+ public Vec3i getSize(Rotation rotation) {
+@@ -649,6 +728,11 @@
+
+ tag.put("entities", list5);
+ 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);
+ }
+
+@@ -681,6 +771,13 @@
+ this.entityInfoList.add(new StructureTemplate.StructureEntityInfo(vec3, blockPos, compound1));
+ }
+ }
++
++ // CraftBukkit start - PDC
++ Tag base = tag.get("BukkitValues");
++ if (base instanceof CompoundTag) {
++ this.persistentDataContainer.putAll((CompoundTag) base);
++ }
++ // CraftBukkit end
+ }
+
+ private void loadPalette(HolderGetter<Block> blockGetter, ListTag paletteTag, ListTag blocksTag) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/material/FlowingFluid.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/material/FlowingFluid.java.patch
new file mode 100644
index 0000000000..76aac7251d
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/material/FlowingFluid.java.patch
@@ -0,0 +1,120 @@
+--- a/net/minecraft/world/level/material/FlowingFluid.java
++++ b/net/minecraft/world/level/material/FlowingFluid.java
+@@ -29,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 {
+ public static final BooleanProperty FALLING = BlockStateProperties.FALLING;
+@@ -111,12 +134,22 @@
+
+ protected void spread(Level level, BlockPos pos, FluidState state) {
+ if (!state.isEmpty()) {
+- BlockState blockState = level.getBlockState(pos);
+- BlockPos blockPos = pos.below();
+- BlockState blockState1 = level.getBlockState(blockPos);
+- FluidState newLiquid = this.getNewLiquid(level, blockPos, blockState1);
+- if (this.canSpreadTo(level, pos, blockState, Direction.DOWN, blockPos, blockState1, level.getFluidState(blockPos), newLiquid.getType())) {
+- this.spreadTo(level, blockPos, blockState1, Direction.DOWN, newLiquid);
++ IBlockData iblockdata = level.getBlockState(pos);
++ BlockPos blockposition1 = pos.below();
++ IBlockData iblockdata1 = level.getBlockState(blockposition1);
++ FluidState fluid1 = this.getNewLiquid(level, blockposition1, iblockdata1);
++
++ 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;
++ }
++ // CraftBukkit end
++ this.spreadTo(level, blockposition1, iblockdata1, Direction.DOWN, fluid1);
+ if (this.sourceNeighborCount(level, pos) >= 3) {
+ this.spreadToSides(level, pos, state, blockState);
+ }
+@@ -135,13 +171,24 @@
+ if (i > 0) {
+ Map<Direction, FluidState> spread = this.getSpread(level, pos, blockState);
+
+- for (Entry<Direction, FluidState> entry : spread.entrySet()) {
+- Direction direction = entry.getKey();
+- FluidState fluidState1 = entry.getValue();
+- BlockPos blockPos = pos.relative(direction);
+- BlockState blockState1 = level.getBlockState(blockPos);
+- if (this.canSpreadTo(level, pos, blockState, direction, blockPos, blockState1, level.getFluidState(blockPos), fluidState1.getType())) {
+- this.spreadTo(level, blockPos, blockState1, direction, fluidState1);
++ while (iterator.hasNext()) {
++ Entry<Direction, FluidState> entry = (Entry) iterator.next();
++ Direction enumdirection = (Direction) entry.getKey();
++ FluidState fluid1 = (FluidState) entry.getValue();
++ BlockPos blockposition1 = pos.relative(enumdirection);
++ IBlockData iblockdata1 = level.getBlockState(blockposition1);
++
++ 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);
+ }
+ }
+ }
+@@ -415,17 +459,30 @@
+ @Override
+ public void tick(Level level, BlockPos pos, FluidState state) {
+ if (!state.isSource()) {
+- FluidState newLiquid = this.getNewLiquid(level, pos, level.getBlockState(pos));
+- int spreadDelay = this.getSpreadDelay(level, pos, state, newLiquid);
+- if (newLiquid.isEmpty()) {
+- state = newLiquid;
+- level.setBlock(pos, Blocks.AIR.defaultBlockState(), 3);
+- } else if (!newLiquid.equals(state)) {
+- state = newLiquid;
+- BlockState blockState = newLiquid.createLegacyBlock();
+- level.setBlock(pos, blockState, 2);
+- level.scheduleTick(pos, newLiquid.getType(), spreadDelay);
+- level.updateNeighborsAt(pos, blockState.getBlock());
++ FluidState fluid1 = this.getNewLiquid(level, pos, level.getBlockState(pos));
++ int i = this.getSpreadDelay(level, pos, state, fluid1);
++
++ 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-vineflower-stripped/net/minecraft/world/level/material/LavaFluid.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/material/LavaFluid.java.patch
new file mode 100644
index 0000000000..c36dbb9da2
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/material/LavaFluid.java.patch
@@ -0,0 +1,57 @@
+--- a/net/minecraft/world/level/material/LavaFluid.java
++++ b/net/minecraft/world/level/material/LavaFluid.java
+@@ -84,10 +80,18 @@
+ return;
+ }
+
+- BlockState blockState = level.getBlockState(blockPos);
+- if (blockState.isAir()) {
+- if (this.hasFlammableNeighbours(level, blockPos)) {
+- level.setBlockAndUpdate(blockPos, BaseFireBlock.getState(level, blockPos));
++ IBlockData iblockdata = level.getBlockState(blockposition1);
++
++ 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()) {
+@@ -101,8 +106,16 @@
+ return;
+ }
+
+- if (level.isEmptyBlock(blockPos1.above()) && this.isFlammable(level, blockPos1)) {
+- level.setBlockAndUpdate(blockPos1.above(), BaseFireBlock.getState(level, blockPos1));
++ 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));
+ }
+ }
+ }
+@@ -195,7 +210,11 @@
+ FluidState fluidState1 = level.getFluidState(pos);
+ if (this.is(FluidTags.LAVA) && fluidState1.is(FluidTags.WATER)) {
+ if (blockState.getBlock() instanceof LiquidBlock) {
+- level.setBlock(pos, Blocks.STONE.defaultBlockState(), 3);
++ // CraftBukkit start
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(level.getMinecraftWorld(), pos, Blocks.STONE.defaultBlockState(), 3)) {
++ return;
++ }
++ // CraftBukkit end
+ }
+
+ this.fizz(level, pos);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/portal/PortalForcer.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/portal/PortalForcer.java.patch
new file mode 100644
index 0000000000..0688334b1a
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/portal/PortalForcer.java.patch
@@ -0,0 +1,176 @@
+--- a/net/minecraft/world/level/portal/PortalForcer.java
++++ b/net/minecraft/world/level/portal/PortalForcer.java
+@@ -41,42 +42,56 @@
+ }
+
+ public Optional<BlockUtil.FoundRectangle> findPortalAround(BlockPos pos, boolean isNether, WorldBorder worldBorder) {
+- PoiManager poiManager = this.level.getPoiManager();
+- int i = isNether ? 16 : 128;
+- poiManager.ensureLoadedAndValid(this.level, pos, i);
+- Optional<PoiRecord> optional = poiManager.getInSquare(poiType -> poiType.is(PoiTypes.NETHER_PORTAL), pos, i, PoiManager.Occupancy.ANY)
+- .filter(poiRecord -> worldBorder.isWithinBounds(poiRecord.getPos()))
+- .sorted(
+- Comparator.<PoiRecord>comparingDouble(poiRecord -> poiRecord.getPos().distSqr(pos)).thenComparingInt(poiRecord -> poiRecord.getPos().getY())
+- )
+- .filter(poiRecord -> this.level.getBlockState(poiRecord.getPos()).hasProperty(BlockStateProperties.HORIZONTAL_AXIS))
+- .findFirst();
+- return optional.map(
+- poiRecord -> {
+- BlockPos pos1 = poiRecord.getPos();
+- this.level.getChunkSource().addRegionTicket(TicketType.PORTAL, new ChunkPos(pos1), 3, pos1);
+- BlockState blockState = this.level.getBlockState(pos1);
+- return BlockUtil.getLargestRectangleAround(
+- pos1,
+- blockState.getValue(BlockStateProperties.HORIZONTAL_AXIS),
+- 21,
+- Direction.Axis.Y,
+- 21,
+- possibleBlockPos -> this.level.getBlockState(possibleBlockPos) == blockState
+- );
+- }
+- );
++ // CraftBukkit start
++ return findPortalAround(pos, worldBorder, isNether ? 16 : 128); // Search Radius
+ }
+
++ 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);
++ }, blockposition, i, PoiManager.Occupancy.ANY).filter((villageplacerecord) -> {
++ return worldborder.isWithinBounds(villageplacerecord.getPos());
++ }).sorted(Comparator.comparingDouble((PoiRecord villageplacerecord) -> { // CraftBukkit - decompile error
++ return villageplacerecord.getPos().distSqr(blockposition);
++ }).thenComparingInt((villageplacerecord) -> {
++ return villageplacerecord.getPos().getY();
++ })).filter((villageplacerecord) -> {
++ return this.level.getBlockState(villageplacerecord.getPos()).hasProperty(BlockStateProperties.HORIZONTAL_AXIS);
++ }).findFirst();
++
++ return optional.map((villageplacerecord) -> {
++ BlockPos blockposition1 = villageplacerecord.getPos();
++
++ this.level.getChunkSource().addRegionTicket(TicketType.PORTAL, new ChunkPos(blockposition1), 3, blockposition1);
++ IBlockData iblockdata = this.level.getBlockState(blockposition1);
++
++ return BlockUtil.getLargestRectangleAround(blockposition1, (Direction.Axis) iblockdata.getValue(BlockStateProperties.HORIZONTAL_AXIS), 21, Direction.Axis.Y, 21, (blockposition2) -> {
++ return this.level.getBlockState(blockposition2) == iblockdata;
++ });
++ });
++ }
++
+ public Optional<BlockUtil.FoundRectangle> createPortal(BlockPos pos, Direction.Axis axis) {
+- Direction direction = Direction.get(Direction.AxisDirection.POSITIVE, axis);
+- double d = -1.0;
+- BlockPos blockPos = null;
+- double d1 = -1.0;
+- BlockPos blockPos1 = null;
+- WorldBorder worldBorder = this.level.getWorldBorder();
++ // 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 blockposition1 = null;
++ double d1 = -1.0D;
++ BlockPos blockposition2 = null;
++ WorldBorder worldborder = this.level.getWorldBorder();
+ int i = Math.min(this.level.getMaxBuildHeight(), this.level.getMinBuildHeight() + this.level.getLogicalHeight()) - 1;
+- BlockPos.MutableBlockPos mutableBlockPos = pos.mutable();
++ BlockPos.MutableBlockPos blockposition_mutableblockposition = blockposition.mutable();
++ Iterator iterator = BlockPos.spiralAround(blockposition, createRadius, Direction.EAST, Direction.SOUTH).iterator(); // CraftBukkit
+
+ for (BlockPos.MutableBlockPos mutableBlockPos1 : BlockPos.spiralAround(pos, 16, Direction.EAST, Direction.SOUTH)) {
+ int min = Math.min(i, this.level.getHeight(Heightmap.Types.MOTION_BLOCKING, mutableBlockPos1.getX(), mutableBlockPos1.getZ()));
+@@ -123,10 +143,14 @@
+ d = d1;
+ }
+
+- if (d == -1.0) {
+- int max = Math.max(this.level.getMinBuildHeight() - -1, 70);
+- int i5 = i - 9;
+- if (i5 < max) {
++ 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;
++ if (k1 < j1) {
+ return Optional.empty();
+ }
+
+@@ -136,38 +161,47 @@
+ return Optional.empty();
+ }
+
+- for (int i1 = -1; i1 < 2; i1++) {
+- for (int i2x = 0; i2x < 2; i2x++) {
+- for (int i3 = -1; i3 < 3; i3++) {
+- BlockState blockState = i3 < 0 ? Blocks.OBSIDIAN.defaultBlockState() : Blocks.AIR.defaultBlockState();
+- mutableBlockPos.setWithOffset(
+- blockPos, i2x * direction.getStepX() + i1 * clockWise.getStepX(), i3, i2x * direction.getStepZ() + i1 * clockWise.getStepZ()
+- );
+- this.level.setBlockAndUpdate(mutableBlockPos, blockState);
++ for (int l1 = -1; l1 < 2; ++l1) {
++ for (k = 0; k < 2; ++k) {
++ for (l = -1; l < 3; ++l) {
++ IBlockData iblockdata = l < 0 ? Blocks.OBSIDIAN.defaultBlockState() : Blocks.AIR.defaultBlockState();
++
++ blockposition_mutableblockposition.setWithOffset(blockposition1, k * enumdirection.getStepX() + l1 * enumdirection1.getStepX(), l, k * enumdirection.getStepZ() + l1 * enumdirection1.getStepZ());
++ blockList.setBlock(blockposition_mutableblockposition, iblockdata, 3); // CraftBukkit
+ }
+ }
+ }
+ }
+
+- for (int maxx = -1; maxx < 3; maxx++) {
+- for (int i5x = -1; i5x < 4; i5x++) {
+- if (maxx == -1 || maxx == 2 || i5x == -1 || i5x == 3) {
+- mutableBlockPos.setWithOffset(blockPos, maxx * direction.getStepX(), i5x, maxx * direction.getStepZ());
+- this.level.setBlock(mutableBlockPos, Blocks.OBSIDIAN.defaultBlockState(), 3);
++ for (j1 = -1; j1 < 3; ++j1) {
++ for (k1 = -1; k1 < 4; ++k1) {
++ if (j1 == -1 || j1 == 2 || k1 == -1 || k1 == 3) {
++ blockposition_mutableblockposition.setWithOffset(blockposition1, j1 * enumdirection.getStepX(), k1, j1 * enumdirection.getStepZ());
++ blockList.setBlock(blockposition_mutableblockposition, Blocks.OBSIDIAN.defaultBlockState(), 3); // CraftBukkit
+ }
+ }
+ }
+
+ BlockState blockState1 = Blocks.NETHER_PORTAL.defaultBlockState().setValue(NetherPortalBlock.AXIS, axis);
+
+- for (int i5xx = 0; i5xx < 2; i5xx++) {
+- for (int min = 0; min < 3; min++) {
+- mutableBlockPos.setWithOffset(blockPos, i5xx * direction.getStepX(), min, i5xx * direction.getStepZ());
+- this.level.setBlock(mutableBlockPos, blockState1, 18);
++ for (k1 = 0; k1 < 2; ++k1) {
++ for (j = 0; j < 3; ++j) {
++ blockposition_mutableblockposition.setWithOffset(blockposition1, k1 * enumdirection.getStepX(), j, k1 * enumdirection.getStepZ());
++ blockList.setBlock(blockposition_mutableblockposition, iblockdata1, 18); // CraftBukkit
+ }
+ }
+
+- return Optional.of(new BlockUtil.FoundRectangle(blockPos.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 pos) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/portal/PortalInfo.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/portal/PortalInfo.java.patch
new file mode 100644
index 0000000000..902de997af
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/portal/PortalInfo.java.patch
@@ -0,0 +1,33 @@
+--- a/net/minecraft/world/level/portal/PortalInfo.java
++++ b/net/minecraft/world/level/portal/PortalInfo.java
+@@ -1,17 +2,25 @@
+ package net.minecraft.world.level.portal;
+
+ import net.minecraft.world.phys.Vec3;
++import org.bukkit.craftbukkit.event.CraftPortalEvent;
++// CraftBukkit end
+
+ public class PortalInfo {
+ public final Vec3 pos;
+ 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 pos, Vec3 speed, float yRot, float xRot) {
+- this.pos = pos;
+- this.speed = speed;
+- this.yRot = yRot;
+- this.xRot = xRot;
++ 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-vineflower-stripped/net/minecraft/world/level/portal/PortalShape.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/portal/PortalShape.java.patch
new file mode 100644
index 0000000000..44dc6ce9ca
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/portal/PortalShape.java.patch
@@ -0,0 +1,150 @@
+--- a/net/minecraft/world/level/portal/PortalShape.java
++++ b/net/minecraft/world/level/portal/PortalShape.java
+@@ -22,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 {
+ private static final int MIN_WIDTH = 2;
+@@ -39,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 level, BlockPos bottomLeft, Direction.Axis axis) {
+ return findPortalShape(level, bottomLeft, shape -> shape.isValid() && shape.numPortalBlocks == 0, axis);
+@@ -55,6 +66,7 @@
+ }
+
+ 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;
+@@ -92,11 +105,13 @@
+ private int getDistanceUntilEdgeAboveFrame(BlockPos pos, Direction direction) {
+ BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
+
+- for (int i = 0; i <= 21; i++) {
+- mutableBlockPos.set(pos).move(direction, i);
+- BlockState blockState = this.level.getBlockState(mutableBlockPos);
+- if (!isEmpty(blockState)) {
+- if (FRAME.test(blockState, this.level, mutableBlockPos)) {
++ for (int i = 0; i <= 21; ++i) {
++ blockposition_mutableblockposition.set(pos).move(direction, i);
++ IBlockData iblockdata = this.level.getBlockState(blockposition_mutableblockposition);
++
++ 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;
+@@ -106,6 +122,7 @@
+ if (!FRAME.test(blockState1, this.level, mutableBlockPos)) {
+ break;
+ }
++ blocks.setBlock(blockposition_mutableblockposition, iblockdata1, 18); // CraftBukkit - bottom row
+ }
+
+ return 0;
+@@ -123,6 +142,7 @@
+ if (!FRAME.test(this.level.getBlockState(mutableBlockPos), this.level, mutableBlockPos)) {
+ return false;
+ }
++ blocks.setBlock(blockposition_mutableblockposition1, this.level.getBlockState(blockposition_mutableblockposition1), 18); // CraftBukkit - upper row
+ }
+
+ return true;
+@@ -151,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;
+@@ -164,10 +189,28 @@
+ return this.bottomLeft != null && this.width >= 2 && this.width <= 21 && this.height >= 3 && this.height <= 21;
+ }
+
+- public void createPortalBlocks() {
+- BlockState blockState = Blocks.NETHER_PORTAL.defaultBlockState().setValue(NetherPortalBlock.AXIS, this.axis);
+- BlockPos.betweenClosed(this.bottomLeft, this.bottomLeft.relative(Direction.UP, this.height - 1).relative(this.rightDir, this.width - 1))
+- .forEach(pos -> this.level.setBlock(pos, blockState, 18));
++ // CraftBukkit start - return boolean
++ public boolean createPortalBlocks() {
++ org.bukkit.World bworld = this.level.getMinecraftWorld().getWorld();
++
++ // Copy below for loop
++ IBlockData iblockdata = (IBlockData) Blocks.NETHER_PORTAL.defaultBlockState().setValue(NetherPortalBlock.AXIS, this.axis);
++
++ BlockPos.betweenClosed(this.bottomLeft, this.bottomLeft.relative(Direction.UP, this.height - 1).relative(this.rightDir, this.width - 1)).forEach((blockposition) -> {
++ blocks.setBlock(blockposition, iblockdata, 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() {
+@@ -199,24 +246,23 @@
+ return new Vec3(d3, d2, d4);
+ }
+
+- public static PortalInfo createPortalInfo(
+- ServerLevel level, BlockUtil.FoundRectangle portalPos, Direction.Axis axis, Vec3 relativePos, Entity entity, Vec3 velocity, float yRot, float xRot
+- ) {
+- BlockPos blockPos = portalPos.minCorner;
+- BlockState blockState = level.getBlockState(blockPos);
+- Direction.Axis axis1 = blockState.getOptionalValue(BlockStateProperties.HORIZONTAL_AXIS).orElse(Direction.Axis.X);
+- double d = (double)portalPos.axis1Size;
+- double d1 = (double)portalPos.axis2Size;
+- EntityDimensions dimensions = entity.getDimensions(entity.getPose());
+- int i = axis == axis1 ? 0 : 90;
+- Vec3 vec3 = axis == axis1 ? velocity : new Vec3(velocity.z, velocity.y, -velocity.x);
+- double d2 = (double)dimensions.width / 2.0 + (d - (double)dimensions.width) * relativePos.x();
+- double d3 = (d1 - (double)dimensions.height) * relativePos.y();
+- double d4 = 0.5 + relativePos.z();
+- boolean flag = axis1 == Direction.Axis.X;
+- Vec3 vec31 = new Vec3((double)blockPos.getX() + (flag ? d2 : d4), (double)blockPos.getY() + d3, (double)blockPos.getZ() + (flag ? d4 : d2));
+- Vec3 vec32 = findCollisionFreePosition(vec31, level, entity, dimensions);
+- return new PortalInfo(vec32, vec3, yRot + (float)i, xRot);
++ 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(vec3d4, vec3d2, f + (float) i, f1, worldserver, portalEventInfo); // CraftBukkit
+ }
+
+ private static Vec3 findCollisionFreePosition(Vec3 pos, ServerLevel level, Entity entity, EntityDimensions dimensions) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/redstone/NeighborUpdater.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/redstone/NeighborUpdater.java.patch
new file mode 100644
index 0000000000..f74d683981
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/redstone/NeighborUpdater.java.patch
@@ -0,0 +1,34 @@
+--- 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 {
+ Direction[] UPDATE_ORDER = new Direction[]{Direction.WEST, Direction.EAST, Direction.DOWN, Direction.UP, Direction.NORTH, Direction.SOUTH};
+@@ -40,6 +52,17 @@
+
+ static void executeUpdate(Level level, BlockState state, BlockPos pos, Block neighborBlock, BlockPos neighborPos, boolean movedByPiston) {
+ try {
++ // 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 var9) {
+ CrashReport crashReport = CrashReport.forThrowable(var9, "Exception while updating neighbours");
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java.patch
new file mode 100644
index 0000000000..5db06ac6bc
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java.patch
@@ -0,0 +1,182 @@
+--- a/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
++++ b/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
+@@ -31,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();
+ private static final int MAP_SIZE = 128;
+@@ -52,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);
+ }
+@@ -67,6 +86,10 @@
+ this.unlimitedTracking = unlimitedTracking;
+ this.locked = locked;
+ this.setDirty();
++ // CraftBukkit start
++ mapView = new CraftMapView(this);
++ server = (CraftServer) org.bukkit.Bukkit.getServer();
++ // CraftBukkit end
+ }
+
+ public static MapItemSavedData createFresh(
+@@ -85,12 +107,34 @@
+ }
+
+ public static MapItemSavedData load(CompoundTag compoundTag) {
+- ResourceKey<Level> resourceKey = DimensionType.parseLegacy(new Dynamic<>(NbtOps.INSTANCE, compoundTag.get("dimension")))
+- .resultOrPartial(LOGGER::error)
+- .orElseThrow(() -> new IllegalArgumentException("Invalid map dimension: " + compoundTag.get("dimension")));
+- int _int = compoundTag.getInt("xCenter");
+- int _int1 = compoundTag.getInt("zCenter");
+- byte b = (byte)Mth.clamp(compoundTag.getByte("scale"), 0, 4);
++ DataResult<ResourceKey<Level>> dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, compoundTag.get("dimension"))); // CraftBukkit - decompile error
++ Logger logger = MapItemSavedData.LOGGER;
++
++ Objects.requireNonNull(logger);
++ // 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");
++ byte b0 = (byte) Mth.clamp(compoundTag.getByte("scale"), 0, 4);
+ boolean flag = !compoundTag.contains("trackingPosition", 1) || compoundTag.getBoolean("trackingPosition");
+ boolean _boolean = compoundTag.getBoolean("unlimitedTracking");
+ boolean _boolean1 = compoundTag.getBoolean("locked");
+@@ -137,10 +168,32 @@
+
+ @Override
+ public CompoundTag save(CompoundTag compound) {
+- ResourceLocation.CODEC
+- .encodeStart(NbtOps.INSTANCE, this.dimension.location())
+- .resultOrPartial(LOGGER::error)
+- .ifPresent(tag -> compound.put("dimension", tag));
++ DataResult<Tag> dataresult = ResourceLocation.CODEC.encodeStart(NbtOps.INSTANCE, this.dimension.location()); // CraftBukkit - decompile error
++ Logger logger = MapItemSavedData.LOGGER;
++
++ Objects.requireNonNull(logger);
++ dataresult.resultOrPartial(logger::error).ifPresent((nbtbase) -> {
++ compound.put("dimension", nbtbase);
++ });
++ // 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);
+@@ -505,16 +568,16 @@
+ this.player = player;
+ }
+
+- private MapItemSavedData.MapPatch createPatch() {
++ private MapItemSavedData.MapPatch createPatch(byte[] buffer) { // CraftBukkit
+ int i = this.minDirtyX;
+ int i1 = this.minDirtyY;
+ int i2 = this.maxDirtyX + 1 - this.minDirtyX;
+ int i3 = this.maxDirtyY + 1 - this.minDirtyY;
+ byte[] bytes = new byte[i2 * i3];
+
+- for (int i4 = 0; i4 < i2; i4++) {
+- for (int i5 = 0; i5 < i3; i5++) {
+- bytes[i4 + i5 * i2] = MapItemSavedData.this.colors[i + i4 + (i1 + i5) * 128];
++ for (int i1 = 0; i1 < k; ++i1) {
++ for (int j1 = 0; j1 < l; ++j1) {
++ abyte[i1 + j1 * k] = buffer[i + i1 + (j + j1) * 128]; // CraftBukkit
+ }
+ }
+
+@@ -523,18 +586,30 @@
+
+ @Nullable
+ Packet<?> nextUpdatePacket(int mapId) {
+- MapItemSavedData.MapPatch mapPatch;
++ 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;
+- mapPatch = this.createPatch();
++ worldmap_b = this.createPatch(render.buffer); // CraftBukkit
+ } else {
+ mapPatch = null;
+ }
+
+- Collection<MapDecoration> collection;
+- if (this.dirtyDecorations && this.tick++ % 5 == 0) {
++ Collection collection;
++
++ 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-vineflower-stripped/net/minecraft/world/level/storage/LevelStorageSource.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/LevelStorageSource.java.patch
new file mode 100644
index 0000000000..ce297e5b2e
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/LevelStorageSource.java.patch
@@ -0,0 +1,110 @@
+--- a/net/minecraft/world/level/storage/LevelStorageSource.java
++++ b/net/minecraft/world/level/storage/LevelStorageSource.java
+@@ -151,14 +164,18 @@
+ ) {
+ Dynamic<?> dynamic1 = wrapWithRegistryOps(dynamic, frozen);
+ Dynamic<?> dynamic2 = dynamic1.get("WorldGenSettings").orElseEmptyMap();
+- WorldGenSettings worldGenSettings = WorldGenSettings.CODEC.parse(dynamic2).getOrThrow(false, Util.prefix("WorldGenSettings: ", LOGGER::error));
+- LevelSettings levelSettings = LevelSettings.parse(dynamic1, worldDataConfiguration);
+- WorldDimensions.Complete complete = worldGenSettings.dimensions().bake(registry);
+- Lifecycle lifecycle = complete.lifecycle().add(frozen.allRegistriesLifecycle());
+- PrimaryLevelData primaryLevelData = PrimaryLevelData.parse(
+- dynamic1, levelSettings, complete.specialWorldProperty(), worldGenSettings.options(), lifecycle
+- );
+- return new LevelDataAndDimensions(primaryLevelData, complete);
++ DataResult dataresult = WorldGenSettings.CODEC.parse(dynamic2);
++ Logger logger = LevelStorageSource.LOGGER;
++
++ Objects.requireNonNull(logger);
++ 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(worlddataserver, worlddimensions_b);
+ }
+
+ private static <T> Dynamic<T> wrapWithRegistryOps(Dynamic<T> dynamic, RegistryAccess.Frozen frozen) {
+@@ -364,26 +423,43 @@
+ return this.backupDir;
+ }
+
+- public LevelStorageSource.LevelStorageAccess validateAndCreateAccess(String saveName) throws IOException, ContentValidationException {
+- Path levelPath = this.getLevelPath(saveName);
+- List<ForbiddenSymlinkInfo> list = this.worldDirValidator.validateDirectory(levelPath, true);
++ 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(levelPath, list);
+ } else {
+- return new LevelStorageSource.LevelStorageAccess(saveName, levelPath);
++ return new LevelStorageSource.LevelStorageAccess(s, path, dimensionType); // CraftBukkit
+ }
+ }
+
+- public LevelStorageSource.LevelStorageAccess createAccess(String saveName) throws IOException {
+- Path levelPath = this.getLevelPath(saveName);
+- return new LevelStorageSource.LevelStorageAccess(saveName, levelPath);
++ public LevelStorageSource.LevelStorageAccess createAccess(String s, ResourceKey<LevelStem> dimensionType) throws IOException { // CraftBukkit
++ Path path = this.getLevelPath(s);
++
++ 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 static record a(List<LevelStorageSource.LevelDirectory> levels) implements Iterable<LevelStorageSource.LevelDirectory> {
++
+ public boolean isEmpty() {
+ return this.levels.isEmpty();
+ }
+@@ -433,11 +516,15 @@
+ 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 levelId, Path levelDir) throws IOException {
+- this.levelId = levelId;
+- this.levelDirectory = new LevelStorageSource.LevelDirectory(levelDir);
+- this.lock = DirectoryLock.create(levelDir);
++ 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);
+ }
+
+ public void safeClose() {
+@@ -465,7 +557,7 @@
+ }
+
+ public Path getDimensionPath(ResourceKey<Level> dimensionPath) {
+- return DimensionType.getStorageFolder(dimensionPath, this.levelDirectory.path());
++ return getStorageFolder(this.levelDirectory.path(), this.dimensionType); // CraftBukkit
+ }
+
+ private void checkLock() {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/PlayerDataStorage.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/PlayerDataStorage.java.patch
new file mode 100644
index 0000000000..c7d0a3a7fc
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/PlayerDataStorage.java.patch
@@ -0,0 +1,73 @@
+--- 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();
+ private final File playerDir;
+@@ -53,15 +65,42 @@
+ LOGGER.warn("Failed to load player data for {}", player.getName().getString());
+ }
+
+- if (compoundTag != null) {
+- int dataVersion = NbtUtils.getDataVersion(compoundTag, -1);
+- compoundTag = DataFixTypes.PLAYER.updateToCurrentVersion(this.fixerUpper, compoundTag, dataVersion);
+- player.load(compoundTag);
++ 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);
++
++ nbttagcompound = DataFixTypes.PLAYER.updateToCurrentVersion(this.fixerUpper, nbttagcompound, i);
++ player.load(nbttagcompound);
+ }
+
+ 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[] strings = this.playerDir.list();
+ if (strings == null) {
+@@ -76,4 +116,10 @@
+
+ return strings;
+ }
++
++ // CraftBukkit start
++ public File getPlayerDir() {
++ return playerDir;
++ }
++ // CraftBukkit end
+ }
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/PrimaryLevelData.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/PrimaryLevelData.java.patch
new file mode 100644
index 0000000000..6b00cebc70
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/PrimaryLevelData.java.patch
@@ -0,0 +1,177 @@
+--- a/net/minecraft/world/level/storage/PrimaryLevelData.java
++++ b/net/minecraft/world/level/storage/PrimaryLevelData.java
+@@ -40,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 {
+ private static final Logger LOGGER = LogUtils.getLogger();
+@@ -78,38 +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 i1,
+- int i2,
+- float f,
+- long l,
+- long l1,
+- int i3,
+- int i4,
+- int i5,
+- boolean flag1,
+- int i6,
+- boolean flag2,
+- boolean flag3,
+- boolean flag4,
+- WorldBorder.Settings settings,
+- int i7,
+- int i8,
+- @Nullable UUID uUID,
+- Set<String> set,
+- Set<String> set1,
+- TimerQueue<MinecraftServer> timerQueue,
+- @Nullable CompoundTag compoundTag1,
+- EndDragonFight.Data data,
+- LevelSettings levelSettings,
+- WorldOptions worldOptions,
+- PrimaryLevelData.SpecialWorldProperty 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 = i1;
+@@ -249,10 +206,14 @@
+ compoundTag.putString("Series", SharedConstants.getCurrentVersion().getDataVersion().getSeries());
+ nbt.put("Version", compoundTag);
+ NbtUtils.addCurrentDataVersion(nbt);
+- DynamicOps<Tag> dynamicOps = RegistryOps.create(NbtOps.INSTANCE, registry);
+- WorldGenSettings.encode(dynamicOps, this.worldOptions, registry)
+- .resultOrPartial(Util.prefix("WorldGenSettings: ", LOGGER::error))
+- .ifPresent(worldOptionsTag -> nbt.put("WorldGenSettings", worldOptionsTag));
++ 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);
++ dataresult.resultOrPartial(Util.prefix("WorldGenSettings: ", logger::error)).ifPresent((nbtbase) -> {
++ nbt.put("WorldGenSettings", nbtbase);
++ });
+ nbt.putInt("GameType", this.settings.gameType().getId());
+ nbt.putInt("SpawnX", this.xSpawn);
+ nbt.putInt("SpawnY", this.ySpawn);
+@@ -294,6 +258,9 @@
+ if (this.wanderingTraderId != null) {
+ nbt.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> stringCollection) {
+@@ -403,6 +373,20 @@
+
+ @Override
+ 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;
+ }
+
+@@ -423,6 +407,20 @@
+
+ @Override
+ 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;
+ }
+
+@@ -489,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
+@@ -625,6 +629,15 @@
+ 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 {
+ NONE,
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/loot/LootDataManager.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/loot/LootDataManager.java.patch
new file mode 100644
index 0000000000..738861faa0
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/loot/LootDataManager.java.patch
@@ -0,0 +1,29 @@
+--- a/net/minecraft/world/level/storage/loot/LootDataManager.java
++++ b/net/minecraft/world/level/storage/loot/LootDataManager.java
+@@ -88,10 +94,22 @@
+ return (T)map.get(id);
+ }
+ });
+- map.forEach((id, element) -> castAndValidate(validationContext, id, element));
+- collector.get().forEach((errorOwner, errorMessage) -> LOGGER.warn("Found loot table element validation problem in {}: {}", errorOwner, errorMessage));
+- this.elements = map;
+- this.typeKeys = builder1.build();
++
++ map1.forEach((lootdataid, object1) -> {
++ castAndValidate(lootcollector, lootdataid, object1);
++ });
++ problemreporter_a.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();
+ }
+
+ private static <T> void castAndValidate(ValidationContext context, LootDataId<T> id, Object element) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/loot/LootTable.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/loot/LootTable.java.patch
new file mode 100644
index 0000000000..378c4c09a4
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/loot/LootTable.java.patch
@@ -0,0 +1,65 @@
+--- a/net/minecraft/world/level/storage/loot/LootTable.java
++++ b/net/minecraft/world/level/storage/loot/LootTable.java
+@@ -27,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();
+ public static final LootTable EMPTY = new LootTable(LootContextParamSets.EMPTY, Optional.empty(), List.of(), List.of());
+@@ -45,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;
+@@ -134,15 +157,32 @@
+ }
+
+ public void fill(Container container, LootParams params, long seed) {
+- LootContext lootContext = new LootContext.Builder(params).withOptionalRandomSeed(seed).create(this.randomSequence);
+- ObjectArrayList<ItemStack> randomItems = this.getRandomItems(lootContext);
+- RandomSource random = lootContext.getRandom();
+- List<Integer> availableSlots = this.getAvailableSlots(container, random);
+- this.shuffleAndSplitItems(randomItems, availableSlots.size(), random);
++ // CraftBukkit start
++ this.fillInventory(container, params, seed, false);
++ }
+
+- for (ItemStack itemStack : randomItems) {
+- if (availableSlots.isEmpty()) {
+- LOGGER.warn("Tried to over-fill a container");
++ 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();
++
++ while (objectlistiterator.hasNext()) {
++ ItemStack itemstack = (ItemStack) objectlistiterator.next();
++
++ if (list.isEmpty()) {
++ LootTable.LOGGER.warn("Tried to over-fill a container");
+ return;
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/loot/functions/LootingEnchantFunction.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/loot/functions/LootingEnchantFunction.java.patch
new file mode 100644
index 0000000000..21d48a2880
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -58,8 +56,14 @@
+ public ItemStack run(ItemStack stack, LootContext context) {
+ Entity entity = context.getParamOrNull(LootContextParams.KILLER_ENTITY);
+ if (entity instanceof LivingEntity) {
+- int mobLooting = EnchantmentHelper.getMobLooting((LivingEntity)entity);
+- if (mobLooting == 0) {
++ 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) { // CraftBukkit - account for possible negative looting values from Bukkit
+ return stack;
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/loot/parameters/LootContextParams.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/loot/parameters/LootContextParams.java.patch
new file mode 100644
index 0000000000..e4f512167f
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -20,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
+
+ private static <T> LootContextParam<T> create(String id) {
+ return new LootContextParam<>(new ResourceLocation(id));
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/loot/predicates/ExplosionCondition.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/loot/predicates/ExplosionCondition.java.patch
new file mode 100644
index 0000000000..772c6496e2
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/loot/predicates/ExplosionCondition.java.patch
@@ -0,0 +1,22 @@
+--- a/net/minecraft/world/level/storage/loot/predicates/ExplosionCondition.java
++++ b/net/minecraft/world/level/storage/loot/predicates/ExplosionCondition.java
+@@ -27,11 +26,14 @@
+
+ @Override
+ public boolean test(LootContext context) {
+- Float _float = context.getParamOrNull(LootContextParams.EXPLOSION_RADIUS);
+- if (_float != null) {
+- RandomSource random = context.getRandom();
+- float f = 1.0F / _float;
+- return random.nextFloat() <= f;
++ Float ofloat = (Float) context.getParamOrNull(LootContextParams.EXPLOSION_RADIUS);
++
++ if (ofloat != null) {
++ RandomSource randomsource = context.getRandom();
++ float f = 1.0F / ofloat;
++
++ // 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-vineflower-stripped/net/minecraft/world/level/storage/loot/predicates/LootItemRandomChanceWithLootingCondition.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/world/level/storage/loot/predicates/LootItemRandomChanceWithLootingCondition.java.patch
new file mode 100644
index 0000000000..7181274268
--- /dev/null
+++ b/patch-remap/mache-vineflower-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
+@@ -38,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 context.getRandom().nextFloat() < this.percent + (float)i * this.lootingMultiplier;
+ }