--- a/net/minecraft/world/entity/decoration/ItemFrame.java
+++ b/net/minecraft/world/entity/decoration/ItemFrame.java
@@ -1,6 +1,7 @@
 package net.minecraft.world.entity.decoration;
 
 import javax.annotation.Nullable;
+import io.papermc.paper.event.player.PlayerItemFrameChangeEvent; // Paper - Add PlayerItemFrameChangeEvent
 import net.minecraft.core.BlockPos;
 import net.minecraft.core.Direction;
 import net.minecraft.core.component.DataComponents;
@@ -40,17 +41,20 @@
 import org.apache.commons.lang3.Validate;
 
 public class ItemFrame extends HangingEntity {
+
     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;
-    public float dropChance = 1.0F;
+    public float dropChance;
     public boolean fixed;
+    public @Nullable MapId cachedMapId; // Paper - Perf: Cache map ids on item frames
 
     public ItemFrame(EntityType<? extends ItemFrame> type, Level world) {
         super(type, world);
+        this.dropChance = 1.0F;
     }
 
     public ItemFrame(Level world, BlockPos pos, Direction facing) {
@@ -59,13 +63,14 @@
 
     public ItemFrame(EntityType<? extends ItemFrame> type, Level world, BlockPos pos, Direction facing) {
         super(type, world, pos);
+        this.dropChance = 1.0F;
         this.setDirection(facing);
     }
 
     @Override
     protected void defineSynchedData(SynchedEntityData.Builder builder) {
-        builder.define(DATA_ITEM, ItemStack.EMPTY);
-        builder.define(DATA_ROTATION, 0);
+        builder.define(ItemFrame.DATA_ITEM, ItemStack.EMPTY);
+        builder.define(ItemFrame.DATA_ROTATION, 0);
     }
 
     @Override
@@ -74,9 +79,9 @@
         this.direction = facing;
         if (facing.getAxis().isHorizontal()) {
             this.setXRot(0.0F);
-            this.setYRot((float)(this.direction.get2DDataValue() * 90));
+            this.setYRot((float) (this.direction.get2DDataValue() * 90));
         } else {
-            this.setXRot((float)(-90 * facing.getAxisDirection().getStep()));
+            this.setXRot((float) (-90 * facing.getAxisDirection().getStep()));
             this.setYRot(0.0F);
         }
 
@@ -87,25 +92,32 @@
 
     @Override
     protected AABB calculateBoundingBox(BlockPos pos, Direction side) {
+        // CraftBukkit start - break out BB calc into own method
+        return ItemFrame.calculateBoundingBoxStatic(pos, side);
+    }
+
+    public static AABB calculateBoundingBoxStatic(BlockPos blockposition, Direction enumdirection) {
+        // CraftBukkit end
         float f = 0.46875F;
-        Vec3 vec3 = Vec3.atCenterOf(pos).relative(side, -0.46875);
-        Direction.Axis axis = side.getAxis();
-        double d = axis == Direction.Axis.X ? 0.0625 : 0.75;
-        double e = axis == Direction.Axis.Y ? 0.0625 : 0.75;
-        double g = axis == Direction.Axis.Z ? 0.0625 : 0.75;
-        return AABB.ofSize(vec3, d, e, g);
+        Vec3 vec3d = Vec3.atCenterOf(blockposition).relative(enumdirection, -0.46875D);
+        Direction.Axis enumdirection_enumaxis = enumdirection.getAxis();
+        double d0 = enumdirection_enumaxis == Direction.Axis.X ? 0.0625D : 0.75D;
+        double d1 = enumdirection_enumaxis == Direction.Axis.Y ? 0.0625D : 0.75D;
+        double d2 = enumdirection_enumaxis == Direction.Axis.Z ? 0.0625D : 0.75D;
+
+        return AABB.ofSize(vec3d, d0, d1, d2);
     }
 
     @Override
     public boolean survives() {
         if (this.fixed) {
             return true;
-        } else if (!this.level().noCollision(this)) {
+        } else if (!this.level().noCollision((Entity) this)) {
             return false;
         } else {
-            BlockState blockState = this.level().getBlockState(this.pos.relative(this.direction.getOpposite()));
-            return (blockState.isSolid() || this.direction.getAxis().isHorizontal() && DiodeBlock.isDiode(blockState))
-                && this.level().getEntities(this, this.getBoundingBox(), HANGING_ENTITY).isEmpty();
+            BlockState iblockdata = this.level().getBlockState(this.pos.relative(this.direction.getOpposite()));
+
+            return !iblockdata.isSolid() && (!this.direction.getAxis().isHorizontal() || !DiodeBlock.isDiode(iblockdata)) ? false : this.level().getEntities((Entity) this, this.getBoundingBox(), ItemFrame.HANGING_ENTITY).isEmpty();
         }
     }
 
@@ -114,13 +126,15 @@
         if (!this.fixed) {
             super.move(movementType, movement);
         }
+
     }
 
     @Override
-    public void push(double deltaX, double deltaY, double deltaZ) {
+    public void push(double deltaX, double deltaY, double deltaZ, @org.jetbrains.annotations.Nullable Entity pushingEntity) { // Paper - add push source entity param
         if (!this.fixed) {
-            super.push(deltaX, deltaY, deltaZ);
+            super.push(deltaX, deltaY, deltaZ, pushingEntity); // Paper - add push source entity param
         }
+
     }
 
     @Override
@@ -132,11 +146,23 @@
     @Override
     public boolean hurt(DamageSource source, float amount) {
         if (this.fixed) {
-            return (source.is(DamageTypeTags.BYPASSES_INVULNERABILITY) || source.isCreativePlayer()) && super.hurt(source, amount);
+            return !source.is(DamageTypeTags.BYPASSES_INVULNERABILITY) && !source.isCreativePlayer() ? false : super.hurt(source, amount);
         } else if (this.isInvulnerableTo(source)) {
             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
+                // Paper start - Add PlayerItemFrameChangeEvent
+                if (source.getEntity() instanceof Player player) {
+                    var event = new PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), this.getItem().asBukkitCopy(), 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(source.getEntity(), false);
                 this.gameEvent(GameEvent.BLOCK_CHANGE, source.getEntity());
                 this.playSound(this.getRemoveItemSound(), 1.0F, 1.0F);
@@ -154,9 +180,10 @@
 
     @Override
     public boolean shouldRenderAtSqrDistance(double distance) {
-        double d = 16.0;
-        d *= 64.0 * getViewScale();
-        return distance < d * d;
+        double d1 = 16.0D;
+
+        d1 *= 64.0D * getViewScale();
+        return distance < d1 * d1;
     }
 
     @Override
@@ -181,53 +208,70 @@
 
     private void dropItem(@Nullable Entity entity, boolean alwaysDrop) {
         if (!this.fixed) {
-            ItemStack itemStack = this.getItem();
+            ItemStack itemstack = this.getItem();
+
             this.setItem(ItemStack.EMPTY);
             if (!this.level().getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
                 if (entity == null) {
-                    this.removeFramedMap(itemStack);
+                    this.removeFramedMap(itemstack);
                 }
+
             } else {
-                if (entity instanceof Player player && player.hasInfiniteMaterials()) {
-                    this.removeFramedMap(itemStack);
-                    return;
+                if (entity instanceof Player) {
+                    Player entityhuman = (Player) entity;
+
+                    if (entityhuman.hasInfiniteMaterials()) {
+                        this.removeFramedMap(itemstack);
+                        return;
+                    }
                 }
 
                 if (alwaysDrop) {
                     this.spawnAtLocation(this.getFrameItemStack());
                 }
 
-                if (!itemStack.isEmpty()) {
-                    itemStack = itemStack.copy();
-                    this.removeFramedMap(itemStack);
+                if (!itemstack.isEmpty()) {
+                    itemstack = itemstack.copy();
+                    this.removeFramedMap(itemstack);
                     if (this.random.nextFloat() < this.dropChance) {
-                        this.spawnAtLocation(itemStack);
+                        this.spawnAtLocation(itemstack);
                     }
                 }
+
             }
         }
     }
 
     private void removeFramedMap(ItemStack stack) {
-        MapId mapId = this.getFramedMapId(stack);
-        if (mapId != null) {
-            MapItemSavedData mapItemSavedData = MapItem.getSavedData(mapId, this.level());
-            if (mapItemSavedData != null) {
-                mapItemSavedData.removedFromFrame(this.pos, this.getId());
-                mapItemSavedData.setDirty(true);
+        MapId mapid = this.getFramedMapId(stack);
+
+        if (mapid != null) {
+            MapItemSavedData worldmap = MapItem.getSavedData(mapid, this.level());
+
+            if (worldmap != null) {
+                worldmap.removedFromFrame(this.pos, this.getId());
+                worldmap.setDirty(true);
             }
         }
 
-        stack.setEntityRepresentation(null);
+        stack.setEntityRepresentation((Entity) null);
     }
 
     public ItemStack getItem() {
-        return this.getEntityData().get(DATA_ITEM);
+        return (ItemStack) this.getEntityData().get(ItemFrame.DATA_ITEM);
+    }
+
+    // Paper start - Fix MC-123848 (spawn item frame drops above block)
+    @Nullable
+    @Override
+    public net.minecraft.world.entity.item.ItemEntity spawnAtLocation(ItemStack stack) {
+        return this.spawnAtLocation(stack, getDirection().equals(Direction.DOWN) ? -0.6F : 0.0F);
     }
+    // Paper end
 
     @Nullable
-    public MapId getFramedMapId(ItemStack itemStack) {
-        return itemStack.get(DataComponents.MAP_ID);
+    public MapId getFramedMapId(ItemStack itemstack) {
+        return (MapId) itemstack.get(DataComponents.MAP_ID);
     }
 
     public boolean hasFramedMap() {
@@ -239,19 +283,26 @@
     }
 
     public void setItem(ItemStack value, boolean update) {
-        if (!value.isEmpty()) {
-            value = value.copyWithCount(1);
+        // CraftBukkit start
+        this.setItem(value, update, true);
+    }
+
+    public void setItem(ItemStack itemstack, boolean flag, boolean playSound) {
+        // CraftBukkit end
+        if (!itemstack.isEmpty()) {
+            itemstack = itemstack.copyWithCount(1);
         }
 
-        this.onItemChanged(value);
-        this.getEntityData().set(DATA_ITEM, value);
-        if (!value.isEmpty()) {
+        this.onItemChanged(itemstack);
+        this.getEntityData().set(ItemFrame.DATA_ITEM, itemstack);
+        if (!itemstack.isEmpty() && flag && playSound) { // CraftBukkit // Paper - only play sound when update flag is set
             this.playSound(this.getAddItemSound(), 1.0F, 1.0F);
         }
 
-        if (update && this.pos != null) {
+        if (flag && this.pos != null) {
             this.level().updateNeighbourForOutputSignal(this.pos, Blocks.AIR);
         }
+
     }
 
     public SoundEvent getAddItemSound() {
@@ -265,12 +316,14 @@
 
     @Override
     public void onSyncedDataUpdated(EntityDataAccessor<?> data) {
-        if (data.equals(DATA_ITEM)) {
+        if (data.equals(ItemFrame.DATA_ITEM)) {
             this.onItemChanged(this.getItem());
         }
+
     }
 
     private void onItemChanged(ItemStack stack) {
+        this.cachedMapId = stack.getComponents().get(net.minecraft.core.component.DataComponents.MAP_ID); // Paper - Perf: Cache map ids on item frames
         if (!stack.isEmpty() && stack.getFrame() != this) {
             stack.setEntityRepresentation(this);
         }
@@ -279,7 +332,7 @@
     }
 
     public int getRotation() {
-        return this.getEntityData().get(DATA_ROTATION);
+        return (Integer) this.getEntityData().get(ItemFrame.DATA_ROTATION);
     }
 
     public void setRotation(int value) {
@@ -287,10 +340,11 @@
     }
 
     private void setRotation(int value, boolean updateComparators) {
-        this.getEntityData().set(DATA_ROTATION, value % 8);
+        this.getEntityData().set(ItemFrame.DATA_ROTATION, value % 8);
         if (updateComparators && this.pos != null) {
             this.level().updateNeighbourForOutputSignal(this.pos, Blocks.AIR);
         }
+
     }
 
     @Override
@@ -298,11 +352,11 @@
         super.addAdditionalSaveData(nbt);
         if (!this.getItem().isEmpty()) {
             nbt.put("Item", this.getItem().save(this.registryAccess()));
-            nbt.putByte("ItemRotation", (byte)this.getRotation());
+            nbt.putByte("ItemRotation", (byte) this.getRotation());
             nbt.putFloat("ItemDropChance", this.dropChance);
         }
 
-        nbt.putByte("Facing", (byte)this.direction.get3DDataValue());
+        nbt.putByte("Facing", (byte) this.direction.get3DDataValue());
         nbt.putBoolean("Invisible", this.isInvisible());
         nbt.putBoolean("Fixed", this.fixed);
     }
@@ -310,21 +364,24 @@
     @Override
     public void readAdditionalSaveData(CompoundTag nbt) {
         super.readAdditionalSaveData(nbt);
-        ItemStack itemStack;
+        ItemStack itemstack;
+
         if (nbt.contains("Item", 10)) {
-            CompoundTag compoundTag = nbt.getCompound("Item");
-            itemStack = ItemStack.parse(this.registryAccess(), compoundTag).orElse(ItemStack.EMPTY);
+            CompoundTag nbttagcompound1 = nbt.getCompound("Item");
+
+            itemstack = (ItemStack) ItemStack.parse(this.registryAccess(), nbttagcompound1).orElse(ItemStack.EMPTY);
         } else {
-            itemStack = ItemStack.EMPTY;
+            itemstack = ItemStack.EMPTY;
         }
 
-        ItemStack itemStack3 = this.getItem();
-        if (!itemStack3.isEmpty() && !ItemStack.matches(itemStack, itemStack3)) {
-            this.removeFramedMap(itemStack3);
+        ItemStack itemstack1 = this.getItem();
+
+        if (!itemstack1.isEmpty() && !ItemStack.matches(itemstack, itemstack1)) {
+            this.removeFramedMap(itemstack1);
         }
 
-        this.setItem(itemStack, false);
-        if (!itemStack.isEmpty()) {
+        this.setItem(itemstack, false);
+        if (!itemstack.isEmpty()) {
             this.setRotation(nbt.getByte("ItemRotation"), false);
             if (nbt.contains("ItemDropChance", 99)) {
                 this.dropChance = nbt.getFloat("ItemDropChance");
@@ -338,26 +395,41 @@
 
     @Override
     public InteractionResult interact(Player player, InteractionHand hand) {
-        ItemStack itemStack = player.getItemInHand(hand);
-        boolean bl = !this.getItem().isEmpty();
-        boolean bl2 = !itemStack.isEmpty();
+        ItemStack itemstack = player.getItemInHand(hand);
+        boolean flag = !this.getItem().isEmpty();
+        boolean flag1 = !itemstack.isEmpty();
+
         if (this.fixed) {
             return InteractionResult.PASS;
         } else if (!this.level().isClientSide) {
-            if (!bl) {
-                if (bl2 && !this.isRemoved()) {
-                    if (itemStack.is(Items.FILLED_MAP)) {
-                        MapItemSavedData mapItemSavedData = MapItem.getSavedData(itemStack, this.level());
-                        if (mapItemSavedData != null && mapItemSavedData.isTrackedCountOverLimit(256)) {
+            if (!flag) {
+                if (flag1 && !this.isRemoved()) {
+                    if (itemstack.is(Items.FILLED_MAP)) {
+                        MapItemSavedData worldmap = MapItem.getSavedData(itemstack, this.level());
+
+                        if (worldmap != null && worldmap.isTrackedCountOverLimit(256)) {
                             return InteractionResult.FAIL;
                         }
                     }
 
-                    this.setItem(itemStack);
+                    // Paper start - Add PlayerItemFrameChangeEvent
+                    PlayerItemFrameChangeEvent event = new PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), itemstack.asBukkitCopy(), 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);
-                    itemStack.consume(1, player);
+                    itemstack.consume(1, player);
                 }
             } else {
+                // Paper start - Add PlayerItemFrameChangeEvent
+                PlayerItemFrameChangeEvent event = new PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), this.getItem().asBukkitCopy(), 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);
@@ -365,7 +437,7 @@
 
             return InteractionResult.CONSUME;
         } else {
-            return !bl && !bl2 ? InteractionResult.PASS : InteractionResult.SUCCESS;
+            return !flag && !flag1 ? InteractionResult.PASS : InteractionResult.SUCCESS;
         }
     }
 
@@ -390,8 +462,9 @@
 
     @Override
     public ItemStack getPickResult() {
-        ItemStack itemStack = this.getItem();
-        return itemStack.isEmpty() ? this.getFrameItemStack() : itemStack.copy();
+        ItemStack itemstack = this.getItem();
+
+        return itemstack.isEmpty() ? this.getFrameItemStack() : itemstack.copy();
     }
 
     protected ItemStack getFrameItemStack() {
@@ -400,8 +473,9 @@
 
     @Override
     public float getVisualRotationYInDegrees() {
-        Direction direction = this.getDirection();
-        int i = direction.getAxis().isVertical() ? 90 * direction.getAxisDirection().getStep() : 0;
-        return (float)Mth.wrapDegrees(180 + direction.get2DDataValue() * 90 + this.getRotation() * 45 + i);
+        Direction enumdirection = this.getDirection();
+        int i = enumdirection.getAxis().isVertical() ? 90 * enumdirection.getAxisDirection().getStep() : 0;
+
+        return (float) Mth.wrapDegrees(180 + enumdirection.get2DDataValue() * 90 + this.getRotation() * 45 + i);
     }
 }
