diff --git a/patches/api/Add-Mob-Experience-reward-API.patch b/patches/api/Add-Mob-Experience-reward-API.patch
index 9d0e74b291..ee4c73e085 100644
--- a/patches/api/Add-Mob-Experience-reward-API.patch
+++ b/patches/api/Add-Mob-Experience-reward-API.patch
@@ -11,14 +11,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
@@ -0,0 +0,0 @@ public interface Mob extends LivingEntity, Lootable {
*/
public void setLeftHanded(boolean leftHanded);
- // Paper end
+ // Paper end - left-handed API
+
-+ // Paper start
++ // Paper start - mob xp reward API
+ /**
+ * Gets the amount of experience the mob will possibly drop. This value is randomized and it can give different results
+ *
+ * @return the amount of experience the mob will possibly drop
+ */
+ public int getPossibleExperienceReward();
-+ // Paper end
++ // Paper end - mob xp reward API
}
diff --git a/patches/api/Left-handed-API.patch b/patches/api/Left-handed-API.patch
index 68c65997b8..d492ebe711 100644
--- a/patches/api/Left-handed-API.patch
+++ b/patches/api/Left-handed-API.patch
@@ -11,9 +11,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
@@ -0,0 +0,0 @@ public interface Mob extends LivingEntity, Lootable {
*/
void setAggressive(boolean aggressive);
- // Paper end
+ // Paper end - Missing Entity API
+
-+ // Paper start
++ // Paper start - left-handed API
+ /**
+ * Check if Mob is left-handed
+ *
@@ -27,5 +27,5 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ * @param leftHanded True if left-handed
+ */
+ public void setLeftHanded(boolean leftHanded);
-+ // Paper end
++ // Paper end - left-handed API
}
diff --git a/patches/api/LootTable-API.patch b/patches/api/LootTable-API.patch
index 358ae1b5fd..f95d46e1d4 100644
--- a/patches/api/LootTable-API.patch
+++ b/patches/api/LootTable-API.patch
@@ -101,8 +101,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ * @param player The player to check
+ * @return Whether or not this player has looted this block
+ */
-+ default boolean hasPlayerLooted(@NotNull Player player) {
-+ return hasPlayerLooted(player.getUniqueId());
++ default boolean hasPlayerLooted(final @NotNull Player player) {
++ return this.hasPlayerLooted(player.getUniqueId());
+ }
+
+ /**
@@ -127,9 +127,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ * @param player The player to check
+ * @return Timestamp last looted, or null if player has not looted this object
+ */
-+ @Nullable
-+ default Long getLastLooted(@NotNull Player player) {
-+ return getLastLooted(player.getUniqueId());
++ default @Nullable Long getLastLooted(final @NotNull Player player) {
++ return this.getLastLooted(player.getUniqueId());
+ }
+
+ /**
@@ -147,8 +146,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ * @param looted true to add player to looted list, false to remove
+ * @return The previous state of whether the player had looted this or not
+ */
-+ default boolean setHasPlayerLooted(@NotNull Player player, boolean looted) {
-+ return setHasPlayerLooted(player.getUniqueId(), looted);
++ default boolean setHasPlayerLooted(final @NotNull Player player, final boolean looted) {
++ return this.setHasPlayerLooted(player.getUniqueId(), looted);
+ }
+
+ /**
@@ -271,6 +270,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
/**
* Gets the inventory of the chest block represented by this block state.
+diff --git a/src/main/java/org/bukkit/block/Crafter.java b/src/main/java/org/bukkit/block/Crafter.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/block/Crafter.java
++++ b/src/main/java/org/bukkit/block/Crafter.java
+@@ -0,0 +0,0 @@ import org.jetbrains.annotations.ApiStatus;
+ */
+ @ApiStatus.Experimental
+ @MinecraftExperimental(Requires.UPDATE_1_21)
+-public interface Crafter extends Container, Lootable {
++public interface Crafter extends Container, com.destroystokyo.paper.loottable.LootableBlockInventory { // Paper - LootTable API
+
+ /**
+ * Gets the number of ticks which this block will remain in the crafting
diff --git a/src/main/java/org/bukkit/block/Dispenser.java b/src/main/java/org/bukkit/block/Dispenser.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/block/Dispenser.java
@@ -355,6 +367,23 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
-public interface ChestBoat extends Boat, InventoryHolder, Lootable {
+public interface ChestBoat extends Boat, InventoryHolder, com.destroystokyo.paper.loottable.LootableEntityInventory { // Paper
}
+diff --git a/src/main/java/org/bukkit/entity/Mob.java b/src/main/java/org/bukkit/entity/Mob.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/entity/Mob.java
++++ b/src/main/java/org/bukkit/entity/Mob.java
+@@ -0,0 +0,0 @@ public interface Mob extends LivingEntity, Lootable {
+ */
+ @Nullable
+ public Sound getAmbientSound();
++
++ // Paper start - LootTable API
++ @Override
++ default void setLootTable(final @Nullable org.bukkit.loot.LootTable table, final long seed) {
++ this.setLootTable(table);
++ this.setSeed(seed);
++ }
++ // Paper end - LootTable API
+ }
diff --git a/src/main/java/org/bukkit/entity/minecart/HopperMinecart.java b/src/main/java/org/bukkit/entity/minecart/HopperMinecart.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/entity/minecart/HopperMinecart.java
@@ -408,24 +437,21 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ * @param table the Loot Table this {@link org.bukkit.block.Container} or {@link org.bukkit.entity.Mob} will have.
+ * @param seed the seed to used to generate loot. Default is 0.
+ */
-+ default void setLootTable(@Nullable LootTable table, long seed) {
-+ setLootTable(table);
-+ setSeed(seed);
-+ }
++ void setLootTable(final @Nullable LootTable table, final long seed);
+
+ /**
+ * Returns whether or not this object has a Loot Table
+ * @return Has a loot table
+ */
+ default boolean hasLootTable() {
-+ return getLootTable() != null;
++ return this.getLootTable() != null;
+ }
+
+ /**
+ * Clears the associated Loot Table to this object
+ */
+ default void clearLootTable() {
-+ setLootTable(null);
++ this.setLootTable(null);
+ }
+ // Paper end
+
diff --git a/patches/api/Missing-Entity-API.patch b/patches/api/Missing-Entity-API.patch
index c8f2ab9991..f0a8f648af 100644
--- a/patches/api/Missing-Entity-API.patch
+++ b/patches/api/Missing-Entity-API.patch
@@ -742,11 +742,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/org/bukkit/entity/Mob.java
+++ b/src/main/java/org/bukkit/entity/Mob.java
@@ -0,0 +0,0 @@ public interface Mob extends LivingEntity, Lootable {
- */
- @Nullable
- public Sound getAmbientSound();
+ this.setSeed(seed);
+ }
+ // Paper end - LootTable API
+
-+ // Paper start
++ // Paper start - Missing Entity API
+ /**
+ * Some mobs will raise their arm(s) when aggressive:
+ *
@@ -778,7 +778,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ * @see #isAggressive()
+ */
+ void setAggressive(boolean aggressive);
-+ // Paper end
++ // Paper end - Missing Entity API
}
diff --git a/src/main/java/org/bukkit/entity/Panda.java b/src/main/java/org/bukkit/entity/Panda.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
diff --git a/patches/server/Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch b/patches/server/Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch
index 4eb8324504..689ba5fa19 100644
--- a/patches/server/Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch
+++ b/patches/server/Add-Debug-Entities-option-to-debug-dupe-uuid-issues.patch
@@ -47,7 +47,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
- public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper
+
private CraftEntity bukkitEntity;
+ public @org.jetbrains.annotations.Nullable net.minecraft.server.level.ChunkMap.TrackedEntity tracker; // Paper
diff --git a/patches/server/Entity-getEntitySpawnReason.patch b/patches/server/Entity-getEntitySpawnReason.patch
index 0c2968a668..e32f796d51 100644
--- a/patches/server/Entity-getEntitySpawnReason.patch
+++ b/patches/server/Entity-getEntitySpawnReason.patch
@@ -68,8 +68,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
// Paper end - Share random for entities to make them more random
+ public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; // Paper - Entity#getEntitySpawnReason
- public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper
private CraftEntity bukkitEntity;
+
@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
}
nbttagcompound.put("Paper.Origin", this.newDoubleList(origin.getX(), origin.getY(), origin.getZ()));
diff --git a/patches/server/Expose-LootTable-of-DecoratedPot.patch b/patches/server/Expose-LootTable-of-DecoratedPot.patch
index ee968db63e..abd4623e02 100644
--- a/patches/server/Expose-LootTable-of-DecoratedPot.patch
+++ b/patches/server/Expose-LootTable-of-DecoratedPot.patch
@@ -20,18 +20,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+
+ @Override
+ public void setLootTable(org.bukkit.loot.LootTable table, long seed) {
-+ net.minecraft.resources.ResourceKey key = (table == null) ? null : net.minecraft.resources.ResourceKey.create(net.minecraft.core.registries.Registries.LOOT_TABLE, org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(table.getKey()));
-+ this.getSnapshot().setLootTable(key, seed);
++ this.getSnapshot().setLootTable(org.bukkit.craftbukkit.CraftLootTable.bukkitToMinecraft(table), seed);
+ }
+
+ @Override
+ public org.bukkit.loot.LootTable getLootTable() {
-+ if (this.getSnapshot().getLootTable() == null) {
-+ return null;
-+ }
-+
-+ net.minecraft.resources.ResourceKey key = this.getSnapshot().getLootTable();
-+ return org.bukkit.Bukkit.getLootTable(org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(key.location()));
++ return org.bukkit.craftbukkit.CraftLootTable.minecraftToBukkit(this.getSnapshot().getLootTable());
+ }
+
+ @Override
diff --git a/patches/server/Fixup-NamespacedKey-handling.patch b/patches/server/Fixup-NamespacedKey-handling.patch
index 72ae23c814..14f9be5371 100644
--- a/patches/server/Fixup-NamespacedKey-handling.patch
+++ b/patches/server/Fixup-NamespacedKey-handling.patch
@@ -4,32 +4,6 @@ Date: Sat, 6 Jan 2024 14:31:00 +0100
Subject: [PATCH] Fixup NamespacedKey handling
-diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperContainerEntityLootableInventory.java b/src/main/java/com/destroystokyo/paper/loottable/PaperContainerEntityLootableInventory.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/com/destroystokyo/paper/loottable/PaperContainerEntityLootableInventory.java
-+++ b/src/main/java/com/destroystokyo/paper/loottable/PaperContainerEntityLootableInventory.java
-@@ -0,0 +0,0 @@ public class PaperContainerEntityLootableInventory implements PaperLootableEntit
-
- @Override
- public org.bukkit.loot.LootTable getLootTable() {
-- return entity.getLootTable() != null ? Optionull.map(entity.getLootTable(), rk -> Bukkit.getLootTable(CraftNamespacedKey.fromMinecraft(rk.location()))) : null;
-+ return entity.getLootTable() != null && !entity.getLootTable().location().getPath().isEmpty() ? Optionull.map(entity.getLootTable(), rk -> Bukkit.getLootTable(CraftNamespacedKey.fromMinecraft(rk.location()))) : null;
- }
-
- @Override
-diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperTileEntityLootableInventory.java b/src/main/java/com/destroystokyo/paper/loottable/PaperTileEntityLootableInventory.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/com/destroystokyo/paper/loottable/PaperTileEntityLootableInventory.java
-+++ b/src/main/java/com/destroystokyo/paper/loottable/PaperTileEntityLootableInventory.java
-@@ -0,0 +0,0 @@ public class PaperTileEntityLootableInventory implements PaperLootableBlockInven
-
- @Override
- public org.bukkit.loot.LootTable getLootTable() {
-- return tileEntityLootable.lootTable != null ? Bukkit.getLootTable(CraftNamespacedKey.fromMinecraft(tileEntityLootable.lootTable.location())) : null;
-+ return tileEntityLootable.lootTable != null && !tileEntityLootable.lootTable.location().getPath().isEmpty() ? Bukkit.getLootTable(CraftNamespacedKey.fromMinecraft(tileEntityLootable.lootTable.location())) : null;
- }
-
- @Override
diff --git a/src/main/java/net/minecraft/world/inventory/LoomMenu.java b/src/main/java/net/minecraft/world/inventory/LoomMenu.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/inventory/LoomMenu.java
@@ -64,6 +38,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
if (!event.callEvent()) {
player.containerMenu.sendAllDataToRemote();
return false;
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java b/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java
+@@ -0,0 +0,0 @@ public class CraftLootTable implements org.bukkit.loot.LootTable {
+ }
+
+ public static org.bukkit.loot.LootTable minecraftToBukkit(ResourceKey minecraft) {
+- return (minecraft == null) ? null : Bukkit.getLootTable(CraftLootTable.minecraftToBukkitKey(minecraft));
++ return (minecraft == null || minecraft.location().getPath().isEmpty()) ? null : Bukkit.getLootTable(CraftLootTable.minecraftToBukkitKey(minecraft)); // Paper - fix some NamespacedKey parsing
+ }
+
+ public static NamespacedKey minecraftToBukkitKey(ResourceKey minecraft) {
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
diff --git a/patches/server/LootTable-API-and-replenishable-lootables.patch b/patches/server/LootTable-API-and-replenishable-lootables.patch
index 8ed2e5b92a..b69e9e4ef7 100644
--- a/patches/server/LootTable-API-and-replenishable-lootables.patch
+++ b/patches/server/LootTable-API-and-replenishable-lootables.patch
@@ -15,76 +15,64 @@ public org.bukkit.craftbukkit.block.CraftBlockEntityState getTileEntity()Lnet/mi
public org.bukkit.craftbukkit.block.CraftLootable setLootTable(Lorg/bukkit/loot/LootTable;J)V
public org.bukkit.craftbukkit.entity.CraftMinecartContainer setLootTable(Lorg/bukkit/loot/LootTable;J)V
-diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperContainerEntityLootableInventory.java b/src/main/java/com/destroystokyo/paper/loottable/PaperContainerEntityLootableInventory.java
+diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperLootable.java b/src/main/java/com/destroystokyo/paper/loottable/PaperLootable.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
-+++ b/src/main/java/com/destroystokyo/paper/loottable/PaperContainerEntityLootableInventory.java
++++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootable.java
@@ -0,0 +0,0 @@
+package com.destroystokyo.paper.loottable;
+
-+import net.minecraft.Optionull;
-+import net.minecraft.core.registries.Registries;
-+import net.minecraft.resources.ResourceKey;
-+import net.minecraft.world.entity.Entity;
-+import net.minecraft.world.entity.vehicle.AbstractMinecartContainer;
-+import net.minecraft.world.entity.vehicle.ContainerEntity;
-+import net.minecraft.world.level.Level;
-+import org.bukkit.Bukkit;
-+import org.bukkit.craftbukkit.util.CraftNamespacedKey;
++import org.bukkit.loot.LootTable;
++import org.bukkit.loot.Lootable;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.checker.nullness.qual.Nullable;
++import org.checkerframework.framework.qual.DefaultQualifier;
+
-+public class PaperContainerEntityLootableInventory implements PaperLootableEntityInventory {
++@DefaultQualifier(NonNull.class)
++public interface PaperLootable extends Lootable {
+
-+ private final ContainerEntity entity;
-+
-+ public PaperContainerEntityLootableInventory(ContainerEntity entity) {
-+ this.entity = entity;
++ @Override
++ default void setLootTable(final @Nullable LootTable table) {
++ this.setLootTable(table, this.getSeed());
+ }
+
+ @Override
-+ public org.bukkit.loot.LootTable getLootTable() {
-+ return entity.getLootTable() != null ? Optionull.map(entity.getLootTable(), rk -> Bukkit.getLootTable(CraftNamespacedKey.fromMinecraft(rk.location()))) : null;
++ default void setSeed(final long seed) {
++ this.setLootTable(this.getLootTable(), seed);
++ }
++}
+diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperLootableBlock.java b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableBlock.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableBlock.java
+@@ -0,0 +0,0 @@
++package com.destroystokyo.paper.loottable;
++
++import net.minecraft.world.RandomizableContainer;
++import org.bukkit.craftbukkit.CraftLootTable;
++import org.bukkit.loot.LootTable;
++import org.checkerframework.checker.nullness.qual.Nullable;
++
++public interface PaperLootableBlock extends PaperLootable {
++
++ RandomizableContainer getRandomizableContainer();
++
++ /* Lootable */
++ @Override
++ default @Nullable LootTable getLootTable() {
++ return CraftLootTable.minecraftToBukkit(this.getRandomizableContainer().getLootTable());
+ }
+
+ @Override
-+ public void setLootTable(org.bukkit.loot.LootTable table, long seed) {
-+ setLootTable(table);
-+ setSeed(seed);
++ default void setLootTable(final @Nullable LootTable table, final long seed) {
++ this.getRandomizableContainer().setLootTable(CraftLootTable.bukkitToMinecraft(table), seed);
+ }
+
+ @Override
-+ public void setSeed(long seed) {
-+ entity.setLootTableSeed(seed);
-+ }
-+
-+ @Override
-+ public long getSeed() {
-+ return entity.getLootTableSeed();
-+ }
-+
-+ @Override
-+ public void setLootTable(org.bukkit.loot.LootTable table) {
-+ entity.setLootTable((table == null) ? null : ResourceKey.create(Registries.LOOT_TABLE, CraftNamespacedKey.toMinecraft(table.getKey())));
-+ }
-+
-+ @Override
-+ public PaperLootableInventoryData getLootableData() {
-+ return entity.getLootableData();
-+ }
-+
-+ @Override
-+ public Entity getHandle() {
-+ return entity.getEntity();
-+ }
-+
-+ @Override
-+ public LootableInventory getAPILootableInventory() {
-+ return (LootableInventory) entity.getEntity().getBukkitEntity();
-+ }
-+
-+ @Override
-+ public Level getNMSWorld() {
-+ return entity.level();
++ default long getSeed() {
++ return this.getRandomizableContainer().getLootTableSeed();
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperLootableBlockInventory.java b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableBlockInventory.java
@@ -95,35 +83,63 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
@@ -0,0 +0,0 @@
+package com.destroystokyo.paper.loottable;
+
++import java.util.Objects;
+import net.minecraft.core.BlockPos;
-+import net.minecraft.world.level.Level;
-+import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity;
-+import org.bukkit.Chunk;
+import org.bukkit.block.Block;
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.framework.qual.DefaultQualifier;
+
-+public interface PaperLootableBlockInventory extends LootableBlockInventory, PaperLootableInventory {
-+
-+ RandomizableContainerBlockEntity getTileEntity();
++@DefaultQualifier(NonNull.class)
++public interface PaperLootableBlockInventory extends LootableBlockInventory, PaperLootableInventory, PaperLootableBlock {
+
++ /* PaperLootableInventory */
+ @Override
-+ default LootableInventory getAPILootableInventory() {
-+ return this;
++ default PaperLootableInventoryData lootableDataForAPI() {
++ return Objects.requireNonNull(this.getRandomizableContainer().lootableData(), "Can only manage loot tables on tile entities with lootableData");
+ }
+
++ /* LootableBlockInventory */
+ @Override
-+ default Level getNMSWorld() {
-+ return this.getTileEntity().getLevel();
-+ }
-+
+ default Block getBlock() {
-+ final BlockPos position = this.getTileEntity().getBlockPos();
-+ final Chunk bukkitChunk = this.getBukkitWorld().getChunkAt(org.bukkit.craftbukkit.block.CraftBlock.at(this.getNMSWorld(), position));
-+ return bukkitChunk.getBlock(position.getX(), position.getY(), position.getZ());
++ final BlockPos position = this.getRandomizableContainer().getBlockPos();
++ return CraftBlock.at(this.getNMSWorld(), position);
++ }
++
++}
+diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperLootableEntity.java b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableEntity.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
+--- /dev/null
++++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableEntity.java
+@@ -0,0 +0,0 @@
++package com.destroystokyo.paper.loottable;
++
++import net.minecraft.world.entity.vehicle.ContainerEntity;
++import org.bukkit.craftbukkit.CraftLootTable;
++import org.bukkit.loot.LootTable;
++import org.bukkit.loot.Lootable;
++import org.checkerframework.checker.nullness.qual.Nullable;
++
++public interface PaperLootableEntity extends Lootable {
++
++ ContainerEntity getHandle();
++
++ /* Lootable */
++ @Override
++ default @Nullable LootTable getLootTable() {
++ return CraftLootTable.minecraftToBukkit(this.getHandle().getLootTable());
+ }
+
+ @Override
-+ default PaperLootableInventoryData getLootableData() {
-+ return this.getTileEntity().lootableData;
++ default void setLootTable(final @Nullable LootTable table, final long seed) {
++ this.getHandle().setLootTable(CraftLootTable.bukkitToMinecraft(table));
++ this.getHandle().setLootTableSeed(seed);
++ }
++
++ @Override
++ default long getSeed() {
++ return this.getHandle().getLootTableSeed();
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperLootableEntityInventory.java b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableEntityInventory.java
@@ -136,28 +152,26 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+
+import net.minecraft.world.level.Level;
+import org.bukkit.entity.Entity;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.framework.qual.DefaultQualifier;
+
-+public interface PaperLootableEntityInventory extends LootableEntityInventory, PaperLootableInventory {
-+
-+ net.minecraft.world.entity.Entity getHandle();
-+
-+ @Override
-+ default LootableInventory getAPILootableInventory() {
-+ return this;
-+ }
-+
-+ default Entity getEntity() {
-+ return getHandle().getBukkitEntity();
-+ }
++@DefaultQualifier(NonNull.class)
++public interface PaperLootableEntityInventory extends LootableEntityInventory, PaperLootableInventory, PaperLootableEntity {
+
++ /* PaperLootableInventory */
+ @Override
+ default Level getNMSWorld() {
-+ return getHandle().getCommandSenderWorld();
++ return this.getHandle().level();
+ }
+
+ @Override
-+ default PaperLootableInventoryData getLootableData() {
-+ return getHandle().lootableData;
++ default PaperLootableInventoryData lootableDataForAPI() {
++ return this.getHandle().lootableData();
++ }
++
++ /* LootableEntityInventory */
++ default Entity getEntity() {
++ return ((net.minecraft.world.entity.Entity) this.getHandle()).getBukkitEntity();
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventory.java b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventory.java
@@ -168,69 +182,73 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
@@ -0,0 +0,0 @@
+package com.destroystokyo.paper.loottable;
+
-+import org.bukkit.loot.Lootable;
+import java.util.UUID;
+import net.minecraft.world.level.Level;
++import org.bukkit.World;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.framework.qual.DefaultQualifier;
+
-+public interface PaperLootableInventory extends LootableInventory, Lootable {
++@DefaultQualifier(NonNull.class)
++public interface PaperLootableInventory extends PaperLootable, LootableInventory {
+
-+ PaperLootableInventoryData getLootableData();
-+ LootableInventory getAPILootableInventory();
++ /* impl */
++ PaperLootableInventoryData lootableDataForAPI();
+
+ Level getNMSWorld();
+
-+ default org.bukkit.World getBukkitWorld() {
-+ return getNMSWorld().getWorld();
++ default World getBukkitWorld() {
++ return this.getNMSWorld().getWorld();
+ }
+
++ /* LootableInventory */
+ @Override
+ default boolean isRefillEnabled() {
-+ return getNMSWorld().paperConfig().lootables.autoReplenish;
++ return this.getNMSWorld().paperConfig().lootables.autoReplenish;
+ }
+
+ @Override
+ default boolean hasBeenFilled() {
-+ return getLastFilled() != -1;
++ return this.getLastFilled() != -1;
+ }
+
+ @Override
-+ default boolean hasPlayerLooted(UUID player) {
-+ return getLootableData().hasPlayerLooted(player);
++ default boolean hasPlayerLooted(final UUID player) {
++ return this.lootableDataForAPI().hasPlayerLooted(player);
+ }
+
+ @Override
+ default boolean canPlayerLoot(final UUID player) {
-+ return getLootableData().canPlayerLoot(player, this.getNMSWorld().paperConfig());
++ return this.lootableDataForAPI().canPlayerLoot(player, this.getNMSWorld().paperConfig());
+ }
+
+ @Override
-+ default Long getLastLooted(UUID player) {
-+ return getLootableData().getLastLooted(player);
++ default Long getLastLooted(final UUID player) {
++ return this.lootableDataForAPI().getLastLooted(player);
+ }
+
+ @Override
-+ default boolean setHasPlayerLooted(UUID player, boolean looted) {
-+ final boolean hasLooted = hasPlayerLooted(player);
++ default boolean setHasPlayerLooted(final UUID player, final boolean looted) {
++ final boolean hasLooted = this.hasPlayerLooted(player);
+ if (hasLooted != looted) {
-+ getLootableData().setPlayerLootedState(player, looted);
++ this.lootableDataForAPI().setPlayerLootedState(player, looted);
+ }
+ return hasLooted;
+ }
+
+ @Override
+ default boolean hasPendingRefill() {
-+ long nextRefill = getLootableData().getNextRefill();
-+ return nextRefill != -1 && nextRefill > getLootableData().getLastFill();
++ final long nextRefill = this.lootableDataForAPI().getNextRefill();
++ return nextRefill != -1 && nextRefill > this.lootableDataForAPI().getLastFill();
+ }
+
+ @Override
+ default long getLastFilled() {
-+ return getLootableData().getLastFill();
++ return this.lootableDataForAPI().getLastFill();
+ }
+
+ @Override
+ default long getNextRefill() {
-+ return getLootableData().getNextRefill();
++ return this.lootableDataForAPI().getNextRefill();
+ }
+
+ @Override
@@ -238,7 +256,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ if (refillAt < -1) {
+ refillAt = -1;
+ }
-+ return getLootableData().setNextRefill(refillAt);
++ return this.lootableDataForAPI().setNextRefill(refillAt);
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java
@@ -251,18 +269,23 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+
+import io.papermc.paper.configuration.WorldConfiguration;
+import io.papermc.paper.configuration.type.DurationOrDisabled;
-+import java.time.temporal.ChronoUnit;
-+import java.util.concurrent.TimeUnit;
-+import org.bukkit.entity.Player;
-+import org.bukkit.loot.LootTable;
-+import javax.annotation.Nullable;
-+import net.minecraft.nbt.CompoundTag;
-+import net.minecraft.nbt.ListTag;
+import java.util.HashMap;
+import java.util.Map;
++import java.util.Objects;
+import java.util.Random;
+import java.util.UUID;
++import java.util.concurrent.TimeUnit;
++import net.minecraft.nbt.CompoundTag;
++import net.minecraft.nbt.ListTag;
++import net.minecraft.nbt.Tag;
++import net.minecraft.world.RandomizableContainer;
++import net.minecraft.world.entity.vehicle.ContainerEntity;
++import org.bukkit.entity.Player;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.checker.nullness.qual.Nullable;
++import org.checkerframework.framework.qual.DefaultQualifier;
+
++@DefaultQualifier(NonNull.class)
+public class PaperLootableInventoryData {
+
+ private static final Random RANDOM = new Random();
@@ -270,12 +293,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ private long lastFill = -1;
+ private long nextRefill = -1;
+ private int numRefills = 0;
-+ private Map lootedPlayers;
-+ private final PaperLootableInventory lootable;
-+
-+ public PaperLootableInventoryData(PaperLootableInventory lootable) {
-+ this.lootable = lootable;
-+ }
++ private @Nullable Map lootedPlayers;
+
+ long getLastFill() {
+ return this.lastFill;
@@ -285,22 +303,21 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ return this.nextRefill;
+ }
+
-+ long setNextRefill(long nextRefill) {
-+ long prev = this.nextRefill;
++ long setNextRefill(final long nextRefill) {
++ final long prev = this.nextRefill;
+ this.nextRefill = nextRefill;
+ return prev;
+ }
+
-+ public boolean shouldReplenish(@Nullable net.minecraft.world.entity.player.Player player) {
-+ LootTable table = this.lootable.getLootTable();
++ public boolean shouldReplenish(final T lootTableHolder, final LootTableInterface holderInterface, final net.minecraft.world.entity.player.@Nullable Player player) {
+
+ // No Loot Table associated
-+ if (table == null) {
++ if (!holderInterface.hasLootTable(lootTableHolder)) {
+ return false;
+ }
+
+ // ALWAYS process the first fill or if the feature is disabled
-+ if (this.lastFill == -1 || !this.lootable.getNMSWorld().paperConfig().lootables.autoReplenish) {
++ if (this.lastFill == -1 || !holderInterface.paperConfig(lootTableHolder).lootables.autoReplenish) {
+ return true;
+ }
+
@@ -314,7 +331,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ return false;
+ }
+
-+ final WorldConfiguration paperConfig = this.lootable.getNMSWorld().paperConfig();
++ final WorldConfiguration paperConfig = holderInterface.paperConfig(lootTableHolder);
+
+ // Check if max refills has been hit
+ if (paperConfig.lootables.maxRefills != -1 && this.numRefills >= paperConfig.lootables.maxRefills) {
@@ -328,85 +345,147 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+
+
+ final Player bukkitPlayer = (Player) player.getBukkitEntity();
-+ LootableInventoryReplenishEvent event = new LootableInventoryReplenishEvent(bukkitPlayer, lootable.getAPILootableInventory());
-+ event.setCancelled(!canPlayerLoot(player.getUUID(), paperConfig));
++ final LootableInventoryReplenishEvent event = new LootableInventoryReplenishEvent(bukkitPlayer, holderInterface.getInventoryForEvent(lootTableHolder));
++ event.setCancelled(!this.canPlayerLoot(player.getUUID(), paperConfig));
+ return event.callEvent();
+ }
-+ public void processRefill(@Nullable net.minecraft.world.entity.player.Player player) {
++
++ public interface LootTableInterface {
++
++ WorldConfiguration paperConfig(T holder);
++
++ void setSeed(T holder, long seed);
++
++ boolean hasLootTable(T holder);
++
++ LootableInventory getInventoryForEvent(T holder);
++ }
++
++ public static final LootTableInterface CONTAINER = new LootTableInterface<>() {
++ @Override
++ public WorldConfiguration paperConfig(final RandomizableContainer holder) {
++ return Objects.requireNonNull(holder.getLevel(), "Can only manager loot replenishment on block entities in a world").paperConfig();
++ }
++
++ @Override
++ public void setSeed(final RandomizableContainer holder, final long seed) {
++ holder.setLootTableSeed(seed);
++ }
++
++ @Override
++ public boolean hasLootTable(final RandomizableContainer holder) {
++ return holder.getLootTable() != null;
++ }
++
++ @Override
++ public LootableInventory getInventoryForEvent(final RandomizableContainer holder) {
++ return holder.getLootableInventory();
++ }
++ };
++
++ public static final LootTableInterface ENTITY = new LootTableInterface<>() {
++ @Override
++ public WorldConfiguration paperConfig(final ContainerEntity holder) {
++ return holder.level().paperConfig();
++ }
++
++ @Override
++ public void setSeed(final ContainerEntity holder, final long seed) {
++ holder.setLootTableSeed(seed);
++ }
++
++ @Override
++ public boolean hasLootTable(final ContainerEntity holder) {
++ return holder.getLootTable() != null;
++ }
++
++ @Override
++ public LootableInventory getInventoryForEvent(final ContainerEntity holder) {
++ return holder.getLootableInventory();
++ }
++ };
++
++ public boolean shouldClearLootTable(final T lootTableHolder, final LootTableInterface holderInterface, final net.minecraft.world.entity.player.@Nullable Player player) {
+ this.lastFill = System.currentTimeMillis();
-+ final WorldConfiguration paperConfig = this.lootable.getNMSWorld().paperConfig();
++ final WorldConfiguration paperConfig = holderInterface.paperConfig(lootTableHolder);
+ if (paperConfig.lootables.autoReplenish) {
-+ long min = paperConfig.lootables.refreshMin.seconds();
-+ long max = paperConfig.lootables.refreshMax.seconds();
++ final long min = paperConfig.lootables.refreshMin.seconds();
++ final long max = paperConfig.lootables.refreshMax.seconds();
+ this.nextRefill = this.lastFill + (min + RANDOM.nextLong(max - min + 1)) * 1000L;
+ this.numRefills++;
+ if (paperConfig.lootables.resetSeedOnFill) {
-+ this.lootable.setSeed(0);
++ holderInterface.setSeed(lootTableHolder, 0);
+ }
+ if (player != null) { // This means that numRefills can be incremented without a player being in the lootedPlayers list - Seems to be EntityMinecartChest specific
+ this.setPlayerLootedState(player.getUUID(), true);
+ }
-+ } else {
-+ this.lootable.clearLootTable();
++ return false;
+ }
++ return true;
+ }
+
++ private static final String ROOT = "Paper.LootableData";
++ private static final String LAST_FILL = "lastFill";
++ private static final String NEXT_REFILL = "nextRefill";
++ private static final String NUM_REFILLS = "numRefills";
++ private static final String LOOTED_PLAYERS = "lootedPlayers";
+
-+ public void loadNbt(CompoundTag base) {
-+ if (!base.contains("Paper.LootableData", 10)) { // 10 = compound
++ public void loadNbt(final CompoundTag base) {
++ if (!base.contains(ROOT, Tag.TAG_COMPOUND)) {
+ return;
+ }
-+ CompoundTag comp = base.getCompound("Paper.LootableData");
-+ if (comp.contains("lastFill")) {
-+ this.lastFill = comp.getLong("lastFill");
++ final CompoundTag comp = base.getCompound(ROOT);
++ if (comp.contains(LAST_FILL)) {
++ this.lastFill = comp.getLong(LAST_FILL);
+ }
-+ if (comp.contains("nextRefill")) {
-+ this.nextRefill = comp.getLong("nextRefill");
++ if (comp.contains(NEXT_REFILL)) {
++ this.nextRefill = comp.getLong(NEXT_REFILL);
+ }
+
-+ if (comp.contains("numRefills")) {
-+ this.numRefills = comp.getInt("numRefills");
++ if (comp.contains(NUM_REFILLS)) {
++ this.numRefills = comp.getInt(NUM_REFILLS);
+ }
-+ if (comp.contains("lootedPlayers", net.minecraft.nbt.Tag.TAG_LIST)) {
-+ ListTag list = comp.getList("lootedPlayers", net.minecraft.nbt.Tag.TAG_COMPOUND);
++ if (comp.contains(LOOTED_PLAYERS, Tag.TAG_LIST)) {
++ final ListTag list = comp.getList(LOOTED_PLAYERS, Tag.TAG_COMPOUND);
+ final int size = list.size();
+ if (size > 0) {
+ this.lootedPlayers = new HashMap<>(list.size());
+ }
+ for (int i = 0; i < size; i++) {
+ final CompoundTag cmp = list.getCompound(i);
-+ lootedPlayers.put(cmp.getUUID("UUID"), cmp.getLong("Time"));
++ this.lootedPlayers.put(cmp.getUUID("UUID"), cmp.getLong("Time"));
+ }
+ }
+ }
-+ public void saveNbt(CompoundTag base) {
-+ CompoundTag comp = new CompoundTag();
++
++ public void saveNbt(final CompoundTag base) {
++ final CompoundTag comp = new CompoundTag();
+ if (this.nextRefill != -1) {
-+ comp.putLong("nextRefill", this.nextRefill);
++ comp.putLong(NEXT_REFILL, this.nextRefill);
+ }
+ if (this.lastFill != -1) {
-+ comp.putLong("lastFill", this.lastFill);
++ comp.putLong(LAST_FILL, this.lastFill);
+ }
+ if (this.numRefills != 0) {
-+ comp.putInt("numRefills", this.numRefills);
++ comp.putInt(NUM_REFILLS, this.numRefills);
+ }
+ if (this.lootedPlayers != null && !this.lootedPlayers.isEmpty()) {
-+ ListTag list = new ListTag();
-+ for (Map.Entry entry : this.lootedPlayers.entrySet()) {
-+ CompoundTag cmp = new CompoundTag();
++ final ListTag list = new ListTag();
++ for (final Map.Entry entry : this.lootedPlayers.entrySet()) {
++ final CompoundTag cmp = new CompoundTag();
+ cmp.putUUID("UUID", entry.getKey());
+ cmp.putLong("Time", entry.getValue());
+ list.add(cmp);
+ }
-+ comp.put("lootedPlayers", list);
++ comp.put(LOOTED_PLAYERS, list);
+ }
+
+ if (!comp.isEmpty()) {
-+ base.put("Paper.LootableData", comp);
++ base.put(ROOT, comp);
+ }
+ }
+
-+ void setPlayerLootedState(UUID player, boolean looted) {
++ void setPlayerLootedState(final UUID player, final boolean looted) {
+ if (looted && this.lootedPlayers == null) {
+ this.lootedPlayers = new HashMap<>();
+ }
@@ -418,7 +497,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ }
+
+ boolean canPlayerLoot(final UUID player, final WorldConfiguration worldConfiguration) {
-+ final Long lastLooted = getLastLooted(player);
++ final @Nullable Long lastLooted = this.getLastLooted(player);
+ if (!worldConfiguration.lootables.restrictPlayerReloot || lastLooted == null) return true;
+
+ final DurationOrDisabled restrictPlayerRelootTime = worldConfiguration.lootables.restrictPlayerRelootTime;
@@ -427,99 +506,95 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ return TimeUnit.SECONDS.toMillis(restrictPlayerRelootTime.value().get().seconds()) + lastLooted < System.currentTimeMillis();
+ }
+
-+ boolean hasPlayerLooted(UUID player) {
++ boolean hasPlayerLooted(final UUID player) {
+ return this.lootedPlayers != null && this.lootedPlayers.containsKey(player);
+ }
+
-+ Long getLastLooted(UUID player) {
-+ return lootedPlayers != null ? lootedPlayers.get(player) : null;
++ @Nullable Long getLastLooted(final UUID player) {
++ return this.lootedPlayers != null ? this.lootedPlayers.get(player) : null;
+ }
+}
-diff --git a/src/main/java/com/destroystokyo/paper/loottable/PaperTileEntityLootableInventory.java b/src/main/java/com/destroystokyo/paper/loottable/PaperTileEntityLootableInventory.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
---- /dev/null
-+++ b/src/main/java/com/destroystokyo/paper/loottable/PaperTileEntityLootableInventory.java
-@@ -0,0 +0,0 @@
-+package com.destroystokyo.paper.loottable;
-+
-+import io.papermc.paper.util.MCUtil;
-+import net.minecraft.core.registries.Registries;
-+import net.minecraft.resources.ResourceKey;
-+import net.minecraft.world.level.Level;
-+import net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity;
-+import org.bukkit.Bukkit;
-+import org.bukkit.craftbukkit.util.CraftNamespacedKey;
-+
-+public class PaperTileEntityLootableInventory implements PaperLootableBlockInventory {
-+ private RandomizableContainerBlockEntity tileEntityLootable;
-+
-+ public PaperTileEntityLootableInventory(RandomizableContainerBlockEntity tileEntityLootable) {
-+ this.tileEntityLootable = tileEntityLootable;
-+ }
-+
-+ @Override
-+ public org.bukkit.loot.LootTable getLootTable() {
-+ return tileEntityLootable.lootTable != null ? Bukkit.getLootTable(CraftNamespacedKey.fromMinecraft(tileEntityLootable.lootTable.location())) : null;
-+ }
-+
-+ @Override
-+ public void setLootTable(org.bukkit.loot.LootTable table, long seed) {
-+ setLootTable(table);
-+ setSeed(seed);
-+ }
-+
-+ @Override
-+ public void setLootTable(org.bukkit.loot.LootTable table) {
-+ tileEntityLootable.lootTable = (table == null) ? null : ResourceKey.create(Registries.LOOT_TABLE, CraftNamespacedKey.toMinecraft(table.getKey()));
-+ }
-+
-+ @Override
-+ public void setSeed(long seed) {
-+ tileEntityLootable.lootTableSeed = seed;
-+ }
-+
-+ @Override
-+ public long getSeed() {
-+ return tileEntityLootable.lootTableSeed;
-+ }
-+
-+ @Override
-+ public PaperLootableInventoryData getLootableData() {
-+ return tileEntityLootable.lootableData;
-+ }
-+
-+ @Override
-+ public RandomizableContainerBlockEntity getTileEntity() {
-+ return tileEntityLootable;
-+ }
-+
-+ @Override
-+ public LootableInventory getAPILootableInventory() {
-+ Level world = tileEntityLootable.getLevel();
-+ if (world == null) {
-+ return null;
-+ }
-+ return (LootableInventory) getBukkitWorld().getBlockAt(MCUtil.toLocation(world, tileEntityLootable.getBlockPos())).getState();
-+ }
-+
-+ @Override
-+ public Level getNMSWorld() {
-+ return tileEntityLootable.getLevel();
-+ }
-+}
-diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
+diff --git a/src/main/java/net/minecraft/world/RandomizableContainer.java b/src/main/java/net/minecraft/world/RandomizableContainer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/net/minecraft/world/entity/Entity.java
-+++ b/src/main/java/net/minecraft/world/entity/Entity.java
-@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+--- a/src/main/java/net/minecraft/world/RandomizableContainer.java
++++ b/src/main/java/net/minecraft/world/RandomizableContainer.java
+@@ -0,0 +0,0 @@ public interface RandomizableContainer extends Container {
+
+ void setLootTable(@Nullable ResourceKey lootTable);
+
+- default void setLootTable(ResourceKey lootTableId, long lootTableSeed) {
++ default void setLootTable(@Nullable ResourceKey lootTableId, long lootTableSeed) { // Paper - add nullable
+ this.setLootTable(lootTableId);
+ this.setLootTableSeed(lootTableSeed);
}
- // Paper end - Share random for entities to make them more random
+@@ -0,0 +0,0 @@ public interface RandomizableContainer extends Container {
+ default boolean tryLoadLootTable(CompoundTag nbt) {
+ if (nbt.contains("LootTable", 8)) {
+ this.setLootTable(ResourceKey.create(Registries.LOOT_TABLE, new ResourceLocation(nbt.getString("LootTable"))));
++ if (this.lootableData() != null && this.getLootTable() != null) this.lootableData().loadNbt(nbt); // Paper - LootTable API
+ if (nbt.contains("LootTableSeed", 4)) {
+ this.setLootTableSeed(nbt.getLong("LootTableSeed"));
+ } else {
+ this.setLootTableSeed(0L);
+ }
-+ public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper
- private CraftEntity bukkitEntity;
+- return true;
++ return this.lootableData() == null; // Paper - only track the loot table if there is chance for replenish
+ } else {
+ return false;
+ }
+@@ -0,0 +0,0 @@ public interface RandomizableContainer extends Container {
+ return false;
+ } else {
+ nbt.putString("LootTable", resourceKey.location().toString());
++ if (this.lootableData() != null) this.lootableData().saveNbt(nbt); // Paper - LootTable API
+ long l = this.getLootTableSeed();
+ if (l != 0L) {
+ nbt.putLong("LootTableSeed", l);
+ }
- public CraftEntity getBukkitEntity() {
+- return true;
++ return this.lootableData() == null; // Paper - only track the loot table if there is chance for replenish
+ }
+ }
+
+@@ -0,0 +0,0 @@ public interface RandomizableContainer extends Container {
+ Level level = this.getLevel();
+ BlockPos blockPos = this.getBlockPos();
+ ResourceKey resourceKey = this.getLootTable();
+- if (resourceKey != null && level != null && level.getServer() != null) {
++ if (resourceKey != null && level != null && level.getServer() != null && (this.lootableData() == null || this.lootableData().shouldReplenish(this, com.destroystokyo.paper.loottable.PaperLootableInventoryData.CONTAINER, player))) { // Paper - LootTable API
+ LootTable lootTable = level.getServer().reloadableRegistries().getLootTable(resourceKey);
+ if (player instanceof ServerPlayer) {
+ CriteriaTriggers.GENERATE_LOOT.trigger((ServerPlayer)player, resourceKey);
+ }
+
+- this.setLootTable(null);
++ // Paper start - LootTable API
++ if (this.lootableData() == null || this.lootableData().shouldClearLootTable(this, com.destroystokyo.paper.loottable.PaperLootableInventoryData.CONTAINER, player)) {
++ this.setLootTable(null);
++ }
++ // Paper end - LootTable API
+ LootParams.Builder builder = new LootParams.Builder((ServerLevel)level).withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(blockPos));
+ if (player != null) {
+ builder.withLuck(player.getLuck()).withParameter(LootContextParams.THIS_ENTITY, player);
+@@ -0,0 +0,0 @@ public interface RandomizableContainer extends Container {
+ lootTable.fill(this, builder.create(LootContextParamSets.CHEST), this.getLootTableSeed());
+ }
+ }
++
++ // Paper start - LootTable API
++ @Nullable @org.jetbrains.annotations.Contract(pure = true)
++ default com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData() {
++ return null; // some containers don't really have a "replenish" ability like decorated pots
++ }
++
++ default com.destroystokyo.paper.loottable.PaperLootableInventory getLootableInventory() {
++ final org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(java.util.Objects.requireNonNull(this.getLevel(), "Cannot manage loot tables on block entities not in world"), this.getBlockPos());
++ return (com.destroystokyo.paper.loottable.PaperLootableInventory) block.getState(false);
++ }
++ // Paper end - LootTable API
+ }
diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java
@@ -528,75 +603,42 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
public ResourceKey lootTable;
public long lootTableSeed;
-+ // Paper start
-+ {
-+ this.lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData(new com.destroystokyo.paper.loottable.PaperContainerEntityLootableInventory(this));
-+ }
-+ @Override
-+ public Entity getEntity() {
-+ return this;
-+ }
++ // Paper start - LootTable API
++ final com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData();
+
+ @Override
-+ public com.destroystokyo.paper.loottable.PaperLootableInventoryData getLootableData() {
++ public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData() {
+ return this.lootableData;
+ }
-+ // Paper end
++ // Paper end - LootTable API
// CraftBukkit start
public List transaction = new java.util.ArrayList();
private int maxStack = MAX_STACK;
-@@ -0,0 +0,0 @@ public abstract class AbstractMinecartContainer extends AbstractMinecart impleme
- @Override
- protected void addAdditionalSaveData(CompoundTag nbt) {
- super.addAdditionalSaveData(nbt);
-+ this.lootableData.saveNbt(nbt); // Paper
- this.addChestVehicleSaveData(nbt, this.registryAccess());
- }
-
- @Override
- protected void readAdditionalSaveData(CompoundTag nbt) {
- super.readAdditionalSaveData(nbt);
-+ this.lootableData.loadNbt(nbt); // Paper
- this.readChestVehicleSaveData(nbt, this.registryAccess());
- }
-
diff --git a/src/main/java/net/minecraft/world/entity/vehicle/ChestBoat.java b/src/main/java/net/minecraft/world/entity/vehicle/ChestBoat.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/vehicle/ChestBoat.java
+++ b/src/main/java/net/minecraft/world/entity/vehicle/ChestBoat.java
@@ -0,0 +0,0 @@ public class ChestBoat extends Boat implements HasCustomInventoryScreen, Contain
+ @Nullable
@Override
- protected void addAdditionalSaveData(CompoundTag nbt) {
- super.addAdditionalSaveData(nbt);
-+ this.lootableData.saveNbt(nbt); // Paper
- this.addChestVehicleSaveData(nbt, this.registryAccess());
- }
-
- @Override
- protected void readAdditionalSaveData(CompoundTag nbt) {
- super.readAdditionalSaveData(nbt);
-+ this.lootableData.loadNbt(nbt); // Paper
- this.readChestVehicleSaveData(nbt, this.registryAccess());
- }
-
+ public AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player) {
+- if (this.lootTable != null && player.isSpectator()) {
++ if (this.lootTable != null && player.isSpectator()) { // Paper - LootTable API (TODO spectators can open chests that aren't ready to be re-generated but this doesn't support that)
+ return null;
+ } else {
+ this.unpackLootTable(playerInventory.player);
@@ -0,0 +0,0 @@ public class ChestBoat extends Boat implements HasCustomInventoryScreen, Contain
this.level().gameEvent((Holder) GameEvent.CONTAINER_CLOSE, this.position(), GameEvent.Context.of((Entity) player));
}
-+ // Paper start
-+ {
-+ this.lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData(new com.destroystokyo.paper.loottable.PaperContainerEntityLootableInventory(this));
-+ }
-+ @Override
-+ public Entity getEntity() {
-+ return this;
-+ }
++ // Paper start - LootTable API
++ final com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData();
+
+ @Override
-+ public com.destroystokyo.paper.loottable.PaperLootableInventoryData getLootableData() {
++ public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData() {
+ return this.lootableData;
+ }
-+ // Paper end
++ // Paper end - LootTable API
// CraftBukkit start
public List transaction = new java.util.ArrayList();
private int maxStack = MAX_STACK;
@@ -605,6 +647,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java
+++ b/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java
@@ -0,0 +0,0 @@ public interface ContainerEntity extends Container, MenuProvider {
+ default void addChestVehicleSaveData(CompoundTag nbt, HolderLookup.Provider registriesLookup) {
+ if (this.getLootTable() != null) {
+ nbt.putString("LootTable", this.getLootTable().location().toString());
++ this.lootableData().saveNbt(nbt); // Paper
if (this.getLootTableSeed() != 0L) {
nbt.putLong("LootTableSeed", this.getLootTableSeed());
}
@@ -615,9 +661,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
}
default void readChestVehicleSaveData(CompoundTag nbt, HolderLookup.Provider registriesLookup) {
-@@ -0,0 +0,0 @@ public interface ContainerEntity extends Container, MenuProvider {
+ this.clearItemStacks();
if (nbt.contains("LootTable", 8)) {
this.setLootTable(ResourceKey.create(Registries.LOOT_TABLE, new ResourceLocation(nbt.getString("LootTable"))));
++ // Paper start - LootTable API
++ if (this.getLootTable() != null) {
++ this.lootableData().loadNbt(nbt);
++ }
++ // Paper end - LootTable API
this.setLootTableSeed(nbt.getLong("LootTableSeed"));
- } else {
- ContainerHelper.loadAllItems(nbt, this.getItemStacks(), registriesLookup);
@@ -631,14 +682,18 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
default void unpackChestVehicleLootTable(@Nullable Player player) {
MinecraftServer minecraftServer = this.level().getServer();
- if (this.getLootTable() != null && minecraftServer != null) {
-+ if (this.getLootableData().shouldReplenish(player) && minecraftServer != null) { // Paper
++ if (minecraftServer != null && this.lootableData().shouldReplenish(this, com.destroystokyo.paper.loottable.PaperLootableInventoryData.ENTITY, player)) { // Paper - LootTable API
LootTable lootTable = minecraftServer.reloadableRegistries().getLootTable(this.getLootTable());
if (player != null) {
CriteriaTriggers.GENERATE_LOOT.trigger((ServerPlayer)player, this.getLootTable());
}
- this.setLootTable(null);
-+ this.getLootableData().processRefill(player); // Paper
++ // Paper start - LootTable API
++ if (this.lootableData().shouldClearLootTable(this, com.destroystokyo.paper.loottable.PaperLootableInventoryData.ENTITY, player)) {
++ this.setLootTable(null);
++ }
++ // Paper end - LootTable API
LootParams.Builder builder = new LootParams.Builder((ServerLevel)this.level()).withParameter(LootContextParams.ORIGIN, this.position());
if (player != null) {
builder.withLuck(player.getLuck()).withParameter(LootContextParams.THIS_ENTITY, player);
@@ -646,85 +701,35 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
default boolean isChestVehicleStillValid(Player player) {
return !this.isRemoved() && player.canInteractWithEntity(this.getBoundingBox(), 4.0);
}
-+ // Paper start
-+ default Entity getEntity() {
-+ throw new UnsupportedOperationException();
++
++ // Paper start - LootTable API
++ default com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData() {
++ throw new UnsupportedOperationException("Implement this method");
+ }
+
-+ default com.destroystokyo.paper.loottable.PaperLootableInventoryData getLootableData() {
-+ throw new UnsupportedOperationException();
++ default com.destroystokyo.paper.loottable.PaperLootableInventory getLootableInventory() {
++ return ((com.destroystokyo.paper.loottable.PaperLootableInventory) ((net.minecraft.world.entity.Entity) this).getBukkitEntity());
+ }
-+ // Paper end
++ // Paper end - LootTable API
}
diff --git a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
@@ -0,0 +0,0 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc
- @Nullable
- public ResourceKey lootTable;
- public long lootTableSeed = 0L;
-+ public final com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData(new com.destroystokyo.paper.loottable.PaperTileEntityLootableInventory(this)); // Paper
-
- protected RandomizableContainerBlockEntity(BlockEntityType> type, BlockPos pos, BlockState state) {
- super(type, pos, state);
-@@ -0,0 +0,0 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc
- this.lootTableSeed = lootTableSeed;
+ nbt.remove("LootTable");
+ nbt.remove("LootTableSeed");
}
-
-+ // Paper start
-+ @Override
-+ public boolean tryLoadLootTable(final net.minecraft.nbt.CompoundTag nbt) {
-+ // Copied from super with changes, always check the original method
-+ this.lootableData.loadNbt(nbt); // Paper
-+ if (nbt.contains("LootTable", 8)) {
-+ this.setLootTable(net.minecraft.Optionull.map(net.minecraft.resources.ResourceLocation.tryParse(nbt.getString("LootTable")), rl -> ResourceKey.create(net.minecraft.core.registries.Registries.LOOT_TABLE, rl)));
-+ try { if (this.lootTable != null) org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(this.lootTable.location()); } catch (IllegalArgumentException ex) { this.lootTable = null; } // Paper - validate
-+ if (nbt.contains("LootTableSeed", 4)) {
-+ this.setLootTableSeed(nbt.getLong("LootTableSeed"));
-+ } else {
-+ this.setLootTableSeed(0L);
-+ }
-+ return false; // Paper - always load the items, table may still remain
-+ } else {
-+ return false;
-+ }
-+ }
++
++ // Paper start - LootTable API
++ final com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData(); // Paper
+
+ @Override
-+ public boolean trySaveLootTable(final net.minecraft.nbt.CompoundTag nbt) {
-+ this.lootableData.saveNbt(nbt);
-+ RandomizableContainer.super.trySaveLootTable(nbt);
-+ return false;
++ public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData() {
++ return this.lootableData;
+ }
-+
-+ @Override
-+ public void unpackLootTable(@org.jetbrains.annotations.Nullable final Player player) {
-+ // Copied from super with changes, always check the original method
-+ net.minecraft.world.level.Level level = this.getLevel();
-+ BlockPos blockPos = this.getBlockPos();
-+ ResourceKey resourceKey = this.getLootTable();
-+ if (this.lootableData.shouldReplenish(player) && resourceKey != null && level != null && level.getServer() != null) { // Paper
-+ net.minecraft.world.level.storage.loot.LootTable lootTable = level.getServer().reloadableRegistries().getLootTable(resourceKey);
-+ if (player instanceof net.minecraft.server.level.ServerPlayer) {
-+ net.minecraft.advancements.CriteriaTriggers.GENERATE_LOOT.trigger((net.minecraft.server.level.ServerPlayer)player, resourceKey);
-+ }
-+
-+ this.lootableData.processRefill(player); // Paper
-+ net.minecraft.world.level.storage.loot.LootParams.Builder builder = (new net.minecraft.world.level.storage.loot.LootParams.Builder((net.minecraft.server.level.ServerLevel)level)).withParameter(net.minecraft.world.level.storage.loot.parameters.LootContextParams.ORIGIN, net.minecraft.world.phys.Vec3.atCenterOf(blockPos));
-+ if (player != null) {
-+ builder.withLuck(player.getLuck()).withParameter(net.minecraft.world.level.storage.loot.parameters.LootContextParams.THIS_ENTITY, player);
-+ }
-+
-+ lootTable.fill(this, builder.create(net.minecraft.world.level.storage.loot.parameters.LootContextParamSets.CHEST), this.getLootTableSeed());
-+ }
-+
-+ }
-+ // Paper end
-+
- @Override
- public boolean isEmpty() {
- this.unpackLootTable(null);
++ // Paper end - LootTable API
+ }
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBrushableBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBrushableBlock.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBrushableBlock.java
@@ -734,25 +739,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
}
- private void setLootTable(LootTable table, long seed) {
++ @Override // Paper - this is now an override
+ public void setLootTable(LootTable table, long seed) { // Paper - make public since it overrides a public method
this.getSnapshot().setLootTable(CraftLootTable.bukkitToMinecraft(table), seed);
}
-diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java b/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java
-index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
---- a/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java
-+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java
-@@ -0,0 +0,0 @@ import org.bukkit.craftbukkit.CraftWorld;
- import org.bukkit.craftbukkit.inventory.CraftInventory;
- import org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest;
- import org.bukkit.inventory.Inventory;
-+import com.destroystokyo.paper.loottable.PaperLootableBlockInventory; // Paper
-
--public class CraftChest extends CraftLootable implements Chest {
-+public class CraftChest extends CraftLootable implements Chest, PaperLootableBlockInventory { // Paper
-
- public CraftChest(World world, ChestBlockEntity tileEntity) {
- super(world, tileEntity);
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftLootable.java b/src/main/java/org/bukkit/craftbukkit/block/CraftLootable.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftLootable.java
@@ -766,6 +757,42 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
public CraftLootable(World world, T tileEntity) {
super(world, tileEntity);
+@@ -0,0 +0,0 @@ public abstract class CraftLootable
+ }
+ }
+
++ // Paper start - move to PaperLootableBlockInventory
+ @Override
+- public LootTable getLootTable() {
+- return CraftLootTable.minecraftToBukkit(this.getSnapshot().lootTable);
++ public net.minecraft.world.level.Level getNMSWorld() {
++ return ((org.bukkit.craftbukkit.CraftWorld) this.getWorld()).getHandle();
+ }
+
+ @Override
+- public void setLootTable(LootTable table) {
+- this.setLootTable(table, this.getSeed());
+- }
+-
+- @Override
+- public long getSeed() {
+- return this.getSnapshot().lootTableSeed;
+- }
+-
+- @Override
+- public void setSeed(long seed) {
+- this.setLootTable(this.getLootTable(), seed);
+- }
+-
+- public void setLootTable(LootTable table, long seed) {
+- this.getSnapshot().setLootTable(CraftLootTable.bukkitToMinecraft(table), seed);
++ public net.minecraft.world.RandomizableContainer getRandomizableContainer() {
++ return this.getSnapshot();
+ }
++ // Paper end - move to PaperLootableBlockInventory
+
+ @Override
+ public abstract CraftLootable copy();
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java
@@ -781,14 +808,35 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
public CraftChestBoat(CraftServer server, ChestBoat entity) {
@@ -0,0 +0,0 @@ public class CraftChestBoat extends CraftBoat implements org.bukkit.entity.Chest
- return this.getHandle().getLootTableSeed();
+ return this.inventory;
}
+- @Override
+- public void setLootTable(LootTable table) {
+- this.setLootTable(table, this.getSeed());
+- }
+-
+- @Override
+- public LootTable getLootTable() {
+- return CraftLootTable.minecraftToBukkit(this.getHandle().getLootTable());
+- }
+-
+- @Override
+- public void setSeed(long seed) {
+- this.setLootTable(this.getLootTable(), seed);
+- }
+-
+- @Override
+- public long getSeed() {
+- return this.getHandle().getLootTableSeed();
+- }
+-
- private void setLootTable(LootTable table, long seed) {
-+ public void setLootTable(LootTable table, long seed) { // Paper - change visibility since it overrides a public method
- this.getHandle().setLootTable(CraftLootTable.bukkitToMinecraft(table));
- this.getHandle().setLootTableSeed(seed);
- }
+- this.getHandle().setLootTable(CraftLootTable.bukkitToMinecraft(table));
+- this.getHandle().setLootTableSeed(seed);
+- }
++ // Paper - moved loot table logic to PaperLootableEntityInventory
+ }
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java
@@ -802,6 +850,48 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
private final CraftInventory inventory;
public CraftMinecartChest(CraftServer server, MinecartChest entity) {
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartContainer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartContainer.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartContainer.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartContainer.java
+@@ -0,0 +0,0 @@ import org.bukkit.craftbukkit.CraftServer;
+ import org.bukkit.loot.LootTable;
+ import org.bukkit.loot.Lootable;
+
+-public abstract class CraftMinecartContainer extends CraftMinecart implements Lootable {
++public abstract class CraftMinecartContainer extends CraftMinecart implements com.destroystokyo.paper.loottable.PaperLootableEntityInventory { // Paper
+
+ public CraftMinecartContainer(CraftServer server, AbstractMinecart entity) {
+ super(server, entity);
+@@ -0,0 +0,0 @@ public abstract class CraftMinecartContainer extends CraftMinecart implements Lo
+ return (AbstractMinecartContainer) this.entity;
+ }
+
+- @Override
+- public void setLootTable(LootTable table) {
+- this.setLootTable(table, this.getSeed());
+- }
+-
+- @Override
+- public LootTable getLootTable() {
+- return CraftLootTable.minecraftToBukkit(this.getHandle().lootTable);
+- }
+-
+- @Override
+- public void setSeed(long seed) {
+- this.setLootTable(this.getLootTable(), seed);
+- }
+-
+- @Override
+- public long getSeed() {
+- return this.getHandle().lootTableSeed;
+- }
+-
+- public void setLootTable(LootTable table, long seed) {
+- this.getHandle().setLootTable(CraftLootTable.bukkitToMinecraft(table), seed);
+- }
++ // Paper - moved loot table logic to PaperLootableEntityInventory
+ }
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java
diff --git a/patches/server/Optimize-Collision-to-not-load-chunks.patch b/patches/server/Optimize-Collision-to-not-load-chunks.patch
index 77e847fc71..d71189a1d3 100644
--- a/patches/server/Optimize-Collision-to-not-load-chunks.patch
+++ b/patches/server/Optimize-Collision-to-not-load-chunks.patch
@@ -30,9 +30,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+ // Paper end - Share random for entities to make them more random
public org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason; // Paper - Entity#getEntitySpawnReason
- public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper
+ public boolean collisionLoadChunks = false; // Paper
private CraftEntity bukkitEntity;
diff --git a/patches/server/Validate-ResourceLocation-in-NBT-reading.patch b/patches/server/Validate-ResourceLocation-in-NBT-reading.patch
index 412e25f0fb..a84732b52a 100644
--- a/patches/server/Validate-ResourceLocation-in-NBT-reading.patch
+++ b/patches/server/Validate-ResourceLocation-in-NBT-reading.patch
@@ -39,6 +39,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
this.namespace = namespace;
this.path = path;
}
+diff --git a/src/main/java/net/minecraft/world/RandomizableContainer.java b/src/main/java/net/minecraft/world/RandomizableContainer.java
+index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
+--- a/src/main/java/net/minecraft/world/RandomizableContainer.java
++++ b/src/main/java/net/minecraft/world/RandomizableContainer.java
+@@ -0,0 +0,0 @@ public interface RandomizableContainer extends Container {
+
+ default boolean tryLoadLootTable(CompoundTag nbt) {
+ if (nbt.contains("LootTable", 8)) {
+- this.setLootTable(ResourceKey.create(Registries.LOOT_TABLE, new ResourceLocation(nbt.getString("LootTable"))));
++ this.setLootTable(net.minecraft.Optionull.map(ResourceLocation.tryParse(nbt.getString("LootTable")), rl -> ResourceKey.create(Registries.LOOT_TABLE, rl))); // Paper - Validate ResourceLocation);
+ if (this.lootableData() != null && this.getLootTable() != null) this.lootableData().loadNbt(nbt); // Paper - LootTable API
+ if (nbt.contains("LootTableSeed", 4)) {
+ this.setLootTableSeed(nbt.getLong("LootTableSeed"));
diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/entity/EntityType.java
@@ -107,9 +120,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
if (nbt.contains("LootTable", 8)) {
- this.setLootTable(ResourceKey.create(Registries.LOOT_TABLE, new ResourceLocation(nbt.getString("LootTable"))));
+ this.setLootTable(net.minecraft.Optionull.map(ResourceLocation.tryParse(nbt.getString("LootTable")), rl -> ResourceKey.create(Registries.LOOT_TABLE, rl))); // Paper - Validate ResourceLocation
- this.setLootTableSeed(nbt.getLong("LootTableSeed"));
- }
- ContainerHelper.loadAllItems(nbt, this.getItemStacks(), registriesLookup); // Paper - always save the items, table may still remain
+ // Paper start - LootTable API
+ if (this.getLootTable() != null) {
+ this.lootableData().loadNbt(nbt);
diff --git a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java