--- a/net/minecraft/world/inventory/LoomMenu.java
+++ b/net/minecraft/world/inventory/LoomMenu.java
@@ -38,21 +_,23 @@
     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; // Paper - Add missing InventoryHolders - move down
+    private final Container outputContainer; // Paper - Add missing InventoryHolders - move down
+    // CraftBukkit start
+    private org.bukkit.craftbukkit.inventory.view.CraftLoomView bukkitEntity = null;
+    private org.bukkit.entity.Player player;
+
+    @Override
+    public org.bukkit.craftbukkit.inventory.view.CraftLoomView getBukkitView() {
+        if (this.bukkitEntity != null) {
+            return this.bukkitEntity;
+        }
+
+        org.bukkit.craftbukkit.inventory.CraftInventoryLoom inventory = new org.bukkit.craftbukkit.inventory.CraftInventoryLoom(this.inputContainer, this.outputContainer);
+        this.bukkitEntity = new org.bukkit.craftbukkit.inventory.view.CraftLoomView(this.player, inventory, this);
+        return this.bukkitEntity;
+    }
+    // CraftBukkit end
 
     public LoomMenu(int containerId, Inventory playerInventory) {
         this(containerId, playerInventory, ContainerLevelAccess.NULL);
@@ -61,6 +_,28 @@
     public LoomMenu(int containerId, Inventory playerInventory, final ContainerLevelAccess access) {
         super(MenuType.LOOM, containerId);
         this.access = access;
+        // CraftBukkit start
+        this.inputContainer = new SimpleContainer(this.createBlockHolder(access), 3) { // Paper - Add missing InventoryHolders
+            @Override
+            public void setChanged() {
+                super.setChanged();
+                LoomMenu.this.slotsChanged(this);
+                LoomMenu.this.slotUpdateListener.run();
+            }
+        };
+        this.outputContainer =  new SimpleContainer(this.createBlockHolder(access), 1) { // Paper - Add missing InventoryHolders
+            @Override
+            public void setChanged() {
+                super.setChanged();
+                LoomMenu.this.slotUpdateListener.run();
+            }
+
+            @Override
+            public org.bukkit.Location getLocation() {
+                return access.getLocation();
+            }
+        };
+        // CraftBukkit end
         this.bannerSlot = this.addSlot(new Slot(this.inputContainer, 0, 13, 26) {
             @Override
             public boolean mayPlace(ItemStack stack) {
@@ -106,18 +_,44 @@
         this.addStandardInventorySlots(playerInventory, 8, 84);
         this.addDataSlot(this.selectedBannerPatternIndex);
         this.patternGetter = playerInventory.player.registryAccess().lookupOrThrow(Registries.BANNER_PATTERN);
+        this.player = (org.bukkit.entity.Player) playerInventory.player.getBukkitEntity(); // CraftBukkit
     }
 
     @Override
     public boolean stillValid(Player player) {
+        if (!this.checkReachable) return true; // CraftBukkit
         return stillValid(this.access, player, Blocks.LOOM);
     }
 
     @Override
     public boolean clickMenuButton(Player player, int id) {
         if (id >= 0 && id < this.selectablePatterns.size()) {
-            this.selectedBannerPatternIndex.set(id);
-            this.setupResultSlot(this.selectablePatterns.get(id));
+            // Paper start - Add PlayerLoomPatternSelectEvent
+            int selectablePatternIndex = id;
+            io.papermc.paper.event.player.PlayerLoomPatternSelectEvent event = new io.papermc.paper.event.player.PlayerLoomPatternSelectEvent((org.bukkit.entity.Player) player.getBukkitEntity(), ((org.bukkit.craftbukkit.inventory.CraftInventoryLoom) getBukkitView().getTopInventory()), org.bukkit.craftbukkit.block.banner.CraftPatternType.minecraftHolderToBukkit(this.selectablePatterns.get(selectablePatternIndex)));
+            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;
@@ -180,7 +_,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();
@@ -269,7 +_,14 @@
             itemStack.update(
                 DataComponents.BANNER_PATTERNS,
                 BannerPatternLayers.EMPTY,
-                bannerPatternLayers -> new BannerPatternLayers.Builder().addAll(bannerPatternLayers).add(pattern, dyeColor).build()
+                // CraftBukkit start
+                bannerPatternLayers -> {
+                    if (bannerPatternLayers.layers().size() > 20) {
+                        bannerPatternLayers = new BannerPatternLayers(List.copyOf(bannerPatternLayers.layers().subList(0, 20)));
+                    }
+                    return new BannerPatternLayers.Builder().addAll(bannerPatternLayers).add(pattern, dyeColor).build();
+                }
+                // CraftBukkit end
             );
         }
 
