--- 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;
@@ -28,26 +31,37 @@
 import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
 import org.slf4j.Logger;
 
+// CraftBukkit start
+import org.bukkit.craftbukkit.CraftLootTable;
+import org.bukkit.craftbukkit.event.CraftEventFactory;
+import org.bukkit.craftbukkit.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 long RANDOMIZE_SEED = 0L;
-    public static final Codec<LootTable> DIRECT_CODEC = RecordCodecBuilder.create(
-        instance -> instance.group(
-                    LootContextParamSets.CODEC.lenientOptionalFieldOf("type", DEFAULT_PARAM_SET).forGetter(table -> table.paramSet),
-                    ResourceLocation.CODEC.optionalFieldOf("random_sequence").forGetter(table -> table.randomSequence),
-                    LootPool.CODEC.listOf().optionalFieldOf("pools", List.of()).forGetter(table -> table.pools),
-                    LootItemFunctions.ROOT_CODEC.listOf().optionalFieldOf("functions", List.of()).forGetter(table -> table.functions)
-                )
-                .apply(instance, LootTable::new)
-    );
-    public static final Codec<Holder<LootTable>> CODEC = RegistryFileCodec.create(Registries.LOOT_TABLE, DIRECT_CODEC);
+    public static final Codec<LootTable> DIRECT_CODEC = RecordCodecBuilder.create((instance) -> {
+        return instance.group(LootContextParamSets.CODEC.lenientOptionalFieldOf("type", LootTable.DEFAULT_PARAM_SET).forGetter((loottable) -> {
+            return loottable.paramSet;
+        }), ResourceLocation.CODEC.optionalFieldOf("random_sequence").forGetter((loottable) -> {
+            return loottable.randomSequence;
+        }), LootPool.CODEC.listOf().optionalFieldOf("pools", List.of()).forGetter((loottable) -> {
+            return loottable.pools;
+        }), LootItemFunctions.ROOT_CODEC.listOf().optionalFieldOf("functions", List.of()).forGetter((loottable) -> {
+            return loottable.functions;
+        })).apply(instance, LootTable::new);
+    });
+    public static final Codec<Holder<LootTable>> CODEC = RegistryFileCodec.create(Registries.LOOT_TABLE, LootTable.DIRECT_CODEC);
     private final LootContextParamSet paramSet;
     private final Optional<ResourceLocation> randomSequence;
     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;
@@ -58,73 +72,80 @@
     }
 
     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);
+                        ItemStack itemstack1 = itemstack.copyWithCount(Math.min(itemstack.getMaxStackSize(), i));
+
+                        i -= itemstack1.getCount();
+                        consumer.accept(itemstack1);
                     }
                 }
+
             }
         };
     }
 
     public void getRandomItemsRaw(LootParams parameters, Consumer<ItemStack> lootConsumer) {
-        this.getRandomItemsRaw(new LootContext.Builder(parameters).create(this.randomSequence), lootConsumer);
+        this.getRandomItemsRaw((new LootContext.Builder(parameters)).create(this.randomSequence), lootConsumer);
     }
 
     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, RandomSource random) {
-        return this.getRandomItems(new LootContext.Builder(parameters).withOptionalRandomSource(random).create(this.randomSequence));
+        return this.getRandomItems((new LootContext.Builder(parameters)).withOptionalRandomSource(random).create(this.randomSequence));
     }
 
     public ObjectArrayList<ItemStack> getRandomItems(LootParams parameters, long seed) {
-        return this.getRandomItems(new LootContext.Builder(parameters).withOptionalRandomSeed(seed).create(this.randomSequence));
+        return this.getRandomItems((new LootContext.Builder(parameters)).withOptionalRandomSeed(seed).create(this.randomSequence));
     }
 
     public ObjectArrayList<ItemStack> getRandomItems(LootParams parameters) {
-        return this.getRandomItems(new LootContext.Builder(parameters).create(this.randomSequence));
+        return this.getRandomItems((new LootContext.Builder(parameters)).create(this.randomSequence));
     }
 
     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() {
@@ -132,34 +153,55 @@
     }
 
     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);
             }
         }
+
     }
 
     private void shuffleAndSplitItems(ObjectArrayList<ItemStack> drops, int freeSlots, RandomSource random) {
@@ -167,29 +209,31 @@
         Iterator<ItemStack> iterator = drops.iterator();
 
         while (iterator.hasNext()) {
-            ItemStack itemStack = iterator.next();
-            if (itemStack.isEmpty()) {
+            ItemStack itemstack = (ItemStack) iterator.next();
+
+            if (itemstack.isEmpty()) {
                 iterator.remove();
-            } else if (itemStack.getCount() > 1) {
-                list.add(itemStack);
+            } else if (itemstack.getCount() > 1) {
+                list.add(itemstack);
                 iterator.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);
+            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);
             }
         }
 
@@ -198,16 +242,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() {
@@ -215,10 +259,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());
@@ -236,8 +286,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;
         }
 
