PaperMC/patches/server/0096-LootTable-API-and-replenishable-lootables.patch

908 lines
40 KiB
Diff
Raw Normal View History

2021-06-11 14:02:28 +02:00
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Sun, 1 May 2016 21:19:14 -0400
Subject: [PATCH] LootTable API and replenishable lootables
2021-06-11 14:02:28 +02:00
Provides an API to control the loot table for an object.
Also provides a feature that any Lootable Inventory (Chests in Structures)
can automatically replenish after a given time.
This feature is good for long term worlds so that newer players
do not suffer with "Every chest has been looted"
== AT ==
public org.bukkit.craftbukkit.block.CraftBlockEntityState getTileEntity()Lnet/minecraft/world/level/block/entity/BlockEntity;
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/PaperLootable.java b/src/main/java/com/destroystokyo/paper/loottable/PaperLootable.java
2022-06-07 23:48:09 +02:00
new file mode 100644
index 0000000000000000000000000000000000000000..a53d51be1da25b87f2bc0a29a196d8f9996dbd2b
2022-06-07 23:48:09 +02:00
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootable.java
@@ -0,0 +1,21 @@
2022-06-07 23:48:09 +02:00
+package com.destroystokyo.paper.loottable;
+
+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;
2022-06-07 23:48:09 +02:00
+
+@DefaultQualifier(NonNull.class)
+public interface PaperLootable extends Lootable {
2022-06-07 23:48:09 +02:00
+
+ @Override
+ default void setLootTable(final @Nullable LootTable table) {
+ this.setLootTable(table, this.getSeed());
2022-06-07 23:48:09 +02:00
+ }
+
+ @Override
+ default void setSeed(final long seed) {
+ this.setLootTable(this.getLootTable(), seed);
2022-06-07 23:48:09 +02:00
+ }
+}
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;
2022-06-07 23:48:09 +02:00
+
+import net.minecraft.world.RandomizableContainer;
+import org.bukkit.craftbukkit.CraftLootTable;
+import org.bukkit.loot.LootTable;
+import org.checkerframework.checker.nullness.qual.Nullable;
2022-06-07 23:48:09 +02:00
+
+public interface PaperLootableBlock extends PaperLootable {
2022-06-07 23:48:09 +02:00
+
+ RandomizableContainer getRandomizableContainer();
2022-06-07 23:48:09 +02:00
+
+ /* Lootable */
2022-06-07 23:48:09 +02:00
+ @Override
+ default @Nullable LootTable getLootTable() {
+ return CraftLootTable.minecraftToBukkit(this.getRandomizableContainer().getLootTable());
2022-06-07 23:48:09 +02:00
+ }
+
+ @Override
+ default void setLootTable(final @Nullable LootTable table, final long seed) {
+ this.getRandomizableContainer().setLootTable(CraftLootTable.bukkitToMinecraft(table), seed);
2022-06-07 23:48:09 +02:00
+ }
+
+ @Override
+ default long getSeed() {
+ return this.getRandomizableContainer().getLootTableSeed();
2022-06-07 23:48:09 +02:00
+ }
+}
2021-06-11 14:02:28 +02:00
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..0699c60920333ea1fec04e3c94d952244d2abeae
2021-06-11 14:02:28 +02:00
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableBlockInventory.java
@@ -0,0 +1,26 @@
2021-06-11 14:02:28 +02:00
+package com.destroystokyo.paper.loottable;
+
+import java.util.Objects;
2021-06-11 14:02:28 +02:00
+import net.minecraft.core.BlockPos;
+import org.bukkit.block.Block;
+import org.bukkit.craftbukkit.block.CraftBlock;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
2021-06-11 14:02:28 +02:00
+
+@DefaultQualifier(NonNull.class)
+public interface PaperLootableBlockInventory extends LootableBlockInventory, PaperLootableInventory, PaperLootableBlock {
2021-06-11 14:02:28 +02:00
+
+ /* PaperLootableInventory */
+ @Override
+ default PaperLootableInventoryData lootableDataForAPI() {
+ return Objects.requireNonNull(this.getRandomizableContainer().lootableData(), "Can only manage loot tables on tile entities with lootableData");
+ }
2021-06-11 14:02:28 +02:00
+
+ /* LootableBlockInventory */
2021-06-11 14:02:28 +02:00
+ @Override
+ default Block getBlock() {
+ final BlockPos position = this.getRandomizableContainer().getBlockPos();
+ return CraftBlock.at(this.getNMSWorld(), position);
2021-06-11 14:02:28 +02:00
+ }
+
+}
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 */
2021-06-11 14:02:28 +02:00
+ @Override
+ default @Nullable LootTable getLootTable() {
+ return CraftLootTable.minecraftToBukkit(this.getHandle().getLootTable());
2021-06-11 14:02:28 +02:00
+ }
+
+ @Override
+ default void setLootTable(final @Nullable LootTable table, final long seed) {
+ this.getHandle().setLootTable(CraftLootTable.bukkitToMinecraft(table));
+ this.getHandle().setLootTableSeed(seed);
2021-06-11 14:02:28 +02:00
+ }
+
+ @Override
+ default long getSeed() {
+ return this.getHandle().getLootTableSeed();
2021-06-11 14:02:28 +02:00
+ }
+}
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..5c57acc95f638a8bcb351ae44e9434a056835470
2021-06-11 14:02:28 +02:00
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableEntityInventory.java
@@ -0,0 +1,26 @@
2021-06-11 14:02:28 +02:00
+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;
2021-06-11 14:02:28 +02:00
+
+@DefaultQualifier(NonNull.class)
+public interface PaperLootableEntityInventory extends LootableEntityInventory, PaperLootableInventory, PaperLootableEntity {
2021-06-11 14:02:28 +02:00
+
+ /* PaperLootableInventory */
2021-06-11 14:02:28 +02:00
+ @Override
+ default Level getNMSWorld() {
+ return this.getHandle().level();
2021-06-11 14:02:28 +02:00
+ }
+
+ @Override
+ default PaperLootableInventoryData lootableDataForAPI() {
+ return this.getHandle().lootableData();
2021-06-11 14:02:28 +02:00
+ }
+
+ /* LootableEntityInventory */
+ default Entity getEntity() {
+ return ((net.minecraft.world.entity.Entity) this.getHandle()).getBukkitEntity();
2021-06-11 14:02:28 +02:00
+ }
+}
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..9e7c22ef49f1699df298f7121d50d27b4cb0923f
2021-06-11 14:02:28 +02:00
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventory.java
@@ -0,0 +1,79 @@
2021-06-11 14:02:28 +02:00
+package com.destroystokyo.paper.loottable;
+
+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;
2021-06-11 14:02:28 +02:00
+
+@DefaultQualifier(NonNull.class)
+public interface PaperLootableInventory extends PaperLootable, LootableInventory {
2021-06-11 14:02:28 +02:00
+
+ /* impl */
+ PaperLootableInventoryData lootableDataForAPI();
2021-06-11 14:02:28 +02:00
+
+ Level getNMSWorld();
+
+ default World getBukkitWorld() {
+ return this.getNMSWorld().getWorld();
2021-06-11 14:02:28 +02:00
+ }
+
+ /* LootableInventory */
2021-06-11 14:02:28 +02:00
+ @Override
+ default boolean isRefillEnabled() {
+ return this.getNMSWorld().paperConfig().lootables.autoReplenish;
2021-06-11 14:02:28 +02:00
+ }
+
+ @Override
+ default boolean hasBeenFilled() {
+ return this.getLastFilled() != -1;
2021-06-11 14:02:28 +02:00
+ }
+
+ @Override
+ default boolean hasPlayerLooted(final UUID player) {
+ return this.lootableDataForAPI().hasPlayerLooted(player);
2021-06-11 14:02:28 +02:00
+ }
+
+ @Override
+ default boolean canPlayerLoot(final UUID player) {
+ return this.lootableDataForAPI().canPlayerLoot(player, this.getNMSWorld().paperConfig());
+ }
+
+ @Override
+ default Long getLastLooted(final UUID player) {
+ return this.lootableDataForAPI().getLastLooted(player);
2021-06-11 14:02:28 +02:00
+ }
+
+ @Override
+ default boolean setHasPlayerLooted(final UUID player, final boolean looted) {
+ final boolean hasLooted = this.hasPlayerLooted(player);
2021-06-11 14:02:28 +02:00
+ if (hasLooted != looted) {
+ this.lootableDataForAPI().setPlayerLootedState(player, looted);
2021-06-11 14:02:28 +02:00
+ }
+ return hasLooted;
+ }
+
+ @Override
+ default boolean hasPendingRefill() {
+ final long nextRefill = this.lootableDataForAPI().getNextRefill();
+ return nextRefill != -1 && nextRefill > this.lootableDataForAPI().getLastFill();
2021-06-11 14:02:28 +02:00
+ }
+
+ @Override
+ default long getLastFilled() {
+ return this.lootableDataForAPI().getLastFill();
2021-06-11 14:02:28 +02:00
+ }
+
+ @Override
+ default long getNextRefill() {
+ return this.lootableDataForAPI().getNextRefill();
2021-06-11 14:02:28 +02:00
+ }
+
+ @Override
+ default long setNextRefill(long refillAt) {
+ if (refillAt < -1) {
+ refillAt = -1;
+ }
+ return this.lootableDataForAPI().setNextRefill(refillAt);
2021-06-11 14:02:28 +02:00
+ }
+}
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..fea92d280c6817cee1f18379d5ed51a3a22ee344
2021-06-11 14:02:28 +02:00
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/loottable/PaperLootableInventoryData.java
@@ -0,0 +1,249 @@
2021-06-11 14:02:28 +02:00
+package com.destroystokyo.paper.loottable;
+
+import io.papermc.paper.configuration.WorldConfiguration;
+import io.papermc.paper.configuration.type.DurationOrDisabled;
2021-06-11 14:02:28 +02:00
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
2021-06-11 14:02:28 +02:00
+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;
2021-06-11 14:02:28 +02:00
+
+@DefaultQualifier(NonNull.class)
2021-06-11 14:02:28 +02:00
+public class PaperLootableInventoryData {
+
+ private static final Random RANDOM = new Random();
+
+ private long lastFill = -1;
+ private long nextRefill = -1;
+ private int numRefills = 0;
+ private @Nullable Map<UUID, Long> lootedPlayers;
2021-06-11 14:02:28 +02:00
+
+ long getLastFill() {
+ return this.lastFill;
+ }
+
+ long getNextRefill() {
+ return this.nextRefill;
+ }
+
+ long setNextRefill(final long nextRefill) {
+ final long prev = this.nextRefill;
2021-06-11 14:02:28 +02:00
+ this.nextRefill = nextRefill;
+ return prev;
+ }
+
+ public <T> boolean shouldReplenish(final T lootTableHolder, final LootTableInterface<T> holderInterface, final net.minecraft.world.entity.player.@Nullable Player player) {
2021-06-11 14:02:28 +02:00
+
+ // No Loot Table associated
+ if (!holderInterface.hasLootTable(lootTableHolder)) {
2021-06-11 14:02:28 +02:00
+ return false;
+ }
+
+ // ALWAYS process the first fill or if the feature is disabled
+ if (this.lastFill == -1 || !holderInterface.paperConfig(lootTableHolder).lootables.autoReplenish) {
2021-06-11 14:02:28 +02:00
+ return true;
+ }
+
+ // Only process refills when a player is set
+ if (player == null) {
+ return false;
+ }
+
+ // Chest is not scheduled for refill
+ if (this.nextRefill == -1) {
+ return false;
+ }
+
+ final WorldConfiguration paperConfig = holderInterface.paperConfig(lootTableHolder);
2021-06-11 14:02:28 +02:00
+
+ // Check if max refills has been hit
+ if (paperConfig.lootables.maxRefills != -1 && this.numRefills >= paperConfig.lootables.maxRefills) {
2021-06-11 14:02:28 +02:00
+ return false;
+ }
+
+ // Refill has not been reached
+ if (this.nextRefill > System.currentTimeMillis()) {
+ return false;
+ }
+
+
+ final Player bukkitPlayer = (Player) player.getBukkitEntity();
+ final LootableInventoryReplenishEvent event = new LootableInventoryReplenishEvent(bukkitPlayer, holderInterface.getInventoryForEvent(lootTableHolder));
+ event.setCancelled(!this.canPlayerLoot(player.getUUID(), paperConfig));
2021-06-11 14:02:28 +02:00
+ return event.callEvent();
+ }
+
+ 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) {
2021-06-11 14:02:28 +02:00
+ this.lastFill = System.currentTimeMillis();
+ final WorldConfiguration paperConfig = holderInterface.paperConfig(lootTableHolder);
+ if (paperConfig.lootables.autoReplenish) {
+ 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;
2021-06-11 14:02:28 +02:00
+ this.numRefills++;
+ if (paperConfig.lootables.resetSeedOnFill) {
+ holderInterface.setSeed(lootTableHolder, 0);
2021-06-11 14:02:28 +02:00
+ }
+ 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);
+ }
+ return false;
2021-06-11 14:02:28 +02:00
+ }
+ return true;
2021-06-11 14:02:28 +02:00
+ }
+
+ 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";
2021-06-11 14:02:28 +02:00
+
+ public void loadNbt(final CompoundTag base) {
+ if (!base.contains(ROOT, Tag.TAG_COMPOUND)) {
2021-06-11 14:02:28 +02:00
+ return;
+ }
+ final CompoundTag comp = base.getCompound(ROOT);
+ if (comp.contains(LAST_FILL)) {
+ this.lastFill = comp.getLong(LAST_FILL);
2021-06-11 14:02:28 +02:00
+ }
+ if (comp.contains(NEXT_REFILL)) {
+ this.nextRefill = comp.getLong(NEXT_REFILL);
2021-06-11 14:02:28 +02:00
+ }
+
+ if (comp.contains(NUM_REFILLS)) {
+ this.numRefills = comp.getInt(NUM_REFILLS);
2021-06-11 14:02:28 +02:00
+ }
+ if (comp.contains(LOOTED_PLAYERS, Tag.TAG_LIST)) {
+ final ListTag list = comp.getList(LOOTED_PLAYERS, Tag.TAG_COMPOUND);
2021-06-11 14:02:28 +02:00
+ 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);
+ this.lootedPlayers.put(cmp.getUUID("UUID"), cmp.getLong("Time"));
2021-06-11 14:02:28 +02:00
+ }
+ }
+ }
+
+ public void saveNbt(final CompoundTag base) {
+ final CompoundTag comp = new CompoundTag();
2021-06-11 14:02:28 +02:00
+ if (this.nextRefill != -1) {
+ comp.putLong(NEXT_REFILL, this.nextRefill);
2021-06-11 14:02:28 +02:00
+ }
+ if (this.lastFill != -1) {
+ comp.putLong(LAST_FILL, this.lastFill);
2021-06-11 14:02:28 +02:00
+ }
+ if (this.numRefills != 0) {
+ comp.putInt(NUM_REFILLS, this.numRefills);
2021-06-11 14:02:28 +02:00
+ }
+ if (this.lootedPlayers != null && !this.lootedPlayers.isEmpty()) {
+ final ListTag list = new ListTag();
+ for (final Map.Entry<UUID, Long> entry : this.lootedPlayers.entrySet()) {
+ final CompoundTag cmp = new CompoundTag();
2021-06-17 23:39:36 +02:00
+ cmp.putUUID("UUID", entry.getKey());
2021-06-11 14:02:28 +02:00
+ cmp.putLong("Time", entry.getValue());
+ list.add(cmp);
+ }
+ comp.put(LOOTED_PLAYERS, list);
2021-06-11 14:02:28 +02:00
+ }
+
+ if (!comp.isEmpty()) {
+ base.put(ROOT, comp);
2021-06-11 14:02:28 +02:00
+ }
+ }
+
+ void setPlayerLootedState(final UUID player, final boolean looted) {
2021-06-11 14:02:28 +02:00
+ if (looted && this.lootedPlayers == null) {
+ this.lootedPlayers = new HashMap<>();
+ }
+ if (looted) {
+ this.lootedPlayers.put(player, System.currentTimeMillis());
2021-06-11 14:02:28 +02:00
+ } else if (this.lootedPlayers != null) {
+ this.lootedPlayers.remove(player);
+ }
+ }
+
+ boolean canPlayerLoot(final UUID player, final WorldConfiguration worldConfiguration) {
+ final @Nullable Long lastLooted = this.getLastLooted(player);
+ if (!worldConfiguration.lootables.restrictPlayerReloot || lastLooted == null) return true;
+
+ final DurationOrDisabled restrictPlayerRelootTime = worldConfiguration.lootables.restrictPlayerRelootTime;
+ if (restrictPlayerRelootTime.value().isEmpty()) return false;
+
+ return TimeUnit.SECONDS.toMillis(restrictPlayerRelootTime.value().get().seconds()) + lastLooted < System.currentTimeMillis();
+ }
+
+ boolean hasPlayerLooted(final UUID player) {
2021-06-11 14:02:28 +02:00
+ return this.lootedPlayers != null && this.lootedPlayers.containsKey(player);
+ }
+
+ @Nullable Long getLastLooted(final UUID player) {
+ return this.lootedPlayers != null ? this.lootedPlayers.get(player) : null;
2021-06-11 14:02:28 +02:00
+ }
+}
diff --git a/src/main/java/net/minecraft/world/RandomizableContainer.java b/src/main/java/net/minecraft/world/RandomizableContainer.java
2024-06-13 19:30:39 +02:00
index 9715f1b63aeea39bde9258275f51e3e8508ca6e4..597cd5dbb4bb415a9b4d874c1c5dd621be1d6fc8 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)) {
2024-06-13 19:30:39 +02:00
this.setLootTable(ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.parse(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());
}
}
2021-06-11 14:02:28 +02:00
+
+ // 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
2021-06-11 14:02:28 +02:00
+ }
+
+ 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);
2021-06-11 14:02:28 +02:00
+ }
+ // Paper end - LootTable API
}
2021-06-11 14:02:28 +02:00
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..9549eee0d92f322bd5232abd7e695213660c2e22 100644
2021-06-11 14:02:28 +02:00
--- 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,14 @@ public abstract class AbstractMinecartContainer extends AbstractMinecart impleme
2024-04-24 03:25:14 +02:00
public ResourceKey<LootTable> lootTable;
2021-06-11 14:02:28 +02:00
public long lootTableSeed;
+ // Paper start - LootTable API
+ final com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData();
2022-06-07 23:48:09 +02:00
+
+ @Override
+ public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData() {
2022-06-07 23:48:09 +02:00
+ return this.lootableData;
+ }
+ // Paper end - LootTable API
2021-06-11 14:02:28 +02:00
// CraftBukkit start
public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
private int maxStack = MAX_STACK;
2022-06-07 21:55:39 +02:00
diff --git a/src/main/java/net/minecraft/world/entity/vehicle/ChestBoat.java b/src/main/java/net/minecraft/world/entity/vehicle/ChestBoat.java
2024-06-13 19:30:39 +02:00
index 42f8e2d961f83c3e9ce384158b9dfc4014eb0a5f..4cdf3b54187ebcb1f5ddfa6114386127a2846f01 100644
2022-06-07 21:55:39 +02:00
--- a/src/main/java/net/minecraft/world/entity/vehicle/ChestBoat.java
+++ b/src/main/java/net/minecraft/world/entity/vehicle/ChestBoat.java
2024-06-13 19:30:39 +02:00
@@ -217,7 +217,7 @@ public class ChestBoat extends Boat implements HasCustomInventoryScreen, Contain
@Nullable
2022-06-07 21:55:39 +02:00
@Override
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);
2024-06-13 19:30:39 +02:00
@@ -265,6 +265,14 @@ public class ChestBoat extends Boat implements HasCustomInventoryScreen, Contain
2024-04-24 03:25:14 +02:00
this.level().gameEvent((Holder) GameEvent.CONTAINER_CLOSE, this.position(), GameEvent.Context.of((Entity) player));
2022-06-07 23:48:09 +02:00
}
+ // Paper start - LootTable API
+ final com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData();
2022-06-07 23:48:09 +02:00
+
+ @Override
+ public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData() {
2022-06-07 23:48:09 +02:00
+ return this.lootableData;
+ }
+ // Paper end - LootTable API
2022-06-07 23:48:09 +02:00
// CraftBukkit start
public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
private int maxStack = MAX_STACK;
2022-06-07 21:55:39 +02:00
diff --git a/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java b/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java
2024-06-13 19:30:39 +02:00
index 3ee99193de5deb6a38d6ded561fe8f2fbf711327..ccc7367ab2740bea0f2b907223a0920b11665092 100644
2022-06-07 21:55:39 +02:00
--- a/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java
+++ b/src/main/java/net/minecraft/world/entity/vehicle/ContainerEntity.java
@@ -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
2022-06-07 21:55:39 +02:00
if (this.getLootTableSeed() != 0L) {
nbt.putLong("LootTableSeed", this.getLootTableSeed());
2021-06-11 14:02:28 +02:00
}
2022-06-07 21:55:39 +02:00
- } else {
2024-04-24 03:25:14 +02:00
- ContainerHelper.saveAllItems(nbt, this.getItemStacks(), registriesLookup);
2022-06-07 21:55:39 +02:00
}
2024-04-24 03:25:14 +02:00
+ ContainerHelper.saveAllItems(nbt, this.getItemStacks(), registriesLookup); // Paper - always save the items, table may still remain
}
2024-04-24 03:25:14 +02:00
default void readChestVehicleSaveData(CompoundTag nbt, HolderLookup.Provider registriesLookup) {
this.clearItemStacks();
2022-06-07 21:55:39 +02:00
if (nbt.contains("LootTable", 8)) {
2024-06-13 19:30:39 +02:00
this.setLootTable(ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.parse(nbt.getString("LootTable"))));
+ // Paper start - LootTable API
+ if (this.getLootTable() != null) {
+ this.lootableData().loadNbt(nbt);
+ }
+ // Paper end - LootTable API
2022-06-07 21:55:39 +02:00
this.setLootTableSeed(nbt.getLong("LootTableSeed"));
- } else {
2024-04-24 03:25:14 +02:00
- ContainerHelper.loadAllItems(nbt, this.getItemStacks(), registriesLookup);
2022-06-07 21:55:39 +02:00
}
2024-04-24 03:25:14 +02:00
+ ContainerHelper.loadAllItems(nbt, this.getItemStacks(), registriesLookup); // Paper - always save the items, table may still remain
}
default void chestVehicleDestroyed(DamageSource source, Level world, Entity vehicle) {
@@ -99,13 +103,17 @@ public interface ContainerEntity extends Container, MenuProvider {
2022-11-25 00:21:40 +01:00
default void unpackChestVehicleLootTable(@Nullable Player player) {
2023-06-07 21:19:26 +02:00
MinecraftServer minecraftServer = this.level().getServer();
2022-11-25 00:21:40 +01:00
- if (this.getLootTable() != null && minecraftServer != null) {
+ if (minecraftServer != null && this.lootableData().shouldReplenish(this, com.destroystokyo.paper.loottable.PaperLootableInventoryData.ENTITY, player)) { // Paper - LootTable API
2024-04-24 03:25:14 +02:00
LootTable lootTable = minecraftServer.reloadableRegistries().getLootTable(this.getLootTable());
2022-11-25 00:21:40 +01:00
if (player != null) {
CriteriaTriggers.GENERATE_LOOT.trigger((ServerPlayer)player, this.getLootTable());
}
- this.setLootTable(null);
+ // 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());
2022-11-25 00:21:40 +01:00
if (player != null) {
builder.withLuck(player.getLuck()).withParameter(LootContextParams.THIS_ENTITY, player);
@@ -175,4 +183,14 @@ public interface ContainerEntity extends Container, MenuProvider {
2022-06-07 23:48:09 +02:00
default boolean isChestVehicleStillValid(Player player) {
2024-04-24 03:25:14 +02:00
return !this.isRemoved() && player.canInteractWithEntity(this.getBoundingBox(), 4.0);
2022-06-07 23:48:09 +02:00
}
+
+ // Paper start - LootTable API
+ default com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData() {
+ throw new UnsupportedOperationException("Implement this method");
2022-06-07 23:48:09 +02:00
+ }
+
+ default com.destroystokyo.paper.loottable.PaperLootableInventory getLootableInventory() {
+ return ((com.destroystokyo.paper.loottable.PaperLootableInventory) ((net.minecraft.world.entity.Entity) this).getBukkitEntity());
2022-06-07 23:48:09 +02:00
+ }
+ // Paper end - LootTable API
2022-06-07 23:48:09 +02:00
}
2021-06-11 14:02:28 +02:00
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..13c9a68b604d4c7c6e09e72b3cea7ab2214b06ab 100644
2021-06-11 14:02:28 +02:00
--- a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
+++ b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
@@ -115,4 +115,13 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc
nbt.remove("LootTable");
nbt.remove("LootTableSeed");
}
+
+ // Paper start - LootTable API
+ final com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData(); // Paper
+
+ @Override
+ public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData() {
+ return this.lootableData;
+ }
+ // Paper end - LootTable API
}
2023-06-07 21:19:26 +02:00
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBrushableBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBrushableBlock.java
index 949e074a32b6593bd8b7405499e686a074e283e5..1f084b73f2ec67dd2022feafc5ab5dac02c338f6 100644
2023-06-07 21:19:26 +02:00
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBrushableBlock.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBrushableBlock.java
@@ -58,7 +58,8 @@ public class CraftBrushableBlock extends CraftBlockEntityState<BrushableBlockEnt
2023-06-07 21:19:26 +02:00
this.setLootTable(this.getLootTable(), seed);
}
- private void setLootTable(LootTable table, long seed) {
+ @Override // Paper - this is now an override
2023-06-07 21:19:26 +02:00
+ public void setLootTable(LootTable table, long seed) { // Paper - make public since it overrides a public method
2024-04-24 03:25:14 +02:00
this.getSnapshot().setLootTable(CraftLootTable.bukkitToMinecraft(table), seed);
2023-06-07 21:19:26 +02:00
}
2024-04-24 03:25:14 +02:00
2021-06-11 14:02:28 +02:00
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftLootable.java b/src/main/java/org/bukkit/craftbukkit/block/CraftLootable.java
index 74315a46f6101775321b1cf4944c124c69aed182..f23fbb8ed39a754b36d2eb162358877ef6dacb17 100644
2021-06-11 14:02:28 +02:00
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftLootable.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftLootable.java
2024-04-24 03:25:14 +02:00
@@ -8,7 +8,7 @@ import org.bukkit.craftbukkit.CraftLootTable;
2021-06-11 14:02:28 +02:00
import org.bukkit.loot.LootTable;
import org.bukkit.loot.Lootable;
-public abstract class CraftLootable<T extends RandomizableContainerBlockEntity> extends CraftContainer<T> implements Nameable, Lootable {
+public abstract class CraftLootable<T extends RandomizableContainerBlockEntity> extends CraftContainer<T> implements Nameable, Lootable, com.destroystokyo.paper.loottable.PaperLootableBlockInventory { // Paper
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();
2022-06-07 23:48:09 +02:00
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java
index cfde210ea9d4b62fe514d3ab0dbab2f43eda0c7a..e4f899a6a1d055b3ea17d1114ed0228fbba53352 100644
2022-06-07 23:48:09 +02:00
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftChestBoat.java
2024-04-24 03:25:14 +02:00
@@ -7,8 +7,7 @@ import org.bukkit.craftbukkit.inventory.CraftInventory;
2022-06-07 23:48:09 +02:00
import org.bukkit.inventory.Inventory;
import org.bukkit.loot.LootTable;
-public class CraftChestBoat extends CraftBoat implements org.bukkit.entity.ChestBoat {
-
+public class CraftChestBoat extends CraftBoat implements org.bukkit.entity.ChestBoat, com.destroystokyo.paper.loottable.PaperLootableEntityInventory { // Paper
private final Inventory inventory;
public CraftChestBoat(CraftServer server, ChestBoat entity) {
@@ -31,28 +30,5 @@ public class CraftChestBoat extends CraftBoat implements org.bukkit.entity.Chest
return this.inventory;
2022-06-08 05:12:24 +02:00
}
- @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();
- }
-
2022-06-08 05:12:24 +02:00
- private void setLootTable(LootTable table, long seed) {
- this.getHandle().setLootTable(CraftLootTable.bukkitToMinecraft(table));
- this.getHandle().setLootTableSeed(seed);
- }
+ // Paper - moved loot table logic to PaperLootableEntityInventory
}
2021-06-11 14:02:28 +02:00
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
2021-06-11 14:02:28 +02:00
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java
@@ -7,7 +7,7 @@ import org.bukkit.entity.minecart.StorageMinecart;
2021-06-11 14:02:28 +02:00
import org.bukkit.inventory.Inventory;
@SuppressWarnings("deprecation")
-public class CraftMinecartChest extends CraftMinecartContainer implements StorageMinecart {
+public class CraftMinecartChest extends CraftMinecartContainer implements StorageMinecart, com.destroystokyo.paper.loottable.PaperLootableEntityInventory { // Paper
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
}
2021-06-11 14:02:28 +02:00
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java
2023-10-27 01:34:58 +02:00
index 39427b4f284e9402663be2b160ccb5f03f8b91da..17f5684cba9d3ed22d9925d1951520cc4751dfe2 100644
2021-06-11 14:02:28 +02:00
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java
@@ -6,7 +6,7 @@ import org.bukkit.craftbukkit.inventory.CraftInventory;
2021-06-11 14:02:28 +02:00
import org.bukkit.entity.minecart.HopperMinecart;
import org.bukkit.inventory.Inventory;
-public final class CraftMinecartHopper extends CraftMinecartContainer implements HopperMinecart {
+public final class CraftMinecartHopper extends CraftMinecartContainer implements HopperMinecart, com.destroystokyo.paper.loottable.PaperLootableEntityInventory { // Paper
private final CraftInventory inventory;
public CraftMinecartHopper(CraftServer server, MinecartHopper entity) {