--- a/net/minecraft/world/inventory/LoomMenu.java
+++ b/net/minecraft/world/inventory/LoomMenu.java
@@ -12,7 +12,6 @@
 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.BlockItem;
@@ -23,38 +22,46 @@
 import net.minecraft.world.level.block.Blocks;
 import net.minecraft.world.level.block.entity.BannerPattern;
 import net.minecraft.world.level.block.entity.BlockEntityType;
+// CraftBukkit start
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftInventoryLoom;
+import org.bukkit.craftbukkit.v1_19_R1.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;
     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);
@@ -62,6 +69,39 @@
 
     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(3) {
+            @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(1) {
+            @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) {
             @Override
@@ -88,18 +128,19 @@
             }
 
             @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((Player)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;
                     }
 
                 });
@@ -107,29 +148,60 @@
             }
         });
 
-        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.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);
     }
 
     @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
+            int selectablePatternIndex = id;
+            io.papermc.paper.event.player.PlayerLoomPatternSelectEvent event = new io.papermc.paper.event.player.PlayerLoomPatternSelectEvent((Player) player.getBukkitEntity(), ((CraftInventoryLoom) getBukkitView().getTopInventory()), org.bukkit.block.banner.PatternType.getByIdentifier(this.selectablePatterns.get(selectablePatternIndex).value().getHashname()));
+            if (!event.callEvent()) {
+                ((Player) player.getBukkitEntity()).updateInventory();
+                return false;
+            }
+            Holder<BannerPattern> selectedPattern = null;
+            for (int i = 0; i < this.selectablePatterns.size(); i++) {
+                final Holder<BannerPattern> holder = this.selectablePatterns.get(i);
+                if (event.getPatternType().getIdentifier().equals(holder.value().getHashname())) {
+                    selectablePatternIndex = i;
+                    selectedPattern = holder;
+                    break;
+                }
+            }
+            if (selectedPattern == null) {
+                for (BannerPattern pattern : Registry.BANNER_PATTERN) {
+                    if (event.getPatternType().getIdentifier().equals(pattern.getHashname())) {
+                        selectedPattern = Registry.BANNER_PATTERN.getHolder(Registry.BANNER_PATTERN.getId(pattern)).orElseThrow();
+                        break;
+                    }
+                }
+                selectablePatternIndex = -1;
+            }
+            ((Player) player.getBukkitEntity()).updateInventory();
+            this.selectedBannerPatternIndex.set(selectablePatternIndex);
+            this.setupResultSlot(java.util.Objects.requireNonNull(selectedPattern, "selectedPattern was null, this is unexpected"));
+            // Paper end
             return true;
         } else {
             return false;
@@ -138,12 +210,14 @@
 
     private List<Holder<BannerPattern>> getSelectablePatterns(ItemStack stack) {
         if (stack.isEmpty()) {
-            return Registry.BANNER_PATTERN.getTag(BannerPatternTags.NO_ITEM_REQUIRED).map(ImmutableList::copyOf).orElse(ImmutableList.of());
+            return (List) Registry.BANNER_PATTERN.getTag(BannerPatternTags.NO_ITEM_REQUIRED).map(ImmutableList::copyOf).orElse(ImmutableList.of());
         } else {
-            Item var3 = stack.getItem();
-            if (var3 instanceof BannerPatternItem) {
-                BannerPatternItem bannerPatternItem = (BannerPatternItem)var3;
-                return Registry.BANNER_PATTERN.getTag(bannerPatternItem.getBannerPattern()).map(ImmutableList::copyOf).orElse(ImmutableList.of());
+            Item item = stack.getItem();
+
+            if (item instanceof BannerPatternItem) {
+                BannerPatternItem itembannerpattern = (BannerPatternItem) item;
+
+                return (List) Registry.BANNER_PATTERN.getTag(itembannerpattern.getBannerPattern()).map(ImmutableList::copyOf).orElse(ImmutableList.of());
             } else {
                 return List.of();
             }
@@ -156,26 +230,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;
@@ -184,9 +262,10 @@
             }
 
             if (holder != null) {
-                CompoundTag compoundTag = BlockItem.getBlockEntityData(itemStack);
-                boolean bl2 = compoundTag != null && compoundTag.contains("Patterns", 9) && !itemStack.isEmpty() && compoundTag.getList("Patterns", 10).size() >= 6;
-                if (bl2) {
+                CompoundTag nbttagcompound = BlockItem.getBlockEntityData(itemstack);
+                boolean flag1 = nbttagcompound != null && nbttagcompound.contains("Patterns", 9) && !itemstack.isEmpty() && nbttagcompound.getList("Patterns", 10).size() >= 6;
+
+                if (flag1) {
                     this.selectedBannerPatternIndex.set(-1);
                     this.resultSlot.set(ItemStack.EMPTY);
                 } else {
@@ -196,7 +275,8 @@
                 this.resultSlot.set(ItemStack.EMPTY);
             }
 
-            this.broadcastChanges();
+            // this.broadcastChanges(); // Paper - done below
+            org.bukkit.craftbukkit.v1_19_R1.event.CraftEventFactory.callPrepareResultEvent(this, 3); // Paper
         } else {
             this.resultSlot.set(ItemStack.EMPTY);
             this.selectablePatterns = List.of();
@@ -217,96 +297,106 @@
     }
 
     @Override
-    public ItemStack quickMoveStack(Player player, int index) {
-        ItemStack itemStack = ItemStack.EMPTY;
-        Slot slot = this.slots.get(index);
+    public ItemStack quickMoveStack(net.minecraft.world.entity.player.Player player, int index) {
+        ItemStack itemstack = ItemStack.EMPTY;
+        Slot slot = (Slot) this.slots.get(index);
+
         if (slot != null && slot.hasItem()) {
-            ItemStack itemStack2 = slot.getItem();
-            itemStack = itemStack2.copy();
+            ItemStack itemstack1 = slot.getItem();
+
+            itemstack = itemstack1.copy();
             if (index == this.resultSlot.index) {
-                if (!this.moveItemStackTo(itemStack2, 4, 40, true)) {
+                if (!this.moveItemStackTo(itemstack1, 4, 40, true)) {
                     return ItemStack.EMPTY;
                 }
 
-                slot.onQuickCraft(itemStack2, itemStack);
+                slot.onQuickCraft(itemstack1, itemstack);
             } else if (index != this.dyeSlot.index && index != this.bannerSlot.index && index != 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 (index >= 4 && index < 31) {
-                    if (!this.moveItemStackTo(itemStack2, 31, 40, false)) {
+                    if (!this.moveItemStackTo(itemstack1, 31, 40, false)) {
                         return ItemStack.EMPTY;
                     }
-                } else if (index >= 31 && index < 40 && !this.moveItemStackTo(itemStack2, 4, 31, false)) {
+                } else if (index >= 31 && index < 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()) {
+            if (itemstack1.isEmpty()) {
                 slot.set(ItemStack.EMPTY);
             } else {
                 slot.setChanged();
             }
 
-            if (itemStack2.getCount() == itemStack.getCount()) {
+            if (itemstack1.getCount() == itemstack.getCount()) {
                 return ItemStack.EMPTY;
             }
 
-            slot.onTake(player, itemStack2);
+            slot.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.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.copy();
-            itemStack3.setCount(1);
-            DyeColor dyeColor = ((DyeItem)itemStack2.getItem()).getDyeColor();
-            CompoundTag compoundTag = BlockItem.getBlockEntityData(itemStack3);
-            ListTag listTag;
-            if (compoundTag != null && compoundTag.contains("Patterns", 9)) {
-                listTag = compoundTag.getList("Patterns", 10);
+        ItemStack itemstack = this.bannerSlot.getItem();
+        ItemStack itemstack1 = this.dyeSlot.getItem();
+        ItemStack itemstack2 = ItemStack.EMPTY;
+
+        if (!itemstack.isEmpty() && !itemstack1.isEmpty()) {
+            itemstack2 = itemstack.copy();
+            itemstack2.setCount(1);
+            DyeColor enumcolor = ((DyeItem) itemstack1.getItem()).getDyeColor();
+            CompoundTag nbttagcompound = BlockItem.getBlockEntityData(itemstack2);
+            ListTag nbttaglist;
+
+            if (nbttagcompound != null && nbttagcompound.contains("Patterns", 9)) {
+                nbttaglist = nbttagcompound.getList("Patterns", 10);
+                // CraftBukkit start
+                while (nbttaglist.size() > 20) {
+                    nbttaglist.remove(20);
+                }
+                // CraftBukkit end
             } else {
-                listTag = new ListTag();
-                if (compoundTag == null) {
-                    compoundTag = new CompoundTag();
+                nbttaglist = new ListTag();
+                if (nbttagcompound == null) {
+                    nbttagcompound = new CompoundTag();
                 }
 
-                compoundTag.put("Patterns", listTag);
+                nbttagcompound.put("Patterns", nbttaglist);
             }
 
-            CompoundTag compoundTag2 = new CompoundTag();
-            compoundTag2.putString("Pattern", pattern.value().getHashname());
-            compoundTag2.putInt("Color", dyeColor.getId());
-            listTag.add(compoundTag2);
-            BlockItem.setBlockEntityData(itemStack3, BlockEntityType.BANNER, compoundTag);
+            CompoundTag nbttagcompound1 = new CompoundTag();
+
+            nbttagcompound1.putString("Pattern", ((BannerPattern) pattern.value()).getHashname());
+            nbttagcompound1.putInt("Color", enumcolor.getId());
+            nbttaglist.add(nbttagcompound1);
+            BlockItem.setBlockEntityData(itemstack2, BlockEntityType.BANNER, nbttagcompound);
         }
 
-        if (!ItemStack.matches(itemStack3, this.resultSlot.getItem())) {
-            this.resultSlot.set(itemStack3);
+        if (!ItemStack.matches(itemstack2, this.resultSlot.getItem())) {
+            this.resultSlot.set(itemstack2);
         }
 
     }
