--- a/net/minecraft/world/inventory/LoomMenu.java
+++ b/net/minecraft/world/inventory/LoomMenu.java
@@ -12,69 +12,110 @@
 import net.minecraft.world.Container;
 import net.minecraft.world.SimpleContainer;
 import net.minecraft.world.entity.player.Inventory;
-import net.minecraft.world.entity.player.Player;
 import net.minecraft.world.item.BannerItem;
 import net.minecraft.world.item.BannerPatternItem;
 import net.minecraft.world.item.DyeColor;
 import net.minecraft.world.item.DyeItem;
+import net.minecraft.world.item.Item;
 import net.minecraft.world.item.ItemStack;
 import net.minecraft.world.level.block.Blocks;
 import net.minecraft.world.level.block.entity.BannerPattern;
 import net.minecraft.world.level.block.entity.BannerPatternLayers;
+// 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 (this.bukkitEntity != null) {
+            return this.bukkitEntity;
+        }
+
+        CraftInventoryLoom inventory = new CraftInventoryLoom(this.inputContainer, this.outputContainer);
+        this.bukkitEntity = new CraftInventoryView(this.player, inventory, this);
+        return this.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;
     private static final int USE_ROW_SLOT_START = 31;
     private static final int USE_ROW_SLOT_END = 40;
     private final ContainerLevelAccess access;
-    final DataSlot selectedBannerPatternIndex = DataSlot.standalone();
-    private List<Holder<BannerPattern>> selectablePatterns = List.of();
-    Runnable slotUpdateListener = () -> {
-    };
+    final DataSlot selectedBannerPatternIndex;
+    private List<Holder<BannerPattern>> selectablePatterns;
+    Runnable slotUpdateListener;
     private final HolderGetter<BannerPattern> patternGetter;
     final Slot bannerSlot;
     final Slot dyeSlot;
     private final Slot patternSlot;
     private final Slot resultSlot;
     long lastSoundTime;
-    private final Container inputContainer = new SimpleContainer(3) {
-        @Override
-        public void setChanged() {
-            super.setChanged();
-            LoomMenu.this.slotsChanged(this);
-            LoomMenu.this.slotUpdateListener.run();
-        }
-    };
-    private final Container outputContainer = new SimpleContainer(1) {
-        @Override
-        public void setChanged() {
-            super.setChanged();
-            LoomMenu.this.slotUpdateListener.run();
-        }
-    };
+    private final Container inputContainer;
+    private final Container outputContainer;
 
     public LoomMenu(int syncId, Inventory playerInventory) {
         this(syncId, playerInventory, ContainerLevelAccess.NULL);
     }
 
-    public LoomMenu(int syncId, Inventory playerInventory, ContainerLevelAccess context) {
+    public LoomMenu(int syncId, Inventory playerInventory, final ContainerLevelAccess context) {
         super(MenuType.LOOM, syncId);
+        this.selectedBannerPatternIndex = DataSlot.standalone();
+        this.selectablePatterns = List.of();
+        this.slotUpdateListener = () -> {
+        };
+        this.inputContainer = new SimpleContainer(this.createBlockHolder(context), 3) { // Paper - Add missing InventoryHolders
+            @Override
+            public void setChanged() {
+                super.setChanged();
+                LoomMenu.this.slotsChanged(this);
+                LoomMenu.this.slotUpdateListener.run();
+            }
+
+            // CraftBukkit start
+            @Override
+            public Location getLocation() {
+                return context.getLocation();
+            }
+            // CraftBukkit end
+        };
+        this.outputContainer = new SimpleContainer(this.createBlockHolder(context), 1) { // Paper - Add missing InventoryHolders
+            @Override
+            public void setChanged() {
+                super.setChanged();
+                LoomMenu.this.slotUpdateListener.run();
+            }
+
+            // CraftBukkit start
+            @Override
+            public Location getLocation() {
+                return context.getLocation();
+            }
+            // CraftBukkit end
+        };
         this.access = context;
-        this.bannerSlot = this.addSlot(new Slot(this.inputContainer, 0, 13, 26) {
+        this.bannerSlot = this.addSlot(new Slot(this.inputContainer, 0, 13, 26) { // CraftBukkit - decompile error
             @Override
             public boolean mayPlace(ItemStack stack) {
                 return stack.getItem() instanceof BannerItem;
             }
         });
-        this.dyeSlot = this.addSlot(new Slot(this.inputContainer, 1, 33, 26) {
+        this.dyeSlot = this.addSlot(new Slot(this.inputContainer, 1, 33, 26) { // CraftBukkit - decompile error
             @Override
             public boolean mayPlace(ItemStack stack) {
                 return stack.getItem() instanceof DyeItem;
             }
         });
-        this.patternSlot = this.addSlot(new Slot(this.inputContainer, 2, 23, 45) {
+        this.patternSlot = this.addSlot(new Slot(this.inputContainer, 2, 23, 45) { // CraftBukkit - decompile error
             @Override
             public boolean mayPlace(ItemStack stack) {
                 return stack.getItem() instanceof BannerPatternItem;
@@ -87,48 +128,94 @@
             }
 
             @Override
-            public void onTake(Player player, ItemStack stack) {
+            public void onTake(net.minecraft.world.entity.player.Player player, ItemStack stack) {
                 LoomMenu.this.bannerSlot.remove(1);
                 LoomMenu.this.dyeSlot.remove(1);
                 if (!LoomMenu.this.bannerSlot.hasItem() || !LoomMenu.this.dyeSlot.hasItem()) {
                     LoomMenu.this.selectedBannerPatternIndex.set(-1);
                 }
 
-                context.execute((world, pos) -> {
-                    long l = world.getGameTime();
-                    if (LoomMenu.this.lastSoundTime != l) {
-                        world.playSound(null, pos, SoundEvents.UI_LOOM_TAKE_RESULT, SoundSource.BLOCKS, 1.0F, 1.0F);
-                        LoomMenu.this.lastSoundTime = l;
+                context.execute((world, blockposition) -> {
+                    long j = world.getGameTime();
+
+                    if (LoomMenu.this.lastSoundTime != j) {
+                        world.playSound((net.minecraft.world.entity.player.Player) null, blockposition, SoundEvents.UI_LOOM_TAKE_RESULT, SoundSource.BLOCKS, 1.0F, 1.0F);
+                        LoomMenu.this.lastSoundTime = j;
                     }
+
                 });
                 super.onTake(player, stack);
             }
         });
 
-        for (int i = 0; i < 3; i++) {
-            for (int j = 0; j < 9; j++) {
-                this.addSlot(new Slot(playerInventory, j + i * 9 + 9, 8 + j * 18, 84 + i * 18));
+        int j;
+
+        for (j = 0; j < 3; ++j) {
+            for (int k = 0; k < 9; ++k) {
+                this.addSlot(new Slot(playerInventory, k + j * 9 + 9, 8 + k * 18, 84 + j * 18));
             }
         }
 
-        for (int k = 0; k < 9; k++) {
-            this.addSlot(new Slot(playerInventory, k, 8 + k * 18, 142));
+        for (j = 0; j < 9; ++j) {
+            this.addSlot(new Slot(playerInventory, j, 8 + j * 18, 142));
         }
 
         this.addDataSlot(this.selectedBannerPatternIndex);
         this.patternGetter = playerInventory.player.registryAccess().lookupOrThrow(Registries.BANNER_PATTERN);
+        this.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);
     }
 
+    private static final org.slf4j.Logger LOGGER = com.mojang.logging.LogUtils.getLogger(); // Paper - handle custom banner pattern, skip the event
+    private static boolean PRINTED_PATTERN_TYPE_NAG = false; // Paper - handle custom banner pattern, skip the event
+
     @Override
-    public boolean clickMenuButton(Player player, int id) {
+    public boolean clickMenuButton(net.minecraft.world.entity.player.Player player, int id) {
         if (id >= 0 && id < this.selectablePatterns.size()) {
-            this.selectedBannerPatternIndex.set(id);
-            this.setupResultSlot(this.selectablePatterns.get(id));
+            // Paper start - handle custom banner pattern, skip the event (todo remove once this is supported)
+            java.util.Optional<org.bukkit.block.banner.PatternType> patternType = org.bukkit.craftbukkit.CraftRegistry.unwrapAndConvertHolder(org.bukkit.Registry.BANNER_PATTERN, this.selectablePatterns.get(id));
+            if (patternType.isEmpty()) {
+                if (!PRINTED_PATTERN_TYPE_NAG) {
+                    LOGGER.warn("A datapack added a custom banner pattern, those are not supported yet in the API, skipping the PlayerLoomPatternSelectEvent for {}.", player.getScoreboardName());
+                    PRINTED_PATTERN_TYPE_NAG = true;
+                }
+                this.selectedBannerPatternIndex.set(id);
+                this.setupResultSlot((Holder) this.selectablePatterns.get(id));
+                return true;
+            }
+            // Paper end - handle custom banner pattern
+
+            // Paper start - Add PlayerLoomPatternSelectEvent
+            int selectablePatternIndex = id;
+            io.papermc.paper.event.player.PlayerLoomPatternSelectEvent event = new io.papermc.paper.event.player.PlayerLoomPatternSelectEvent((Player) player.getBukkitEntity(), ((CraftInventoryLoom) getBukkitView().getTopInventory()), patternType.get());
+            if (!event.callEvent()) {
+                player.containerMenu.sendAllDataToRemote();
+                return false;
+            }
+            final Holder<BannerPattern> eventPattern = org.bukkit.craftbukkit.block.banner.CraftPatternType.bukkitToMinecraftHolder(event.getPatternType());
+            Holder<BannerPattern> selectedPattern = null;
+            for (int i = 0; i < this.selectablePatterns.size(); i++) {
+                final Holder<BannerPattern> holder = this.selectablePatterns.get(i);
+                if (eventPattern.equals(holder)) {
+                    selectablePatternIndex = i;
+                    selectedPattern = holder;
+                    break;
+                }
+            }
+            if (selectedPattern == null) {
+                selectedPattern = eventPattern;
+                selectablePatternIndex = -1;
+            }
+
+            player.containerMenu.sendAllDataToRemote();
+            this.selectedBannerPatternIndex.set(selectablePatternIndex);
+            this.setupResultSlot(java.util.Objects.requireNonNull(selectedPattern, "selectedPattern was null, this is unexpected"));
+            // Paper end - Add PlayerLoomPatternSelectEvent
             return true;
         } else {
             return false;
@@ -137,11 +224,17 @@
 
     private List<Holder<BannerPattern>> getSelectablePatterns(ItemStack stack) {
         if (stack.isEmpty()) {
-            return this.patternGetter.get(BannerPatternTags.NO_ITEM_REQUIRED).map(ImmutableList::copyOf).orElse(ImmutableList.of());
+            return (List) this.patternGetter.get(BannerPatternTags.NO_ITEM_REQUIRED).map(ImmutableList::copyOf).orElse(ImmutableList.of());
         } else {
-            return (List<Holder<BannerPattern>>)(stack.getItem() instanceof BannerPatternItem bannerPatternItem
-                ? this.patternGetter.get(bannerPatternItem.getBannerPattern()).map(ImmutableList::copyOf).orElse(ImmutableList.of())
-                : List.of());
+            Item item = stack.getItem();
+
+            if (item instanceof BannerPatternItem) {
+                BannerPatternItem itembannerpattern = (BannerPatternItem) item;
+
+                return (List) this.patternGetter.get(itembannerpattern.getBannerPattern()).map(ImmutableList::copyOf).orElse(ImmutableList.of());
+            } else {
+                return List.of();
+            }
         }
     }
 
@@ -151,26 +244,30 @@
 
     @Override
     public void slotsChanged(Container inventory) {
-        ItemStack itemStack = this.bannerSlot.getItem();
-        ItemStack itemStack2 = this.dyeSlot.getItem();
-        ItemStack itemStack3 = this.patternSlot.getItem();
-        if (!itemStack.isEmpty() && !itemStack2.isEmpty()) {
+        ItemStack itemstack = this.bannerSlot.getItem();
+        ItemStack itemstack1 = this.dyeSlot.getItem();
+        ItemStack itemstack2 = this.patternSlot.getItem();
+
+        if (!itemstack.isEmpty() && !itemstack1.isEmpty()) {
             int i = this.selectedBannerPatternIndex.get();
-            boolean bl = this.isValidPatternIndex(i);
+            boolean flag = this.isValidPatternIndex(i);
             List<Holder<BannerPattern>> list = this.selectablePatterns;
-            this.selectablePatterns = this.getSelectablePatterns(itemStack3);
-            Holder<BannerPattern> holder;
+
+            this.selectablePatterns = this.getSelectablePatterns(itemstack2);
+            Holder holder;
+
             if (this.selectablePatterns.size() == 1) {
                 this.selectedBannerPatternIndex.set(0);
-                holder = this.selectablePatterns.get(0);
-            } else if (!bl) {
+                holder = (Holder) this.selectablePatterns.get(0);
+            } else if (!flag) {
                 this.selectedBannerPatternIndex.set(-1);
                 holder = null;
             } else {
-                Holder<BannerPattern> holder3 = list.get(i);
-                int j = this.selectablePatterns.indexOf(holder3);
+                Holder<BannerPattern> holder1 = (Holder) list.get(i);
+                int j = this.selectablePatterns.indexOf(holder1);
+
                 if (j != -1) {
-                    holder = holder3;
+                    holder = holder1;
                     this.selectedBannerPatternIndex.set(j);
                 } else {
                     holder = null;
@@ -179,9 +276,10 @@
             }
 
             if (holder != null) {
-                BannerPatternLayers bannerPatternLayers = itemStack.getOrDefault(DataComponents.BANNER_PATTERNS, BannerPatternLayers.EMPTY);
-                boolean bl2 = bannerPatternLayers.layers().size() >= 6;
-                if (bl2) {
+                BannerPatternLayers bannerpatternlayers = (BannerPatternLayers) itemstack.getOrDefault(DataComponents.BANNER_PATTERNS, BannerPatternLayers.EMPTY);
+                boolean flag1 = bannerpatternlayers.layers().size() >= 6;
+
+                if (flag1) {
                     this.selectedBannerPatternIndex.set(-1);
                     this.resultSlot.set(ItemStack.EMPTY);
                 } else {
@@ -191,7 +289,8 @@
                 this.resultSlot.set(ItemStack.EMPTY);
             }
 
-            this.broadcastChanges();
+            // this.broadcastChanges(); // Paper - Add PrepareResultEvent; done below
+            org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareResultEvent(this, 3); // Paper - Add PrepareResultEvent
         } else {
             this.resultSlot.set(ItemStack.EMPTY);
             this.selectablePatterns = List.of();
@@ -212,81 +311,91 @@
     }
 
     @Override
-    public ItemStack quickMoveStack(Player player, int slot) {
-        ItemStack itemStack = ItemStack.EMPTY;
-        Slot slot2 = this.slots.get(slot);
-        if (slot2 != null && slot2.hasItem()) {
-            ItemStack itemStack2 = slot2.getItem();
-            itemStack = itemStack2.copy();
+    public ItemStack quickMoveStack(net.minecraft.world.entity.player.Player player, int slot) {
+        ItemStack itemstack = ItemStack.EMPTY;
+        Slot slot1 = (Slot) this.slots.get(slot);
+
+        if (slot1 != null && slot1.hasItem()) {
+            ItemStack itemstack1 = slot1.getItem();
+
+            itemstack = itemstack1.copy();
             if (slot == this.resultSlot.index) {
-                if (!this.moveItemStackTo(itemStack2, 4, 40, true)) {
+                if (!this.moveItemStackTo(itemstack1, 4, 40, true)) {
                     return ItemStack.EMPTY;
                 }
 
-                slot2.onQuickCraft(itemStack2, itemStack);
+                slot1.onQuickCraft(itemstack1, itemstack);
             } else if (slot != this.dyeSlot.index && slot != this.bannerSlot.index && slot != this.patternSlot.index) {
-                if (itemStack2.getItem() instanceof BannerItem) {
-                    if (!this.moveItemStackTo(itemStack2, this.bannerSlot.index, this.bannerSlot.index + 1, false)) {
+                if (itemstack1.getItem() instanceof BannerItem) {
+                    if (!this.moveItemStackTo(itemstack1, this.bannerSlot.index, this.bannerSlot.index + 1, false)) {
                         return ItemStack.EMPTY;
                     }
-                } else if (itemStack2.getItem() instanceof DyeItem) {
-                    if (!this.moveItemStackTo(itemStack2, this.dyeSlot.index, this.dyeSlot.index + 1, false)) {
+                } else if (itemstack1.getItem() instanceof DyeItem) {
+                    if (!this.moveItemStackTo(itemstack1, this.dyeSlot.index, this.dyeSlot.index + 1, false)) {
                         return ItemStack.EMPTY;
                     }
-                } else if (itemStack2.getItem() instanceof BannerPatternItem) {
-                    if (!this.moveItemStackTo(itemStack2, this.patternSlot.index, this.patternSlot.index + 1, false)) {
+                } else if (itemstack1.getItem() instanceof BannerPatternItem) {
+                    if (!this.moveItemStackTo(itemstack1, this.patternSlot.index, this.patternSlot.index + 1, false)) {
                         return ItemStack.EMPTY;
                     }
                 } else if (slot >= 4 && slot < 31) {
-                    if (!this.moveItemStackTo(itemStack2, 31, 40, false)) {
+                    if (!this.moveItemStackTo(itemstack1, 31, 40, false)) {
                         return ItemStack.EMPTY;
                     }
-                } else if (slot >= 31 && slot < 40 && !this.moveItemStackTo(itemStack2, 4, 31, false)) {
+                } else if (slot >= 31 && slot < 40 && !this.moveItemStackTo(itemstack1, 4, 31, false)) {
                     return ItemStack.EMPTY;
                 }
-            } else if (!this.moveItemStackTo(itemStack2, 4, 40, false)) {
+            } else if (!this.moveItemStackTo(itemstack1, 4, 40, false)) {
                 return ItemStack.EMPTY;
             }
 
-            if (itemStack2.isEmpty()) {
-                slot2.setByPlayer(ItemStack.EMPTY);
+            if (itemstack1.isEmpty()) {
+                slot1.setByPlayer(ItemStack.EMPTY);
             } else {
-                slot2.setChanged();
+                slot1.setChanged();
             }
 
-            if (itemStack2.getCount() == itemStack.getCount()) {
+            if (itemstack1.getCount() == itemstack.getCount()) {
                 return ItemStack.EMPTY;
             }
 
-            slot2.onTake(player, itemStack2);
+            slot1.onTake(player, itemstack1);
         }
 
-        return itemStack;
+        return itemstack;
     }
 
     @Override
-    public void removed(Player player) {
+    public void removed(net.minecraft.world.entity.player.Player player) {
         super.removed(player);
-        this.access.execute((world, pos) -> this.clearContainer(player, this.inputContainer));
+        this.access.execute((world, blockposition) -> {
+            this.clearContainer(player, this.inputContainer);
+        });
     }
 
     private void setupResultSlot(Holder<BannerPattern> pattern) {
-        ItemStack itemStack = this.bannerSlot.getItem();
-        ItemStack itemStack2 = this.dyeSlot.getItem();
-        ItemStack itemStack3 = ItemStack.EMPTY;
-        if (!itemStack.isEmpty() && !itemStack2.isEmpty()) {
-            itemStack3 = itemStack.copyWithCount(1);
-            DyeColor dyeColor = ((DyeItem)itemStack2.getItem()).getDyeColor();
-            itemStack3.update(
-                DataComponents.BANNER_PATTERNS,
-                BannerPatternLayers.EMPTY,
-                component -> new BannerPatternLayers.Builder().addAll(component).add(pattern, dyeColor).build()
-            );
+        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();
+
+            itemstack2.update(DataComponents.BANNER_PATTERNS, BannerPatternLayers.EMPTY, (bannerpatternlayers) -> {
+                // CraftBukkit start
+                if (bannerpatternlayers.layers().size() > 20) {
+                    bannerpatternlayers = new BannerPatternLayers(List.copyOf(bannerpatternlayers.layers().subList(0, 20)));
+                }
+                // CraftBukkit end
+                return (new BannerPatternLayers.Builder()).addAll(bannerpatternlayers).add(pattern, enumcolor).build();
+            });
         }
 
-        if (!ItemStack.matches(itemStack3, this.resultSlot.getItem())) {
-            this.resultSlot.set(itemStack3);
+        if (!ItemStack.matches(itemstack2, this.resultSlot.getItem())) {
+            this.resultSlot.set(itemstack2);
         }
+
     }
 
     public Slot getBannerSlot() {
