diff options
Diffstat (limited to 'patches/server/0097-LootTable-API-and-replenishable-lootables.patch')
-rw-r--r-- | patches/server/0097-LootTable-API-and-replenishable-lootables.patch | 880 |
1 files changed, 485 insertions, 395 deletions
diff --git a/patches/server/0097-LootTable-API-and-replenishable-lootables.patch b/patches/server/0097-LootTable-API-and-replenishable-lootables.patch index d78da70503..32294e4e41 100644 --- a/patches/server/0097-LootTable-API-and-replenishable-lootables.patch +++ b/patches/server/0097-LootTable-API-and-replenishable-lootables.patch @@ -15,222 +15,240 @@ 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..7f5283a8ee74253e326fe994f20ae00cf3c75e1b +index 0000000000000000000000000000000000000000..a53d51be1da25b87f2bc0a29a196d8f9996dbd2b --- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/loottable/PaperContainerEntityLootableInventory.java -@@ -0,0 +1,66 @@ ++++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootable.java +@@ -0,0 +1,21 @@ +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; -+ -+public class PaperContainerEntityLootableInventory implements PaperLootableEntityInventory { -+ -+ private final ContainerEntity entity; -+ -+ public PaperContainerEntityLootableInventory(ContainerEntity entity) { -+ this.entity = entity; -+ } ++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; + -+ @Override -+ public org.bukkit.loot.LootTable getLootTable() { -+ return entity.getLootTable() != null ? Optionull.map(entity.getLootTable(), rk -> Bukkit.getLootTable(CraftNamespacedKey.fromMinecraft(rk.location()))) : null; -+ } ++@DefaultQualifier(NonNull.class) ++public interface PaperLootable extends Lootable { + + @Override -+ public void setLootTable(org.bukkit.loot.LootTable table, long seed) { -+ setLootTable(table); -+ setSeed(seed); ++ default void setLootTable(final @Nullable LootTable table) { ++ this.setLootTable(table, this.getSeed()); + } + + @Override -+ public void setSeed(long seed) { -+ entity.setLootTableSeed(seed); ++ 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..9e9ea13234703d3e4a39eed2b007e8be69dfbd12 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableBlock.java +@@ -0,0 +1,27 @@ ++package com.destroystokyo.paper.loottable; + -+ @Override -+ public long getSeed() { -+ return entity.getLootTableSeed(); -+ } ++import net.minecraft.world.RandomizableContainer; ++import org.bukkit.craftbukkit.CraftLootTable; ++import org.bukkit.loot.LootTable; ++import org.checkerframework.checker.nullness.qual.Nullable; + -+ @Override -+ public void setLootTable(org.bukkit.loot.LootTable table) { -+ entity.setLootTable((table == null) ? null : ResourceKey.create(Registries.LOOT_TABLE, CraftNamespacedKey.toMinecraft(table.getKey()))); -+ } ++public interface PaperLootableBlock extends PaperLootable { + -+ @Override -+ public PaperLootableInventoryData getLootableData() { -+ return entity.getLootableData(); -+ } ++ RandomizableContainer getRandomizableContainer(); + ++ /* Lootable */ + @Override -+ public Entity getHandle() { -+ return entity.getEntity(); ++ default @Nullable LootTable getLootTable() { ++ return CraftLootTable.minecraftToBukkit(this.getRandomizableContainer().getLootTable()); + } + + @Override -+ public LootableInventory getAPILootableInventory() { -+ return (LootableInventory) entity.getEntity().getBukkitEntity(); ++ default void setLootTable(final @Nullable LootTable table, final long seed) { ++ this.getRandomizableContainer().setLootTable(CraftLootTable.bukkitToMinecraft(table), seed); + } + + @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 new file mode 100644 -index 0000000000000000000000000000000000000000..24c6ff57cd25533e71f8a1d0b3c0ece2fdbbf87e +index 0000000000000000000000000000000000000000..0699c60920333ea1fec04e3c94d952244d2abeae --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableBlockInventory.java -@@ -0,0 +1,33 @@ +@@ -0,0 +1,26 @@ +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 { ++@DefaultQualifier(NonNull.class) ++public interface PaperLootableBlockInventory extends LootableBlockInventory, PaperLootableInventory, PaperLootableBlock { + -+ RandomizableContainerBlockEntity getTileEntity(); ++ /* PaperLootableInventory */ ++ @Override ++ default PaperLootableInventoryData lootableDataForAPI() { ++ return Objects.requireNonNull(this.getRandomizableContainer().lootableData(), "Can only manage loot tables on tile entities with lootableData"); ++ } + ++ /* LootableBlockInventory */ + @Override -+ default LootableInventory getAPILootableInventory() { -+ return this; ++ default Block getBlock() { ++ 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..de528b8bafd75b6f14b1384157f3a8a27e06b4a2 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableEntity.java +@@ -0,0 +1,29 @@ ++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 Level getNMSWorld() { -+ return this.getTileEntity().getLevel(); ++ default @Nullable LootTable getLootTable() { ++ return CraftLootTable.minecraftToBukkit(this.getHandle().getLootTable()); + } + -+ 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()); ++ @Override ++ default void setLootTable(final @Nullable LootTable table, final long seed) { ++ this.getHandle().setLootTable(CraftLootTable.bukkitToMinecraft(table)); ++ this.getHandle().setLootTableSeed(seed); + } + + @Override -+ default PaperLootableInventoryData getLootableData() { -+ return this.getTileEntity().lootableData; ++ 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 new file mode 100644 -index 0000000000000000000000000000000000000000..2fba5bc0f982e143ad5f5bda55d768edc5f847df +index 0000000000000000000000000000000000000000..5c57acc95f638a8bcb351ae44e9434a056835470 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableEntityInventory.java -@@ -0,0 +1,28 @@ +@@ -0,0 +1,26 @@ +package com.destroystokyo.paper.loottable; + +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(); ++@DefaultQualifier(NonNull.class) ++public interface PaperLootableEntityInventory extends LootableEntityInventory, PaperLootableInventory, PaperLootableEntity { + ++ /* PaperLootableInventory */ + @Override -+ default LootableInventory getAPILootableInventory() { -+ return this; -+ } -+ -+ default Entity getEntity() { -+ return getHandle().getBukkitEntity(); ++ default Level getNMSWorld() { ++ return this.getHandle().level(); + } + + @Override -+ default Level getNMSWorld() { -+ return getHandle().getCommandSenderWorld(); ++ default PaperLootableInventoryData lootableDataForAPI() { ++ return this.getHandle().lootableData(); + } + -+ @Override -+ default PaperLootableInventoryData getLootableData() { -+ return 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 new file mode 100644 -index 0000000000000000000000000000000000000000..8e6dac2cef7af26ad74928eff631c1826c2980bb +index 0000000000000000000000000000000000000000..9e7c22ef49f1699df298f7121d50d27b4cb0923f --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventory.java -@@ -0,0 +1,75 @@ +@@ -0,0 +1,79 @@ +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,31 +256,36 @@ index 0000000000000000000000000000000000000000..8e6dac2cef7af26ad74928eff631c182 + 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 new file mode 100644 -index 0000000000000000000000000000000000000000..6e72c43b9d3834eb91c02ce68e7d114ad907812d +index 0000000000000000000000000000000000000000..fea92d280c6817cee1f18379d5ed51a3a22ee344 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java -@@ -0,0 +1,188 @@ +@@ -0,0 +1,249 @@ +package com.destroystokyo.paper.loottable; + +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..6e72c43b9d3834eb91c02ce68e7d114a + private long lastFill = -1; + private long nextRefill = -1; + private int numRefills = 0; -+ private Map<UUID, Long> lootedPlayers; -+ private final PaperLootableInventory lootable; -+ -+ public PaperLootableInventoryData(PaperLootableInventory lootable) { -+ this.lootable = lootable; -+ } ++ private @Nullable Map<UUID, Long> lootedPlayers; + + long getLastFill() { + return this.lastFill; @@ -285,22 +303,21 @@ index 0000000000000000000000000000000000000000..6e72c43b9d3834eb91c02ce68e7d114a + 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 <T> boolean shouldReplenish(final T lootTableHolder, final LootTableInterface<T> 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..6e72c43b9d3834eb91c02ce68e7d114a + 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..6e72c43b9d3834eb91c02ce68e7d114a + + + 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<T> { ++ ++ WorldConfiguration paperConfig(T holder); ++ ++ void setSeed(T holder, long seed); ++ ++ boolean hasLootTable(T holder); ++ ++ LootableInventory getInventoryForEvent(T holder); ++ } ++ ++ public static final LootTableInterface<RandomizableContainer> 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<ContainerEntity> 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 <T> boolean shouldClearLootTable(final T lootTableHolder, final LootTableInterface<T> 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<UUID, Long> entry : this.lootedPlayers.entrySet()) { -+ CompoundTag cmp = new CompoundTag(); ++ final ListTag list = new ListTag(); ++ for (final Map.Entry<UUID, Long> 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..6e72c43b9d3834eb91c02ce68e7d114a + } + + 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,184 +506,151 @@ index 0000000000000000000000000000000000000000..6e72c43b9d3834eb91c02ce68e7d114a + 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..fb72bdea520ccc0928cfbda0569e02a1917a7e86 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/loottable/PaperTileEntityLootableInventory.java -@@ -0,0 +1,67 @@ -+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; -+ } +diff --git a/src/main/java/net/minecraft/world/RandomizableContainer.java b/src/main/java/net/minecraft/world/RandomizableContainer.java +index 155902bc2b99ba36003bceb37ba34958952900d3..68fadc351464dde459eb4cc86660cf9add40d04e 100644 +--- a/src/main/java/net/minecraft/world/RandomizableContainer.java ++++ b/src/main/java/net/minecraft/world/RandomizableContainer.java +@@ -28,7 +28,7 @@ public interface RandomizableContainer extends Container { + + void setLootTable(@Nullable ResourceKey<LootTable> lootTable); + +- default void setLootTable(ResourceKey<LootTable> lootTableId, long lootTableSeed) { ++ default void setLootTable(@Nullable ResourceKey<LootTable> lootTableId, long lootTableSeed) { // Paper - add nullable + this.setLootTable(lootTableId); + this.setLootTableSeed(lootTableSeed); + } +@@ -51,13 +51,14 @@ 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); + } + +- return true; ++ return this.lootableData() == null; // Paper - only track the loot table if there is chance for replenish + } else { + return false; + } +@@ -69,12 +70,13 @@ 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); + } + +- return true; ++ return this.lootableData() == null; // Paper - only track the loot table if there is chance for replenish + } + } + +@@ -82,13 +84,17 @@ public interface RandomizableContainer extends Container { + Level level = this.getLevel(); + BlockPos blockPos = this.getBlockPos(); + ResourceKey<LootTable> 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); +@@ -97,4 +103,16 @@ public interface RandomizableContainer extends Container { + lootTable.fill(this, builder.create(LootContextParamSets.CHEST), this.getLootTableSeed()); + } + } + -+ @Override -+ public LootableInventory getAPILootableInventory() { -+ Level world = tileEntityLootable.getLevel(); -+ if (world == null) { -+ return null; -+ } -+ return (LootableInventory) getBukkitWorld().getBlockAt(MCUtil.toLocation(world, tileEntityLootable.getBlockPos())).getState(); ++ // 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 + } + -+ @Override -+ public Level getNMSWorld() { -+ return tileEntityLootable.getLevel(); ++ 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); + } -+} -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 2fcdd61e9669904756aa33b1ff8ab7160ea5e371..e04c0d333f8a425e4a1315b20f182b2206c22277 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -242,6 +242,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess - } - // Paper end - Share random for entities to make them more random - -+ public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper - private CraftEntity bukkitEntity; - - public CraftEntity getBukkitEntity() { ++ // 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 67840327e934b631a85cf2d64911f5cfab4402b1..2704389bc3ec6dbbf1b568a4380972f8c0d62d15 100644 +index 67840327e934b631a85cf2d64911f5cfab4402b1..9549eee0d92f322bd5232abd7e695213660c2e22 100644 --- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java +++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecartContainer.java -@@ -35,6 +35,20 @@ public abstract class AbstractMinecartContainer extends AbstractMinecart impleme +@@ -35,6 +35,14 @@ public abstract class AbstractMinecartContainer extends AbstractMinecart impleme public ResourceKey<LootTable> 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<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>(); private int maxStack = MAX_STACK; -@@ -144,12 +158,14 @@ 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 025e57993b85402c48db29d65ea3ceaf277ff27a..b04f7ce0805453f6c737fa9dc11c4129ca64e934 100644 +index 025e57993b85402c48db29d65ea3ceaf277ff27a..e0aec3b3e04d603dc208029554f981c6b9e6b43e 100644 --- a/src/main/java/net/minecraft/world/entity/vehicle/ChestBoat.java +++ b/src/main/java/net/minecraft/world/entity/vehicle/ChestBoat.java -@@ -70,12 +70,14 @@ public class ChestBoat extends Boat implements HasCustomInventoryScreen, Contain - @Override - protected void addAdditionalSaveData(CompoundTag nbt) { - super.addAdditionalSaveData(nbt); -+ this.lootableData.saveNbt(nbt); // Paper - this.addChestVehicleSaveData(nbt, this.registryAccess()); - } - +@@ -209,7 +209,7 @@ public class ChestBoat extends Boat implements HasCustomInventoryScreen, Contain + @Nullable @Override - protected void readAdditionalSaveData(CompoundTag nbt) { - super.readAdditionalSaveData(nbt); -+ this.lootableData.loadNbt(nbt); // Paper - this.readChestVehicleSaveData(nbt, this.registryAccess()); - } - -@@ -257,6 +259,20 @@ public class ChestBoat extends Boat implements HasCustomInventoryScreen, Contain + 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); +@@ -257,6 +257,14 @@ 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<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>(); private int maxStack = MAX_STACK; diff --git a/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java b/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java -index dbde2402fd46b0d06e8efeb90be6fb98d7ae7798..f33e5cf6d456e615050047e924d9b24268a2c51e 100644 +index dbde2402fd46b0d06e8efeb90be6fb98d7ae7798..d976a6e3a79a01392a5033b05864d82782a30916 100644 --- a/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java +++ b/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java -@@ -65,9 +65,8 @@ public interface ContainerEntity extends Container, MenuProvider { +@@ -62,22 +62,26 @@ 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 dbde2402fd46b0d06e8efeb90be6fb98d7ae7798..f33e5cf6d456e615050047e924d9b242 } default void readChestVehicleSaveData(CompoundTag nbt, HolderLookup.Provider registriesLookup) { -@@ -75,9 +74,8 @@ 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); @@ -626,135 +677,75 @@ index dbde2402fd46b0d06e8efeb90be6fb98d7ae7798..f33e5cf6d456e615050047e924d9b242 } default void chestVehicleDestroyed(DamageSource source, Level world, Entity vehicle) { -@@ -99,13 +97,13 @@ public interface ContainerEntity extends Container, MenuProvider { +@@ -99,13 +103,17 @@ public interface ContainerEntity extends Container, MenuProvider { 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); -@@ -175,4 +173,13 @@ public interface ContainerEntity extends Container, MenuProvider { +@@ -175,4 +183,14 @@ public interface ContainerEntity extends Container, MenuProvider { 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 c2493c15d8fe4587d6ee2db100cc13303b66b39b..5b183f12717008dd6c9863938c17b2e668ebded4 100644 +index c2493c15d8fe4587d6ee2db100cc13303b66b39b..13c9a68b604d4c7c6e09e72b3cea7ab2214b06ab 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 -@@ -19,6 +19,7 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc - @Nullable - public ResourceKey<LootTable> 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); -@@ -45,6 +46,56 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc - this.lootTableSeed = lootTableSeed; +@@ -115,4 +115,13 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc + 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; -+ } -+ } + -+ @Override -+ public boolean trySaveLootTable(final net.minecraft.nbt.CompoundTag nbt) { -+ this.lootableData.saveNbt(nbt); -+ RandomizableContainer.super.trySaveLootTable(nbt); -+ return false; -+ } ++ // Paper start - LootTable API ++ final com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData(); // Paper + + @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<LootTable> 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()); -+ } -+ ++ public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData() { ++ return this.lootableData; + } -+ // 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 949e074a32b6593bd8b7405499e686a074e283e5..398ffe274bddee2b01350b9490def3d2fe854917 100644 +index 949e074a32b6593bd8b7405499e686a074e283e5..1f084b73f2ec67dd2022feafc5ab5dac02c338f6 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftBrushableBlock.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBrushableBlock.java -@@ -58,7 +58,7 @@ public class CraftBrushableBlock extends CraftBlockEntityState<BrushableBlockEnt +@@ -58,7 +58,8 @@ public class CraftBrushableBlock extends CraftBlockEntityState<BrushableBlockEnt this.setLootTable(this.getLootTable(), seed); } - 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 2b6a93a944b27290745278957a3577772b7b8212..29df470d6706a33dad4317a9aa599456d5c7f6ee 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java -@@ -13,8 +13,9 @@ 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<ChestBlockEntity> implements Chest { -+public class CraftChest extends CraftLootable<ChestBlockEntity> 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 74315a46f6101775321b1cf4944c124c69aed182..c3215f15b3088199dcf96f62b58d0ec7c2b4125c 100644 +index 74315a46f6101775321b1cf4944c124c69aed182..f23fbb8ed39a754b36d2eb162358877ef6dacb17 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/CraftLootable.java +++ b/src/main/java/org/bukkit/craftbukkit/block/CraftLootable.java @@ -8,7 +8,7 @@ import org.bukkit.craftbukkit.CraftLootTable; @@ -766,8 +757,44 @@ index 74315a46f6101775321b1cf4944c124c69aed182..c3215f15b3088199dcf96f62b58d0ec7 public CraftLootable(World world, T tileEntity) { super(world, tileEntity); +@@ -27,29 +27,17 @@ public abstract class CraftLootable<T extends RandomizableContainerBlockEntity> + } + } + ++ // 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<T> copy(); diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java -index cfde210ea9d4b62fe514d3ab0dbab2f43eda0c7a..c0f6939ed782dd3151ebd7ee9d3d7e292154e76c 100644 +index cfde210ea9d4b62fe514d3ab0dbab2f43eda0c7a..e4f899a6a1d055b3ea17d1114ed0228fbba53352 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java @@ -7,8 +7,7 @@ import org.bukkit.craftbukkit.inventory.CraftInventory; @@ -780,15 +807,36 @@ index cfde210ea9d4b62fe514d3ab0dbab2f43eda0c7a..c0f6939ed782dd3151ebd7ee9d3d7e29 private final Inventory inventory; public CraftChestBoat(CraftServer server, ChestBoat entity) { -@@ -51,7 +50,7 @@ public class CraftChestBoat extends CraftBoat implements org.bukkit.entity.Chest - return this.getHandle().getLootTableSeed(); +@@ -31,28 +30,5 @@ public class CraftChestBoat extends CraftBoat implements org.bukkit.entity.Chest + 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 fd42f0b20132d08039ca7735d31a61806a6b07dc..b1a708de6790bbe336202b13ab862ced78de084f 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java @@ -802,6 +850,48 @@ index fd42f0b20132d08039ca7735d31a61806a6b07dc..b1a708de6790bbe336202b13ab862ced 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 4388cd0303b45faf21631e7644baebb63baaba10..451f3a6f0b47493da3af3f5d6baced6a8c97f350 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartContainer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartContainer.java +@@ -7,7 +7,7 @@ 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); +@@ -18,27 +18,5 @@ 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 39427b4f284e9402663be2b160ccb5f03f8b91da..17f5684cba9d3ed22d9925d1951520cc4751dfe2 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java |