--- a/net/minecraft/world/RandomizableContainer.java
+++ b/net/minecraft/world/RandomizableContainer.java
@@ -26,7 +_,7 @@
 
     void setLootTable(@Nullable ResourceKey<LootTable> lootTable);
 
-    default void setLootTable(ResourceKey<LootTable> lootTable, long seed) {
+    default void setLootTable(@Nullable ResourceKey<LootTable> lootTable, long seed) { // Paper - add nullable
         this.setLootTable(lootTable);
         this.setLootTableSeed(seed);
     }
@@ -49,8 +_,9 @@
     default boolean tryLoadLootTable(CompoundTag tag) {
         ResourceKey<LootTable> resourceKey = tag.read("LootTable", LootTable.KEY_CODEC).orElse(null);
         this.setLootTable(resourceKey);
+        if (this.lootableData() != null && resourceKey != null) this.lootableData().loadNbt(tag); // Paper - LootTable API
         this.setLootTableSeed(tag.getLongOr("LootTableSeed", 0L));
-        return resourceKey != null;
+        return resourceKey != null && this.lootableData() == null; // Paper - only track the loot table if there is chance for replenish
     }
 
     default boolean trySaveLootTable(CompoundTag tag) {
@@ -59,26 +_,42 @@
             return false;
         } else {
             tag.store("LootTable", LootTable.KEY_CODEC, lootTable);
+            if (this.lootableData() != null) this.lootableData().saveNbt(tag); // Paper - LootTable API
             long lootTableSeed = this.getLootTableSeed();
             if (lootTableSeed != 0L) {
                 tag.putLong("LootTableSeed", lootTableSeed);
             }
 
-            return true;
+            return this.lootableData() == null; // Paper - only track the loot table if there is chance for replenish
         }
     }
 
     default void unpackLootTable(@Nullable Player player) {
+        // Paper start - LootTable API
+        this.unpackLootTable(player, false);
+    }
+    default void unpackLootTable(@Nullable final Player player, final boolean forceClearLootTable) {
+        // Paper end - LootTable API
         Level level = this.getLevel();
         BlockPos blockPos = this.getBlockPos();
         ResourceKey<LootTable> lootTable = this.getLootTable();
-        if (lootTable != null && level != null && level.getServer() != null) {
+        // Paper start - LootTable API
+        lootReplenish: if (lootTable != null && level != null && level.getServer() != null) {
+            if (this.lootableData() != null && !this.lootableData().shouldReplenish(this, com.destroystokyo.paper.loottable.PaperLootableInventoryData.CONTAINER, player)) {
+                if (forceClearLootTable) {
+                    this.setLootTable(null);
+                }
+                break lootReplenish;
+            }
+            // Paper end - LootTable API
             LootTable lootTable1 = level.getServer().reloadableRegistries().getLootTable(lootTable);
             if (player instanceof ServerPlayer) {
                 CriteriaTriggers.GENERATE_LOOT.trigger((ServerPlayer)player, lootTable);
             }
 
+            if (forceClearLootTable || this.lootableData() == null || this.lootableData().shouldClearLootTable(this, com.destroystokyo.paper.loottable.PaperLootableInventoryData.CONTAINER, player)) { // Paper - LootTable API
             this.setLootTable(null);
+            } // Paper - 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);
@@ -87,4 +_,17 @@
             lootTable1.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
 }
