--- a/net/minecraft/world/entity/decoration/ItemFrame.java
+++ b/net/minecraft/world/entity/decoration/ItemFrame.java
@@ -41,14 +_,15 @@
 import org.apache.commons.lang3.Validate;
 
 public class ItemFrame extends HangingEntity {
-    private static final EntityDataAccessor<ItemStack> DATA_ITEM = SynchedEntityData.defineId(ItemFrame.class, EntityDataSerializers.ITEM_STACK);
-    private static final EntityDataAccessor<Integer> DATA_ROTATION = SynchedEntityData.defineId(ItemFrame.class, EntityDataSerializers.INT);
+    public static final EntityDataAccessor<ItemStack> DATA_ITEM = SynchedEntityData.defineId(ItemFrame.class, EntityDataSerializers.ITEM_STACK);
+    public static final EntityDataAccessor<Integer> DATA_ROTATION = SynchedEntityData.defineId(ItemFrame.class, EntityDataSerializers.INT);
     public static final int NUM_ROTATIONS = 8;
     private static final float DEPTH = 0.0625F;
     private static final float WIDTH = 0.75F;
     private static final float HEIGHT = 0.75F;
-    private float dropChance = 1.0F;
-    private boolean fixed;
+    public float dropChance = 1.0F;
+    public boolean fixed;
+    public @Nullable MapId cachedMapId; // Paper - Perf: Cache map ids on item frames
 
     public ItemFrame(EntityType<? extends ItemFrame> entityType, Level level) {
         super(entityType, level);
@@ -70,7 +_,7 @@
     }
 
     @Override
-    protected void setDirection(Direction facingDirection) {
+    public void setDirection(Direction facingDirection) {
         Validate.notNull(facingDirection);
         this.direction = facingDirection;
         if (facingDirection.getAxis().isHorizontal()) {
@@ -88,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();
@@ -118,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
         }
     }
 
@@ -149,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);
@@ -234,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);
@@ -248,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);
         }
 
@@ -280,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);
         }
@@ -359,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;
@@ -368,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);
