3
0
Mirror von https://github.com/PaperMC/Paper.git synchronisiert 2024-12-16 03:20:07 +01:00
Paper/Spigot-Server-Patches/0153-LootTable-API-Replenishable-Lootables-Feature.patch
Aikar 211cba970b LootTable API & Replenishable Lootables Feature
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"

API and Event added to control the Auto Replenish feature for players.
2016-05-04 19:46:28 -04:00

702 Zeilen
26 KiB
Diff

From 82ffa4af43c27227820035be39705b4a233087c2 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 & Replenishable Lootables Feature
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"
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index 9a9e2eb..3612701 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -339,4 +339,26 @@ public class PaperWorldConfig {
this.frostedIceDelayMax = this.getInt("frosted-ice.delay.max", this.frostedIceDelayMax);
this.log("Frosted Ice: " + (this.frostedIceEnabled ? "enabled" : "disabled") + " / delay: min=" + this.frostedIceDelayMin + ", max=" + this.frostedIceDelayMax);
}
+
+ public boolean autoReplenishLootables;
+ public boolean restrictPlayerReloot;
+ public boolean changeLootTableSeedOnFill;
+ public int maxLootableRefills;
+ public int lootableRegenMin;
+ public int lootableRegenMax;
+ private void enhancedLootables() {
+ autoReplenishLootables = getBoolean("lootables.auto-replenish", false);
+ restrictPlayerReloot = getBoolean("lootables.restrict-player-reloot", true);
+ changeLootTableSeedOnFill = getBoolean("lootables.reset-seed-on-fill", true);
+ maxLootableRefills = getInt("lootables.max-refills", -1);
+ lootableRegenMin = PaperConfig.getSeconds(getString("lootables.refresh-min", "12h"));
+ lootableRegenMax = PaperConfig.getSeconds(getString("lootables.refresh-max", "2d"));
+ if (autoReplenishLootables) {
+ log("Lootables: Replenishing every " +
+ PaperConfig.timeSummary(lootableRegenMin) + " to " +
+ PaperConfig.timeSummary(lootableRegenMax) +
+ (restrictPlayerReloot ? " (restricting reloot)" : "")
+ );
+ }
+ }
}
diff --git a/src/main/java/com/destroystokyo/paper/loottable/CraftLootable.java b/src/main/java/com/destroystokyo/paper/loottable/CraftLootable.java
new file mode 100644
index 0000000..36c36d1
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/loottable/CraftLootable.java
@@ -0,0 +1,12 @@
+package com.destroystokyo.paper.loottable;
+
+import net.minecraft.server.World;
+
+interface CraftLootable extends Lootable {
+
+ World getNMSWorld();
+
+ default org.bukkit.World getBukkitWorld() {
+ return getNMSWorld().getWorld();
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/loottable/CraftLootableBlockInventory.java b/src/main/java/com/destroystokyo/paper/loottable/CraftLootableBlockInventory.java
new file mode 100644
index 0000000..042fc6f
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/loottable/CraftLootableBlockInventory.java
@@ -0,0 +1,28 @@
+package com.destroystokyo.paper.loottable;
+
+import net.minecraft.server.BlockPosition;
+import net.minecraft.server.TileEntityLootable;
+import net.minecraft.server.World;
+import org.bukkit.Chunk;
+import org.bukkit.block.Block;
+
+public interface CraftLootableBlockInventory extends LootableBlockInventory, CraftLootableInventory {
+
+ TileEntityLootable getTileEntity();
+
+ @Override
+ default World getNMSWorld() {
+ return getTileEntity().getWorld();
+ }
+
+ default Block getBlock() {
+ final BlockPosition position = getTileEntity().getPosition();
+ final Chunk bukkitChunk = getTileEntity().getWorld().getChunkAtWorldCoords(position).bukkitChunk;
+ return bukkitChunk.getBlock(position.getX(), position.getY(), position.getZ());
+ }
+
+ @Override
+ default CraftLootableInventoryData getLootableData() {
+ return getTileEntity().getLootableData();
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/loottable/CraftLootableEntityInventory.java b/src/main/java/com/destroystokyo/paper/loottable/CraftLootableEntityInventory.java
new file mode 100644
index 0000000..6407c58
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/loottable/CraftLootableEntityInventory.java
@@ -0,0 +1,26 @@
+package com.destroystokyo.paper.loottable;
+
+import net.minecraft.server.World;
+import org.bukkit.entity.Entity;
+
+public interface CraftLootableEntityInventory extends LootableEntityInventory, CraftLootableInventory {
+
+ net.minecraft.server.Entity getHandle();
+
+ default Entity getEntity() {
+ return getHandle().getBukkitEntity();
+ }
+
+ @Override
+ default World getNMSWorld() {
+ return getHandle().getWorld();
+ }
+
+ @Override
+ default CraftLootableInventoryData getLootableData() {
+ if (getHandle() instanceof CraftLootableInventory) {
+ return ((CraftLootableInventory) getHandle()).getLootableData();
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/loottable/CraftLootableInventory.java b/src/main/java/com/destroystokyo/paper/loottable/CraftLootableInventory.java
new file mode 100644
index 0000000..87917d5
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/loottable/CraftLootableInventory.java
@@ -0,0 +1,87 @@
+package com.destroystokyo.paper.loottable;
+
+import org.apache.commons.lang.Validate;
+
+import java.util.UUID;
+
+public interface CraftLootableInventory extends CraftLootable, LootableInventory {
+
+ CraftLootableInventoryData getLootableData();
+
+ @Override
+ default boolean isRefillEnabled() {
+ return getNMSWorld().paperConfig.autoReplenishLootables;
+ }
+
+ @Override
+ default boolean hasBeenFilled() {
+ return getLastFilled() != -1;
+ }
+
+ @Override
+ default String getLootTableName() {
+ return getLootableData().getLootable().getLootTableName();
+ }
+
+ @Override
+ default String setLootTable(String name, long seed) {
+ Validate.notNull(name);
+
+ String prevLootTable = getLootTableName();
+ getLootableData().getLootable().setLootTable(name, seed);
+ return prevLootTable;
+ }
+
+ @Override
+ default long getLootTableSeed() {
+ return getLootableData().getLootable().getLootTableSeed();
+ }
+
+ @Override
+ default void clearLootTable() {
+ getLootableData().getLootable().clearLootTable();
+ }
+
+ @Override
+ default boolean hasPlayerLooted(UUID player) {
+ return getLootableData().hasPlayerLooted(player);
+ }
+
+ @Override
+ default Long getLastLooted(UUID player) {
+ return getLootableData().getLastLooted(player);
+ }
+
+ @Override
+ default boolean setHasPlayerLooted(UUID player, boolean looted) {
+ final boolean hasLooted = hasPlayerLooted(player);
+ if (hasLooted != looted) {
+ getLootableData().setPlayerLootedState(player, looted);
+ }
+ return hasLooted;
+ }
+
+ @Override
+ default boolean hasPendingRefill() {
+ long nextRefill = getLootableData().getNextRefill();
+ return nextRefill != -1 && nextRefill > getLootableData().getLastFill();
+ }
+
+ @Override
+ default long getLastFilled() {
+ return getLootableData().getLastFill();
+ }
+
+ @Override
+ default long getNextRefill() {
+ return getLootableData().getNextRefill();
+ }
+
+ @Override
+ default long setNextRefill(long refillAt) {
+ if (refillAt < -1) {
+ refillAt = -1;
+ }
+ return getLootableData().setNextRefill(refillAt);
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/loottable/CraftLootableInventoryData.java b/src/main/java/com/destroystokyo/paper/loottable/CraftLootableInventoryData.java
new file mode 100644
index 0000000..4b2c3fd
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/loottable/CraftLootableInventoryData.java
@@ -0,0 +1,179 @@
+package com.destroystokyo.paper.loottable;
+
+import com.destroystokyo.paper.PaperWorldConfig;
+import net.minecraft.server.*;
+import org.bukkit.entity.Player;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+import java.util.UUID;
+
+public class CraftLootableInventoryData {
+
+ private static final Random RANDOM = new Random();
+
+ private long lastFill = -1;
+ private long nextRefill = -1;
+ private int numRefills = 0;
+ private Map<UUID, Long> lootedPlayers;
+ private final CraftLootableInventory lootable;
+
+ public CraftLootableInventoryData(CraftLootableInventory lootable) {
+ this.lootable = lootable;
+ }
+
+ long getLastFill() {
+ return this.lastFill;
+ }
+
+ long getNextRefill() {
+ return this.nextRefill;
+ }
+
+ long setNextRefill(long nextRefill) {
+ long prev = this.nextRefill;
+ this.nextRefill = nextRefill;
+ return prev;
+ }
+
+ CraftLootableInventory getLootable() {
+ return lootable;
+ }
+
+ public boolean shouldReplenish(EntityHuman player) {
+ String tableName = this.lootable.getLootTableName();
+
+ // No Loot Table associated
+ if (tableName == null) {
+ return false;
+ }
+
+ // ALWAYS process the first fill
+ if (this.lastFill == -1) {
+ 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 PaperWorldConfig paperConfig = this.lootable.getNMSWorld().paperConfig;
+
+ // Check if max refills has been hit
+ if (paperConfig.maxLootableRefills != -1 && this.numRefills >= paperConfig.maxLootableRefills) {
+ return false;
+ }
+
+ // Refill has not been reached
+ if (this.nextRefill > System.currentTimeMillis()) {
+ return false;
+ }
+
+
+ final Player bukkitPlayer = (Player) player.getBukkitEntity();
+ LootableInventoryReplenishEvent event = new LootableInventoryReplenishEvent(bukkitPlayer, lootable);
+ if (paperConfig.restrictPlayerReloot && hasPlayerLooted(player.getUniqueID())) {
+ event.setCancelled(true);
+ }
+ return event.callEvent();
+ }
+ public void processRefill(EntityHuman player) {
+ this.lastFill = System.currentTimeMillis();
+ final PaperWorldConfig paperConfig = this.lootable.getNMSWorld().paperConfig;
+ if (paperConfig.autoReplenishLootables) {
+ int min = paperConfig.lootableRegenMin * 1000;
+ int max = paperConfig.lootableRegenMax * 1000;
+ this.nextRefill = this.lastFill + min + RANDOM.nextInt(max - min + 1);
+ this.numRefills++;
+ if (paperConfig.changeLootTableSeedOnFill) {
+ this.lootable.setLootTableSeed(0);
+ }
+ this.setPlayerLootedState(player.getUniqueID(), true);
+ } else {
+ this.lootable.clearLootTable();
+ }
+ }
+
+
+ public void loadNbt(NBTTagCompound base) {
+ if (!base.hasKeyOfType("Paper.LootableData", 10)) { // 10 = compound
+ return;
+ }
+ NBTTagCompound comp = base.getCompound("Paper.LootableData");
+ if (comp.hasKey("lastFill")) {
+ this.lastFill = comp.getLong("lastFill");
+ }
+ if (comp.hasKey("nextRefill")) {
+ this.nextRefill = comp.getLong("nextRefill");
+ }
+
+ if (comp.hasKey("numRefills")) {
+ this.numRefills = comp.getInt("numRefills");
+ }
+ if (comp.hasKeyOfType("lootedPlayers", 9)) { // 9 = list
+ NBTTagList list = comp.getList("lootedPlayers", 10); // 10 = compound
+ final int size = list.size();
+ if (size > 0) {
+ this.lootedPlayers = new HashMap<>(list.size());
+ }
+ for (int i = 0; i < size; i++) {
+ final NBTTagCompound cmp = list.get(i);
+ lootedPlayers.put(cmp.getUUID("UUID"), cmp.getLong("Time"));
+ }
+ }
+ }
+ public void saveNbt(NBTTagCompound base) {
+ NBTTagCompound comp = new NBTTagCompound();
+ if (this.nextRefill != -1) {
+ comp.setLong("nextRefill", this.nextRefill);
+ }
+ if (this.lastFill != -1) {
+ comp.setLong("lastFill", this.lastFill);
+ }
+ if (this.numRefills != 0) {
+ comp.setInt("numRefills", this.numRefills);
+ }
+ if (this.lootedPlayers != null && !this.lootedPlayers.isEmpty()) {
+ NBTTagList list = new NBTTagList();
+ for (Map.Entry<UUID, Long> entry : this.lootedPlayers.entrySet()) {
+ NBTTagCompound cmp = new NBTTagCompound();
+ cmp.setUUID("UUID", entry.getKey());
+ cmp.setLong("Time", entry.getValue());
+ list.add(cmp);
+ }
+ comp.set("lootedPlayers", list);
+ }
+
+ if (!comp.isEmpty()) {
+ base.set("Paper.LootableData", comp);
+ }
+ }
+
+ void setPlayerLootedState(UUID player, boolean looted) {
+ if (looted && this.lootedPlayers == null) {
+ this.lootedPlayers = new HashMap<>();
+ }
+ if (looted) {
+ if (!this.lootedPlayers.containsKey(player)) {
+ this.lootedPlayers.put(player, System.currentTimeMillis());
+ }
+ } else if (this.lootedPlayers != null) {
+ this.lootedPlayers.remove(player);
+ }
+ }
+
+ boolean hasPlayerLooted(UUID player) {
+ return this.lootedPlayers != null && this.lootedPlayers.containsKey(player);
+ }
+
+ Long getLastLooted(UUID player) {
+ return lootedPlayers != null ? lootedPlayers.get(player) : null;
+ }
+}
diff --git a/src/main/java/net/minecraft/server/EntityMinecartContainer.java b/src/main/java/net/minecraft/server/EntityMinecartContainer.java
index 56e7de8..51b29c8 100644
--- a/src/main/java/net/minecraft/server/EntityMinecartContainer.java
+++ b/src/main/java/net/minecraft/server/EntityMinecartContainer.java
@@ -3,17 +3,20 @@ package net.minecraft.server;
import java.util.Random;
// CraftBukkit start
import java.util.List;
+
+import com.destroystokyo.paper.loottable.CraftLootableInventoryData; // Paper
+import com.destroystokyo.paper.loottable.CraftLootableInventory; // Paper
import org.bukkit.craftbukkit.entity.CraftHumanEntity;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.InventoryHolder;
// CraftBukkit end
-public abstract class EntityMinecartContainer extends EntityMinecartAbstract implements ITileInventory, ILootable {
+public abstract class EntityMinecartContainer extends EntityMinecartAbstract implements ITileInventory, ILootable, CraftLootableInventory { // Paper
private ItemStack[] items = new ItemStack[27]; // CraftBukkit - 36 -> 27
private boolean b = true;
private MinecraftKey c;
- private long d;
+ private long d;public long getLootTableSeed() { return d; } // Paper // OBFHELPER
// CraftBukkit start
public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
@@ -130,6 +133,7 @@ public abstract class EntityMinecartContainer extends EntityMinecartAbstract imp
protected void b(NBTTagCompound nbttagcompound) {
super.b(nbttagcompound);
+ lootableData.saveNbt(nbttagcompound); // Paper
if (this.c != null) {
nbttagcompound.setString("LootTable", this.c.toString());
if (this.d != 0L) {
@@ -156,6 +160,7 @@ public abstract class EntityMinecartContainer extends EntityMinecartAbstract imp
protected void a(NBTTagCompound nbttagcompound) {
super.a(nbttagcompound);
this.items = new ItemStack[this.getSize()];
+ lootableData.loadNbt(nbttagcompound); // Paper
if (nbttagcompound.hasKeyOfType("LootTable", 8)) {
this.c = new MinecraftKey(nbttagcompound.getString("LootTable"));
this.d = nbttagcompound.getLong("LootTableSeed");
@@ -217,10 +222,10 @@ public abstract class EntityMinecartContainer extends EntityMinecartAbstract imp
}
public void f(EntityHuman entityhuman) {
- if (this.c != null) {
+ if (lootableData.shouldReplenish(entityhuman)) { // Paper
LootTable loottable = this.world.ak().a(this.c);
- this.c = null;
+ lootableData.processRefill(entityhuman); // Paper
Random random;
if (this.d == 0L) {
@@ -249,12 +254,42 @@ public abstract class EntityMinecartContainer extends EntityMinecartAbstract imp
}
+ public void setLootTable(MinecraftKey key, long seed) { a(key, seed);} // Paper // OBFHELPER
public void a(MinecraftKey minecraftkey, long i) {
this.c = minecraftkey;
this.d = i;
}
+
+ public MinecraftKey getLootTableKey() { return b(); } // Paper // OBFHELPER
public MinecraftKey b() {
return this.c;
}
+
+ // Paper start
+ private CraftLootableInventoryData lootableData = new CraftLootableInventoryData(this);
+
+ @Override
+ public CraftLootableInventoryData getLootableData() {
+ return lootableData;
+ }
+
+ public String getLootTableName() {
+ final MinecraftKey key = getLootTableKey();
+ return key != null ? key.toString() : null;
+ }
+
+ @Override
+ public String setLootTable(String name, long seed) {
+ String prev = getLootTableName();
+ setLootTable(new MinecraftKey(name), seed);
+ return prev;
+ }
+
+ @Override
+ public void clearLootTable() {
+ //noinspection RedundantCast
+ this.c = (MinecraftKey) null;
+ }
+ // Paper end
}
diff --git a/src/main/java/net/minecraft/server/TileEntityLootable.java b/src/main/java/net/minecraft/server/TileEntityLootable.java
index 437ac1e..2df98ff 100644
--- a/src/main/java/net/minecraft/server/TileEntityLootable.java
+++ b/src/main/java/net/minecraft/server/TileEntityLootable.java
@@ -1,15 +1,19 @@
package net.minecraft.server;
+import com.destroystokyo.paper.loottable.CraftLootableInventoryData; // Paper
+import com.destroystokyo.paper.loottable.CraftLootableInventory; // Paper
+
import java.util.Random;
-public abstract class TileEntityLootable extends TileEntityContainer implements ILootable {
+public abstract class TileEntityLootable extends TileEntityContainer implements ILootable, CraftLootableInventory { // Paper
protected MinecraftKey m;
- protected long n;
+ protected long n; public long getLootTableSeed() { return n; } // Paper // OBFHELPER
public TileEntityLootable() {}
protected boolean c(NBTTagCompound nbttagcompound) {
+ lootableData.loadNbt(nbttagcompound); // Paper
if (nbttagcompound.hasKeyOfType("LootTable", 8)) {
this.m = new MinecraftKey(nbttagcompound.getString("LootTable"));
this.n = nbttagcompound.getLong("LootTableSeed");
@@ -20,6 +24,7 @@ public abstract class TileEntityLootable extends TileEntityContainer implements
}
protected boolean d(NBTTagCompound nbttagcompound) {
+ lootableData.saveNbt(nbttagcompound); // Paper
if (this.m != null) {
nbttagcompound.setString("LootTable", this.m.toString());
if (this.n != 0L) {
@@ -33,10 +38,10 @@ public abstract class TileEntityLootable extends TileEntityContainer implements
}
protected void d(EntityHuman entityhuman) {
- if (this.m != null) {
+ if (lootableData.shouldReplenish(entityhuman)) { // Paper
LootTable loottable = this.world.ak().a(this.m);
- this.m = null;
+ lootableData.processRefill(entityhuman); // Paper
Random random;
if (this.n == 0L) {
@@ -56,12 +61,46 @@ public abstract class TileEntityLootable extends TileEntityContainer implements
}
+ public MinecraftKey getLootTableKey() { return b(); } // Paper // OBFHELPER
public MinecraftKey b() {
return this.m;
}
+ public void setLootTable(MinecraftKey key, long seed) { a(key, seed);} // Paper // OBFHELPER
public void a(MinecraftKey minecraftkey, long i) {
this.m = minecraftkey;
this.n = i;
}
+
+ // Paper start - LootTable API
+ private CraftLootableInventoryData lootableData = new CraftLootableInventoryData(this);
+
+ @Override
+ public CraftLootableInventoryData getLootableData() {
+ return lootableData;
+ }
+
+ @Override
+ public World getNMSWorld() {
+ return world;
+ }
+
+ public String getLootTableName() {
+ final MinecraftKey key = getLootTableKey();
+ return key != null ? key.toString() : null;
+ }
+
+ @Override
+ public String setLootTable(String name, long seed) {
+ String prev = getLootTableName();
+ setLootTable(new MinecraftKey(name), seed);
+ return prev;
+ }
+
+ @Override
+ public void clearLootTable() {
+ //noinspection RedundantCast
+ this.m = (MinecraftKey) null;
+ }
+ // Paper end
}
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java b/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java
index ce36ee4..99b039b 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java
@@ -1,5 +1,6 @@
package org.bukkit.craftbukkit.block;
+import com.destroystokyo.paper.loottable.CraftLootableBlockInventory; // Paper
import net.minecraft.server.BlockPosition;
import net.minecraft.server.TileEntityChest;
@@ -11,7 +12,7 @@ import org.bukkit.craftbukkit.inventory.CraftInventory;
import org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest;
import org.bukkit.inventory.Inventory;
-public class CraftChest extends CraftBlockState implements Chest {
+public class CraftChest extends CraftBlockState implements Chest, CraftLootableBlockInventory { // Paper
private final CraftWorld world;
private final TileEntityChest chest;
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftDispenser.java b/src/main/java/org/bukkit/craftbukkit/block/CraftDispenser.java
index a3ca3a5..cd7e65d 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftDispenser.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftDispenser.java
@@ -1,5 +1,6 @@
package org.bukkit.craftbukkit.block;
+import com.destroystokyo.paper.loottable.CraftLootableBlockInventory; // Paper
import net.minecraft.server.BlockDispenser;
import net.minecraft.server.BlockPosition;
import net.minecraft.server.Blocks;
@@ -14,7 +15,7 @@ import org.bukkit.craftbukkit.projectiles.CraftBlockProjectileSource;
import org.bukkit.inventory.Inventory;
import org.bukkit.projectiles.BlockProjectileSource;
-public class CraftDispenser extends CraftBlockState implements Dispenser {
+public class CraftDispenser extends CraftBlockState implements Dispenser, CraftLootableBlockInventory { // Paper
private final CraftWorld world;
private final TileEntityDispenser dispenser;
diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftHopper.java b/src/main/java/org/bukkit/craftbukkit/block/CraftHopper.java
index db844a3..1e805a7 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftHopper.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftHopper.java
@@ -1,5 +1,6 @@
package org.bukkit.craftbukkit.block;
+import com.destroystokyo.paper.loottable.CraftLootableBlockInventory; // Paper
import net.minecraft.server.TileEntityHopper;
import org.bukkit.Material;
import org.bukkit.block.Block;
@@ -8,7 +9,7 @@ import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.inventory.CraftInventory;
import org.bukkit.inventory.Inventory;
-public class CraftHopper extends CraftBlockState implements Hopper {
+public class CraftHopper extends CraftBlockState implements Hopper, CraftLootableBlockInventory { // Paper
private final TileEntityHopper hopper;
public CraftHopper(final Block block) {
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java
index f5a1875..b2c5679 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java
@@ -1,5 +1,6 @@
package org.bukkit.craftbukkit.entity;
+import com.destroystokyo.paper.loottable.CraftLootableEntityInventory; // Paper
import net.minecraft.server.EntityMinecartChest;
import org.bukkit.craftbukkit.CraftServer;
@@ -9,7 +10,7 @@ import org.bukkit.entity.StorageMinecart;
import org.bukkit.inventory.Inventory;
@SuppressWarnings("deprecation")
-public class CraftMinecartChest extends CraftMinecart implements StorageMinecart {
+public class CraftMinecartChest extends CraftMinecart implements StorageMinecart, CraftLootableEntityInventory { // Paper
private final CraftInventory inventory;
public CraftMinecartChest(CraftServer server, EntityMinecartChest entity) {
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java
index 38a5e02..0ccc616 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java
@@ -1,5 +1,6 @@
package org.bukkit.craftbukkit.entity;
+import com.destroystokyo.paper.loottable.CraftLootableEntityInventory; // Paper
import net.minecraft.server.EntityMinecartHopper;
import org.bukkit.craftbukkit.CraftServer;
@@ -8,7 +9,7 @@ import org.bukkit.entity.EntityType;
import org.bukkit.entity.minecart.HopperMinecart;
import org.bukkit.inventory.Inventory;
-final class CraftMinecartHopper extends CraftMinecart implements HopperMinecart {
+final class CraftMinecartHopper extends CraftMinecart implements HopperMinecart, CraftLootableEntityInventory { // Paper
private final CraftInventory inventory;
CraftMinecartHopper(CraftServer server, EntityMinecartHopper entity) {
--
2.8.2