--- a/net/minecraft/world/level/storage/loot/LootTable.java
+++ b/net/minecraft/world/level/storage/loot/LootTable.java
@@ -1,13 +1,16 @@
 package net.minecraft.world.level.storage.loot;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
 import com.google.common.collect.Lists;
 import com.mojang.logging.LogUtils;
 import com.mojang.serialization.Codec;
 import com.mojang.serialization.codecs.RecordCodecBuilder;
 import it.unimi.dsi.fastutil.objects.ObjectArrayList;
+import it.unimi.dsi.fastutil.objects.ObjectListIterator;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
@@ -26,19 +29,27 @@
 import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
 import org.slf4j.Logger;
 
+// CraftBukkit start
+import org.bukkit.craftbukkit.v1_20_R2.CraftLootTable;
+import org.bukkit.craftbukkit.v1_20_R2.event.CraftEventFactory;
+import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftItemStack;
+import org.bukkit.event.world.LootGenerateEvent;
+// CraftBukkit end
+
 public class LootTable {
+
     private static final Logger LOGGER = LogUtils.getLogger();
     public static final LootTable EMPTY = new LootTable(LootContextParamSets.EMPTY, Optional.empty(), List.of(), List.of());
     public static final LootContextParamSet DEFAULT_PARAM_SET = LootContextParamSets.ALL_PARAMS;
     public static final Codec<LootTable> CODEC = RecordCodecBuilder.create((instance) -> {
-        return instance.group(LootContextParamSets.CODEC.optionalFieldOf("type", DEFAULT_PARAM_SET).forGetter((table) -> {
-            return table.paramSet;
-        }), ExtraCodecs.strictOptionalField(ResourceLocation.CODEC, "random_sequence").forGetter((table) -> {
-            return table.randomSequence;
-        }), ExtraCodecs.strictOptionalField(LootPool.CODEC.listOf(), "pools", List.of()).forGetter((table) -> {
-            return table.pools;
-        }), ExtraCodecs.strictOptionalField(LootItemFunctions.CODEC.listOf(), "functions", List.of()).forGetter((table) -> {
-            return table.functions;
+        return instance.group(LootContextParamSets.CODEC.optionalFieldOf("type", LootTable.DEFAULT_PARAM_SET).forGetter((loottable) -> {
+            return loottable.paramSet;
+        }), ExtraCodecs.strictOptionalField(ResourceLocation.CODEC, "random_sequence").forGetter((loottable) -> {
+            return loottable.randomSequence;
+        }), ExtraCodecs.strictOptionalField(LootPool.CODEC.listOf(), "pools", List.of()).forGetter((loottable) -> {
+            return loottable.pools;
+        }), ExtraCodecs.strictOptionalField(LootItemFunctions.CODEC.listOf(), "functions", List.of()).forGetter((loottable) -> {
+            return loottable.functions;
         })).apply(instance, LootTable::new);
     });
     private final LootContextParamSet paramSet;
@@ -46,6 +57,7 @@
     private final List<LootPool> pools;
     private final List<LootItemFunction> functions;
     private final BiFunction<ItemStack, LootContext, ItemStack> compositeFunction;
+    public CraftLootTable craftLootTable; // CraftBukkit
 
     LootTable(LootContextParamSet type, Optional<ResourceLocation> randomSequenceId, List<LootPool> pools, List<LootItemFunction> functions) {
         this.paramSet = type;
@@ -56,17 +68,19 @@
     }
 
     public static Consumer<ItemStack> createStackSplitter(ServerLevel world, Consumer<ItemStack> consumer) {
-        return (stack) -> {
-            if (stack.isItemEnabled(world.enabledFeatures())) {
-                if (stack.getCount() < stack.getMaxStackSize()) {
-                    consumer.accept(stack);
+        boolean skipSplitter = world != null && !world.paperConfig().fixes.splitOverstackedLoot; // Paper - preserve overstacked items
+        return (itemstack) -> {
+            if (itemstack.isItemEnabled(world.enabledFeatures())) {
+                if (skipSplitter || itemstack.getCount() < itemstack.getMaxStackSize()) { // Paper - preserve overstacked items
+                    consumer.accept(itemstack);
                 } else {
-                    int i = stack.getCount();
+                    int i = itemstack.getCount();
 
-                    while(i > 0) {
-                        ItemStack itemStack = stack.copyWithCount(Math.min(stack.getMaxStackSize(), i));
-                        i -= itemStack.getCount();
-                        consumer.accept(itemStack);
+                    while (i > 0) {
+                        ItemStack itemstack1 = itemstack.copyWithCount(Math.min(itemstack.getMaxStackSize(), i));
+
+                        i -= itemstack1.getCount();
+                        consumer.accept(itemstack1);
                     }
                 }
 
@@ -79,31 +93,35 @@
     }
 
     public void getRandomItemsRaw(LootContext context, Consumer<ItemStack> lootConsumer) {
-        LootContext.VisitedEntry<?> visitedEntry = LootContext.createVisitedEntry(this);
-        if (context.pushVisitedElement(visitedEntry)) {
-            Consumer<ItemStack> consumer = LootItemFunction.decorate(this.compositeFunction, lootConsumer, context);
+        LootContext.VisitedEntry<?> loottableinfo_c = LootContext.createVisitedEntry(this);
 
-            for(LootPool lootPool : this.pools) {
-                lootPool.addRandomItems(consumer, context);
+        if (context.pushVisitedElement(loottableinfo_c)) {
+            Consumer<ItemStack> consumer1 = LootItemFunction.decorate(this.compositeFunction, lootConsumer, context);
+            Iterator iterator = this.pools.iterator();
+
+            while (iterator.hasNext()) {
+                LootPool lootselector = (LootPool) iterator.next();
+
+                lootselector.addRandomItems(consumer1, context);
             }
 
-            context.popVisitedElement(visitedEntry);
+            context.popVisitedElement(loottableinfo_c);
         } else {
-            LOGGER.warn("Detected infinite loop in loot tables");
+            LootTable.LOGGER.warn("Detected infinite loop in loot tables");
         }
 
     }
 
     public void getRandomItems(LootParams parameters, long seed, Consumer<ItemStack> lootConsumer) {
-        this.getRandomItemsRaw((new LootContext.Builder(parameters)).withOptionalRandomSeed(seed).create(this.randomSequence), createStackSplitter(parameters.getLevel(), lootConsumer));
+        this.getRandomItemsRaw((new LootContext.Builder(parameters)).withOptionalRandomSeed(seed).create(this.randomSequence), LootTable.createStackSplitter(parameters.getLevel(), lootConsumer));
     }
 
     public void getRandomItems(LootParams parameters, Consumer<ItemStack> lootConsumer) {
-        this.getRandomItemsRaw(parameters, createStackSplitter(parameters.getLevel(), lootConsumer));
+        this.getRandomItemsRaw(parameters, LootTable.createStackSplitter(parameters.getLevel(), lootConsumer));
     }
 
     public void getRandomItems(LootContext context, Consumer<ItemStack> lootConsumer) {
-        this.getRandomItemsRaw(context, createStackSplitter(context.getLevel(), lootConsumer));
+        this.getRandomItemsRaw(context, LootTable.createStackSplitter(context.getLevel(), lootConsumer));
     }
 
     public ObjectArrayList<ItemStack> getRandomItems(LootParams parameters, long seed) {
@@ -115,9 +133,11 @@
     }
 
     private ObjectArrayList<ItemStack> getRandomItems(LootContext context) {
-        ObjectArrayList<ItemStack> objectArrayList = new ObjectArrayList<>();
-        this.getRandomItems(context, objectArrayList::add);
-        return objectArrayList;
+        ObjectArrayList<ItemStack> objectarraylist = new ObjectArrayList();
+
+        Objects.requireNonNull(objectarraylist);
+        this.getRandomItems(context, objectarraylist::add);
+        return objectarraylist;
     }
 
     public LootContextParamSet getParamSet() {
@@ -125,33 +145,52 @@
     }
 
     public void validate(ValidationContext reporter) {
-        for(int i = 0; i < this.pools.size(); ++i) {
-            this.pools.get(i).validate(reporter.forChild(".pools[" + i + "]"));
+        int i;
+
+        for (i = 0; i < this.pools.size(); ++i) {
+            ((LootPool) this.pools.get(i)).validate(reporter.forChild(".pools[" + i + "]"));
         }
 
-        for(int j = 0; j < this.functions.size(); ++j) {
-            this.functions.get(j).validate(reporter.forChild(".functions[" + j + "]"));
+        for (i = 0; i < this.functions.size(); ++i) {
+            ((LootItemFunction) this.functions.get(i)).validate(reporter.forChild(".functions[" + i + "]"));
         }
 
     }
 
     public void fill(Container inventory, LootParams parameters, long seed) {
-        LootContext lootContext = (new LootContext.Builder(parameters)).withOptionalRandomSeed(seed).create(this.randomSequence);
-        ObjectArrayList<ItemStack> objectArrayList = this.getRandomItems(lootContext);
-        RandomSource randomSource = lootContext.getRandom();
-        List<Integer> list = this.getAvailableSlots(inventory, randomSource);
-        this.shuffleAndSplitItems(objectArrayList, list.size(), randomSource);
+        // CraftBukkit start
+        this.fillInventory(inventory, parameters, seed, false);
+    }
+
+    public void fillInventory(Container iinventory, LootParams lootparams, long i, boolean plugin) {
+        // CraftBukkit end
+        LootContext loottableinfo = (new LootContext.Builder(lootparams)).withOptionalRandomSeed(i).create(this.randomSequence);
+        ObjectArrayList<ItemStack> objectarraylist = this.getRandomItems(loottableinfo);
+        RandomSource randomsource = loottableinfo.getRandom();
+        // CraftBukkit start
+        LootGenerateEvent event = CraftEventFactory.callLootGenerateEvent(iinventory, this, loottableinfo, objectarraylist, plugin);
+        if (event.isCancelled()) {
+            return;
+        }
+        objectarraylist = event.getLoot().stream().map(CraftItemStack::asNMSCopy).collect(ObjectArrayList.toList());
+        // CraftBukkit end
+        List<Integer> list = this.getAvailableSlots(iinventory, randomsource);
+
+        this.shuffleAndSplitItems(objectarraylist, list.size(), randomsource);
+        ObjectListIterator objectlistiterator = objectarraylist.iterator();
+
+        while (objectlistiterator.hasNext()) {
+            ItemStack itemstack = (ItemStack) objectlistiterator.next();
 
-        for(ItemStack itemStack : objectArrayList) {
             if (list.isEmpty()) {
-                LOGGER.warn("Tried to over-fill a container");
+                LootTable.LOGGER.warn("Tried to over-fill a container");
                 return;
             }
 
-            if (itemStack.isEmpty()) {
-                inventory.setItem(list.remove(list.size() - 1), ItemStack.EMPTY);
+            if (itemstack.isEmpty()) {
+                iinventory.setItem((Integer) list.remove(list.size() - 1), ItemStack.EMPTY);
             } else {
-                inventory.setItem(list.remove(list.size() - 1), itemStack);
+                iinventory.setItem((Integer) list.remove(list.size() - 1), itemstack);
             }
         }
 
@@ -159,32 +198,34 @@
 
     private void shuffleAndSplitItems(ObjectArrayList<ItemStack> drops, int freeSlots, RandomSource random) {
         List<ItemStack> list = Lists.newArrayList();
-        Iterator<ItemStack> iterator = drops.iterator();
+        ObjectListIterator objectlistiterator = drops.iterator();
 
-        while(iterator.hasNext()) {
-            ItemStack itemStack = iterator.next();
-            if (itemStack.isEmpty()) {
-                iterator.remove();
-            } else if (itemStack.getCount() > 1) {
-                list.add(itemStack);
-                iterator.remove();
+        while (objectlistiterator.hasNext()) {
+            ItemStack itemstack = (ItemStack) objectlistiterator.next();
+
+            if (itemstack.isEmpty()) {
+                objectlistiterator.remove();
+            } else if (itemstack.getCount() > 1) {
+                list.add(itemstack);
+                objectlistiterator.remove();
             }
         }
 
-        while(freeSlots - drops.size() - list.size() > 0 && !list.isEmpty()) {
-            ItemStack itemStack2 = list.remove(Mth.nextInt(random, 0, list.size() - 1));
-            int i = Mth.nextInt(random, 1, itemStack2.getCount() / 2);
-            ItemStack itemStack3 = itemStack2.split(i);
-            if (itemStack2.getCount() > 1 && random.nextBoolean()) {
-                list.add(itemStack2);
+        while (freeSlots - drops.size() - list.size() > 0 && !list.isEmpty()) {
+            ItemStack itemstack1 = (ItemStack) list.remove(Mth.nextInt(random, 0, list.size() - 1));
+            int j = Mth.nextInt(random, 1, itemstack1.getCount() / 2);
+            ItemStack itemstack2 = itemstack1.split(j);
+
+            if (itemstack1.getCount() > 1 && random.nextBoolean()) {
+                list.add(itemstack1);
             } else {
-                drops.add(itemStack2);
+                drops.add(itemstack1);
             }
 
-            if (itemStack3.getCount() > 1 && random.nextBoolean()) {
-                list.add(itemStack3);
+            if (itemstack2.getCount() > 1 && random.nextBoolean()) {
+                list.add(itemstack2);
             } else {
-                drops.add(itemStack3);
+                drops.add(itemstack2);
             }
         }
 
@@ -193,16 +234,16 @@
     }
 
     private List<Integer> getAvailableSlots(Container inventory, RandomSource random) {
-        ObjectArrayList<Integer> objectArrayList = new ObjectArrayList<>();
+        ObjectArrayList<Integer> objectarraylist = new ObjectArrayList();
 
-        for(int i = 0; i < inventory.getContainerSize(); ++i) {
+        for (int i = 0; i < inventory.getContainerSize(); ++i) {
             if (inventory.getItem(i).isEmpty()) {
-                objectArrayList.add(i);
+                objectarraylist.add(i);
             }
         }
 
-        Util.shuffle(objectArrayList, random);
-        return objectArrayList;
+        Util.shuffle(objectarraylist, random);
+        return objectarraylist;
     }
 
     public static LootTable.Builder lootTable() {
@@ -210,10 +251,16 @@
     }
 
     public static class Builder implements FunctionUserBuilder<LootTable.Builder> {
+
         private final ImmutableList.Builder<LootPool> pools = ImmutableList.builder();
         private final ImmutableList.Builder<LootItemFunction> functions = ImmutableList.builder();
-        private LootContextParamSet paramSet = LootTable.DEFAULT_PARAM_SET;
-        private Optional<ResourceLocation> randomSequence = Optional.empty();
+        private LootContextParamSet paramSet;
+        private Optional<ResourceLocation> randomSequence;
+
+        public Builder() {
+            this.paramSet = LootTable.DEFAULT_PARAM_SET;
+            this.randomSequence = Optional.empty();
+        }
 
         public LootTable.Builder withPool(LootPool.Builder poolBuilder) {
             this.pools.add(poolBuilder.build());
@@ -231,8 +278,8 @@
         }
 
         @Override
-        public LootTable.Builder apply(LootItemFunction.Builder builder) {
-            this.functions.add(builder.build());
+        public LootTable.Builder apply(LootItemFunction.Builder function) {
+            this.functions.add(function.build());
             return this;
         }
 
