--- a/net/minecraft/world/entity/decoration/ItemFrame.java
+++ b/net/minecraft/world/entity/decoration/ItemFrame.java
@@ -54,6 +_,7 @@
     private static final boolean DEFAULT_FIXED = false;
     public float dropChance = 1.0F;
     public boolean fixed = false;
+    public @Nullable MapId cachedMapId; // Paper - Perf: Cache map ids on item frames
 
     public ItemFrame(EntityType<? extends ItemFrame> entityType, Level level) {
         super(entityType, level);
@@ -102,6 +_,12 @@
 
     @Override
     protected AABB calculateBoundingBox(BlockPos pos, Direction direction) {
+        // CraftBukkit start - break out BB calc into own method
+        return ItemFrame.calculateBoundingBoxStatic(pos, direction);
+    }
+
+    public static AABB calculateBoundingBoxStatic(BlockPos pos, Direction direction) {
+        // CraftBukkit end
         float f = 0.46875F;
         Vec3 vec3 = Vec3.atCenterOf(pos).relative(direction, -0.46875);
         Direction.Axis axis = direction.getAxis();
@@ -132,9 +_,9 @@
     }
 
     @Override
-    public void push(double x, double y, double z) {
+    public void push(double x, double y, double z, @Nullable Entity pushingEntity) { // Paper - add push source entity param
         if (!this.fixed) {
-            super.push(x, y, z);
+            super.push(x, y, z, pushingEntity); // Paper - add push source entity param
         }
     }
 
@@ -163,6 +_,18 @@
             if (this.isInvulnerableToBase(damageSource)) {
                 return false;
             } else if (this.shouldDamageDropItem(damageSource)) {
+                // CraftBukkit start - fire EntityDamageEvent
+                if (org.bukkit.craftbukkit.event.CraftEventFactory.handleNonLivingEntityDamageEvent(this, damageSource, amount, false) || this.isRemoved()) {
+                    return true;
+                }
+                // CraftBukkit end
+                // Paper start - Add PlayerItemFrameChangeEvent
+                if (damageSource.getEntity() instanceof Player player) {
+                    var event = new io.papermc.paper.event.player.PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), this.getItem().asBukkitCopy(), io.papermc.paper.event.player.PlayerItemFrameChangeEvent.ItemFrameChangeAction.REMOVE);
+                    if (!event.callEvent()) return true; // return true here because you aren't cancelling the damage, just the change
+                    this.setItem(ItemStack.fromBukkitCopy(event.getItemStack()), false);
+                }
+                // Paper end - Add PlayerItemFrameChangeEvent
                 this.dropItem(level, damageSource.getEntity(), false);
                 this.gameEvent(GameEvent.BLOCK_CHANGE, damageSource.getEntity());
                 this.playSound(this.getRemoveItemSound(), 1.0F, 1.0F);
@@ -248,6 +_,14 @@
         return this.getEntityData().get(DATA_ITEM);
     }
 
+    // Paper start - Fix MC-123848 (spawn item frame drops above block)
+    @Nullable
+    @Override
+    public net.minecraft.world.entity.item.ItemEntity spawnAtLocation(ServerLevel serverLevel, ItemStack stack) {
+        return this.spawnAtLocation(serverLevel, stack, this.getDirection() == Direction.DOWN ? -0.6F : 0.0F);
+    }
+    // Paper end
+
     @Nullable
     public MapId getFramedMapId(ItemStack stack) {
         return stack.get(DataComponents.MAP_ID);
@@ -262,13 +_,19 @@
     }
 
     public void setItem(ItemStack stack, boolean updateNeighbours) {
+        // CraftBukkit start
+        this.setItem(stack, updateNeighbours, true);
+    }
+
+    public void setItem(ItemStack stack, boolean updateNeighbours, boolean playSound) {
+        // CraftBukkit end
         if (!stack.isEmpty()) {
             stack = stack.copyWithCount(1);
         }
 
         this.onItemChanged(stack);
         this.getEntityData().set(DATA_ITEM, stack);
-        if (!stack.isEmpty()) {
+        if (!stack.isEmpty() && updateNeighbours && playSound) { // CraftBukkit // Paper - only play sound when update flag is set
             this.playSound(this.getAddItemSound(), 1.0F, 1.0F);
         }
 
@@ -295,6 +_,7 @@
     }
 
     private void onItemChanged(ItemStack item) {
+        this.cachedMapId = item.getComponents().get(net.minecraft.core.component.DataComponents.MAP_ID); // Paper - Perf: Cache map ids on item frames
         if (!item.isEmpty() && item.getFrame() != this) {
             item.setEntityRepresentation(this);
         }
@@ -363,7 +_,13 @@
                     if (savedData != null && savedData.isTrackedCountOverLimit(256)) {
                         return InteractionResult.FAIL;
                     } else {
-                        this.setItem(itemInHand);
+                        // Paper start - Add PlayerItemFrameChangeEvent
+                        io.papermc.paper.event.player.PlayerItemFrameChangeEvent event = new io.papermc.paper.event.player.PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), itemInHand.asBukkitCopy(), io.papermc.paper.event.player.PlayerItemFrameChangeEvent.ItemFrameChangeAction.PLACE);
+                        if (!event.callEvent()) {
+                            return InteractionResult.FAIL;
+                        }
+                        this.setItem(ItemStack.fromBukkitCopy(event.getItemStack()));
+                        // Paper end - Add PlayerItemFrameChangeEvent
                         this.gameEvent(GameEvent.BLOCK_CHANGE, player);
                         itemInHand.consume(1, player);
                         return InteractionResult.SUCCESS;
@@ -372,6 +_,13 @@
                     return InteractionResult.PASS;
                 }
             } else {
+                // Paper start - Add PlayerItemFrameChangeEvent
+                io.papermc.paper.event.player.PlayerItemFrameChangeEvent event = new io.papermc.paper.event.player.PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), this.getItem().asBukkitCopy(), io.papermc.paper.event.player.PlayerItemFrameChangeEvent.ItemFrameChangeAction.ROTATE);
+                if (!event.callEvent()) {
+                    return InteractionResult.FAIL;
+                }
+                setItem(ItemStack.fromBukkitCopy(event.getItemStack()), false, false);
+                // Paper end - Add PlayerItemFrameChangeEvent
                 this.playSound(this.getRotateItemSound(), 1.0F, 1.0F);
                 this.setRotation(this.getRotation() + 1);
                 this.gameEvent(GameEvent.BLOCK_CHANGE, player);
