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.
This commit is contained in:
Aikar 2016-05-01 23:54:08 -04:00
parent 4e7355aa95
commit 3da6be053f
7 changed files with 1140 additions and 20 deletions

View file

@ -0,0 +1,352 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Sun, 1 May 2016 15:19:49 -0400
Subject: [PATCH] LootTable API
Provides API to control what Loot Table an object uses.
Also provides an Event to control if a lootable inventory should
auto replenish for a player.
Provides methods to determine players looted state for an object
diff --git a/src/main/java/com/destroystokyo/paper/loottable/Lootable.java b/src/main/java/com/destroystokyo/paper/loottable/Lootable.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/loottable/Lootable.java
@@ -0,0 +0,0 @@
+package com.destroystokyo.paper.loottable;
+
+/**
+ * Defines an object that has a Loot Table and seed associated with it.
+ *
+ * How the Loot Table and seed are used may vary based on Minecraft Versions
+ * and what type of object is using the Loot Table
+ */
+public interface Lootable {
+
+ /**
+ * Gets the name of the Loot Table to be used in the World Folder
+ * @return The name, or null if no loot table exists
+ */
+ String getLootTableName();
+
+ /**
+ * Returns whether or not this object has a Loot Table
+ * @return Has a loot table
+ */
+ default boolean hasLootTable() {
+ return getLootTableName() != null;
+ }
+
+ /**
+ * Sets the name of the Loot Table to be used in the World Folder
+ * Will use a random seed (0)
+ *
+ * @param name name in either foo or minecraft:foo format
+ * @return The previous Loot Table before the change
+ */
+ default String setLootTable(String name) {
+ return setLootTable(name, 0);
+ }
+
+ /**
+ * Sets the name of the Loot Table to be used in the World Folder
+ * Uses supplied Seed
+ *
+ * @param name name in either foo or minecraft:foo format
+ * @param seed seed for the loot table. If 0, seed will be random
+ * @return The previous Loot Table before the change
+ */
+ String setLootTable(String name, long seed);
+
+ /**
+ * Gets the current seed associated to the Loot Table on this object
+ *
+ * @return The seed, or 0 for random
+ */
+ long getLootTableSeed();
+
+ /**
+ * Changes the current seed associated with the Loot Table on this object.
+ *
+ * The seed will have no affect if this object does not have a Loot Table
+ * associated with it.
+ *
+ * @throws IllegalStateException If called when this object does not have a loot table
+ * @param seed The seed to use, or 0 for random
+ * @return The previous seed
+ */
+ default long setLootTableSeed(long seed) {
+ final String lootTableName = getLootTableName();
+ if (lootTableName == null) {
+ throw new IllegalStateException("This object does not currently have a Loot Table.");
+ }
+
+ long prev = getLootTableSeed();
+ setLootTable(lootTableName, seed);
+ return prev;
+ }
+
+ /**
+ * Clears the associated Loot Table to this object
+ */
+ void clearLootTable();
+}
diff --git a/src/main/java/com/destroystokyo/paper/loottable/LootableBlockInventory.java b/src/main/java/com/destroystokyo/paper/loottable/LootableBlockInventory.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/loottable/LootableBlockInventory.java
@@ -0,0 +0,0 @@
+package com.destroystokyo.paper.loottable;
+
+import org.bukkit.block.Block;
+
+public interface LootableBlockInventory extends LootableInventory {
+
+ /**
+ * Gets the block that is lootable
+ * @return The Block
+ */
+ Block getBlock();
+}
diff --git a/src/main/java/com/destroystokyo/paper/loottable/LootableEntityInventory.java b/src/main/java/com/destroystokyo/paper/loottable/LootableEntityInventory.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/loottable/LootableEntityInventory.java
@@ -0,0 +0,0 @@
+package com.destroystokyo.paper.loottable;
+
+import org.bukkit.entity.Entity;
+
+public interface LootableEntityInventory extends LootableInventory {
+
+ /**
+ * Gets the entity that is lootable
+ * @return The Entity
+ */
+ Entity getEntity();
+}
diff --git a/src/main/java/com/destroystokyo/paper/loottable/LootableInventory.java b/src/main/java/com/destroystokyo/paper/loottable/LootableInventory.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/loottable/LootableInventory.java
@@ -0,0 +0,0 @@
+package com.destroystokyo.paper.loottable;
+
+import org.bukkit.entity.Player;
+
+import java.util.UUID;
+
+/**
+ * Represents an Inventory that contains a Loot Table associated to it that will
+ * automatically fill on first open.
+ *
+ * A new feature and API is provided to support automatically refreshing the contents
+ * of the inventory based on that Loot Table after a configurable amount of time has passed.
+ *
+ * The behavior of how the Inventory is filled based on the loot table may vary based
+ * on Minecraft versions and the Loot Table feature.
+ */
+public interface LootableInventory extends Lootable {
+
+ /**
+ * Server owners have to enable whether or not an object in a world should refill
+ *
+ * @return If the world this inventory is currently in has Replenishable Lootables enabled
+ */
+ boolean isRefillEnabled();
+
+ /**
+ * Whether or not this object has ever been filled
+ * @return Has ever been filled
+ */
+ boolean hasBeenFilled();
+
+ /**
+ * Has this player ever looted this block
+ * @param player The player to check
+ * @return Whether or not this player has looted this block
+ */
+ default boolean hasPlayerLooted(Player player) {
+ return hasPlayerLooted(player.getUniqueId());
+ }
+
+ /**
+ * Has this player ever looted this block
+ * @param player The player to check
+ * @return Whether or not this player has looted this block
+ */
+ boolean hasPlayerLooted(UUID player);
+
+ /**
+ * Gets the timestamp, in milliseconds, of when the player last looted this object
+ *
+ * @param player The player to check
+ * @return Timestamp last looted, or null if player has not looted this object
+ */
+ default Long getLastLooted(Player player) {
+ return getLastLooted(player.getUniqueId());
+ }
+
+ /**
+ * Gets the timestamp, in milliseconds, of when the player last looted this object
+ *
+ * @param player The player to check
+ * @return Timestamp last looted, or null if player has not looted this object
+ */
+ Long getLastLooted(UUID player);
+
+ /**
+ * Change the state of whether or not a player has looted this block
+ * @param player The player to change state for
+ * @param looted true to add player to looted list, false to remove
+ * @return The previous state of whether the player had looted this or not
+ */
+ default boolean setHasPlayerLooted(Player player, boolean looted) {
+ return setHasPlayerLooted(player.getUniqueId(), looted);
+ }
+
+ /**
+ * Change the state of whether or not a player has looted this block
+ * @param player The player to change state for
+ * @param looted true to add player to looted list, false to remove
+ * @return The previous state of whether the player had looted this or not
+ */
+ boolean setHasPlayerLooted(UUID player, boolean looted);
+
+ /**
+ * Returns Whether or not this object has been filled and now has a pending refill
+ * @return Has pending refill
+ */
+ boolean hasPendingRefill();
+
+ /**
+ * Gets the timestamp in milliseconds that the Lootable object was last refilled
+ *
+ * @return -1 if it was never refilled, or timestamp in milliseconds
+ */
+ long getLastFilled();
+
+ /**
+ * Gets the timestamp in milliseconds that the Lootable object will refill
+ *
+ * @return -1 if it is not scheduled for refill, or timestamp in milliseconds
+ */
+ long getNextRefill();
+
+ /**
+ * Sets the timestamp in milliseconds of the next refill for this object
+ *
+ * @param refillAt timestamp in milliseconds. -1 to clear next refill
+ * @return The previous scheduled time to refill, or -1 if was not scheduled
+ */
+ long setNextRefill(long refillAt);
+}
diff --git a/src/main/java/com/destroystokyo/paper/loottable/LootableInventoryReplenishEvent.java b/src/main/java/com/destroystokyo/paper/loottable/LootableInventoryReplenishEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/loottable/LootableInventoryReplenishEvent.java
@@ -0,0 +0,0 @@
+package com.destroystokyo.paper.loottable;
+
+import org.bukkit.entity.Player;
+import org.bukkit.event.Cancellable;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.player.PlayerEvent;
+
+public class LootableInventoryReplenishEvent extends PlayerEvent implements Cancellable {
+ private final LootableInventory inventory;
+
+ public LootableInventoryReplenishEvent(Player player, LootableInventory inventory) {
+ super(player);
+ this.inventory = inventory;
+ }
+
+ public LootableInventory getInventory() {
+ return inventory;
+ }
+
+ private static final HandlerList handlers = new HandlerList();
+
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+
+ private boolean cancelled = false;
+
+ @Override
+ public boolean isCancelled() {
+ return cancelled;
+ }
+
+ @Override
+ public void setCancelled(boolean cancel) {
+ cancelled = cancel;
+ }
+}
diff --git a/src/main/java/org/bukkit/block/Chest.java b/src/main/java/org/bukkit/block/Chest.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/block/Chest.java
+++ b/src/main/java/org/bukkit/block/Chest.java
@@ -0,0 +0,0 @@
package org.bukkit.block;
+import com.destroystokyo.paper.loottable.LootableInventory; // Paper
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
/**
* Represents a chest.
*/
-public interface Chest extends BlockState, InventoryHolder {
+public interface Chest extends BlockState, InventoryHolder, LootableInventory { // Paper
/**
* Returns the chest's inventory. If this is a double chest, it returns
diff --git a/src/main/java/org/bukkit/block/Dispenser.java b/src/main/java/org/bukkit/block/Dispenser.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/block/Dispenser.java
+++ b/src/main/java/org/bukkit/block/Dispenser.java
@@ -0,0 +0,0 @@
package org.bukkit.block;
+import com.destroystokyo.paper.loottable.LootableInventory; // Paper
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.projectiles.BlockProjectileSource;
/**
* Represents a dispenser.
*/
-public interface Dispenser extends BlockState, InventoryHolder {
+public interface Dispenser extends BlockState, InventoryHolder, LootableInventory { // Paper
/**
* Gets the BlockProjectileSource object for this dispenser.
diff --git a/src/main/java/org/bukkit/block/Hopper.java b/src/main/java/org/bukkit/block/Hopper.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/block/Hopper.java
+++ b/src/main/java/org/bukkit/block/Hopper.java
@@ -0,0 +0,0 @@
package org.bukkit.block;
+import com.destroystokyo.paper.loottable.LootableInventory; // Paper
import org.bukkit.inventory.InventoryHolder;
/**
* Represents a hopper.
*/
-public interface Hopper extends BlockState, InventoryHolder {
+public interface Hopper extends BlockState, InventoryHolder, LootableInventory { // Paper
}
--

View file

@ -0,0 +1,699 @@
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 & 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/loottable/CraftLootable.java
@@ -0,0 +0,0 @@
+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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/loottable/CraftLootableBlockInventory.java
@@ -0,0 +0,0 @@
+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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/loottable/CraftLootableEntityInventory.java
@@ -0,0 +0,0 @@
+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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/loottable/CraftLootableInventory.java
@@ -0,0 +0,0 @@
+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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/loottable/CraftLootableInventoryData.java
@@ -0,0 +0,0 @@
+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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/EntityMinecartContainer.java
+++ b/src/main/java/net/minecraft/server/EntityMinecartContainer.java
@@ -0,0 +0,0 @@ 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>();
@@ -0,0 +0,0 @@ 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) {
@@ -0,0 +0,0 @@ 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");
@@ -0,0 +0,0 @@ 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) {
@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/TileEntityLootable.java
+++ b/src/main/java/net/minecraft/server/TileEntityLootable.java
@@ -0,0 +0,0 @@
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");
@@ -0,0 +0,0 @@ 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) {
@@ -0,0 +0,0 @@ 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) {
@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java
@@ -0,0 +0,0 @@
package org.bukkit.craftbukkit.block;
+import com.destroystokyo.paper.loottable.CraftLootableBlockInventory; // Paper
import net.minecraft.server.BlockPosition;
import net.minecraft.server.TileEntityChest;
@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftDispenser.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftDispenser.java
@@ -0,0 +0,0 @@
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;
@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/block/CraftHopper.java
+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftHopper.java
@@ -0,0 +0,0 @@
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;
@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartChest.java
@@ -0,0 +0,0 @@
package org.bukkit.craftbukkit.entity;
+import com.destroystokyo.paper.loottable.CraftLootableEntityInventory; // Paper
import net.minecraft.server.EntityMinecartChest;
import org.bukkit.craftbukkit.CraftServer;
@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java
@@ -0,0 +0,0 @@
package org.bukkit.craftbukkit.entity;
+import com.destroystokyo.paper.loottable.CraftLootableEntityInventory; // Paper
import net.minecraft.server.EntityMinecartHopper;
import org.bukkit.craftbukkit.CraftServer;
@@ -0,0 +0,0 @@ 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) {
--

View file

@ -130,4 +130,44 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ return (modX == 0 || modX == 15 || modZ == 0 || modZ == 15);
+ }
+}
diff --git a/src/main/java/net/minecraft/server/NBTTagCompound.java b/src/main/java/net/minecraft/server/NBTTagCompound.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/NBTTagCompound.java
+++ b/src/main/java/net/minecraft/server/NBTTagCompound.java
@@ -0,0 +0,0 @@ import java.util.concurrent.Callable;
public class NBTTagCompound extends NBTBase {
- private Map<String, NBTBase> map = Maps.newHashMap();
+ public Map<String, NBTBase> map = Maps.newHashMap(); // Paper
public NBTTagCompound() {}
@@ -0,0 +0,0 @@ public class NBTTagCompound extends NBTBase {
this.map.put(s, new NBTTagLong(i));
}
+ public void setUUID(String prefix, UUID uuid) { a(prefix, uuid); } // Paper // OBFHELPER
public void a(String s, UUID uuid) {
this.setLong(s + "Most", uuid.getMostSignificantBits());
this.setLong(s + "Least", uuid.getLeastSignificantBits());
}
+ public UUID getUUID(String prefix) { return a(prefix); } // Paper // OBFHELPER
public UUID a(String s) {
return new UUID(this.getLong(s + "Most"), this.getLong(s + "Least"));
}
diff --git a/src/main/java/net/minecraft/server/NBTTagList.java b/src/main/java/net/minecraft/server/NBTTagList.java
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/net/minecraft/server/NBTTagList.java
+++ b/src/main/java/net/minecraft/server/NBTTagList.java
@@ -0,0 +0,0 @@ import org.apache.logging.log4j.Logger;
public class NBTTagList extends NBTBase {
private static final Logger b = LogManager.getLogger();
- private List<NBTBase> list = Lists.newArrayList();
+ public List<NBTBase> list = Lists.newArrayList(); // Paper
private byte type = 0;
public NBTTagList() {}
--

View file

@ -22,7 +22,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.regex.Pattern;
+
+import net.minecraft.server.MinecraftServer;
+import org.bukkit.Bukkit;
@ -98,6 +100,46 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ }
+ }
+
+ private static final Pattern SPACE = Pattern.compile(" ");
+ private static final Pattern NOT_NUMERIC = Pattern.compile("[^-\\d.]");
+ public static int getSeconds(String str) {
+ str = SPACE.matcher(str).replaceAll("");
+ final char unit = str.charAt(str.length() - 1);
+ str = NOT_NUMERIC.matcher(str).replaceAll("");
+ double num;
+ try {
+ num = Double.parseDouble(str);
+ } catch (Exception e) {
+ num = 0D;
+ }
+ switch (unit) {
+ case 'd': num *= (double) 60*60*24; break;
+ case 'h': num *= (double) 60*60; break;
+ case 'm': num *= (double) 60; break;
+ default: case 's': break;
+ }
+ return (int) num;
+ }
+
+ protected static String timeSummary(int seconds) {
+ String time = "";
+
+ if (seconds > 60 * 60 * 24) {
+ time += TimeUnit.SECONDS.toDays(seconds) + "d";
+ seconds %= 60 * 60 * 24;
+ }
+
+ if (seconds > 60 * 60) {
+ time += TimeUnit.SECONDS.toHours(seconds) + "h";
+ seconds %= 60 * 60;
+ }
+
+ if (seconds > 0) {
+ time += TimeUnit.SECONDS.toMinutes(seconds) + "m";
+ }
+ return time;
+ }
+
+ private static void set(String path, Object val) {
+ config.set(path, val);
+ }

View file

@ -218,12 +218,9 @@ diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/j
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
@@ -0,0 +0,0 @@ import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
@@ -0,0 +0,0 @@ import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.regex.Pattern;
+import com.google.common.collect.Lists;
import net.minecraft.server.MinecraftServer;
@ -259,19 +256,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
+ " - Verbose: " + verboseTimings +
+ " - Interval: " + timeSummary(Timings.getHistoryInterval() / 20) +
+ " - Length: " + timeSummary(Timings.getHistoryLength() / 20));
+ }
+
+ protected static String timeSummary(int seconds) {
+ String time = "";
+ if (seconds > 60 * 60) {
+ time += TimeUnit.SECONDS.toHours(seconds) + "h";
+ seconds /= 60;
+ }
+
+ if (seconds > 0) {
+ time += TimeUnit.SECONDS.toMinutes(seconds) + "m";
+ }
+ return time;
+ }
}
diff --git a/src/main/java/net/minecraft/server/Block.java b/src/main/java/net/minecraft/server/Block.java

View file

@ -9,8 +9,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
@@ -0,0 +0,0 @@ public class PaperConfig {
}
return time;
" - Interval: " + timeSummary(Timings.getHistoryInterval() / 20) +
" - Length: " + timeSummary(Timings.getHistoryLength() / 20));
}
+
+ public static boolean useInteractLimiter;

View file

@ -56,6 +56,8 @@ import EntitySquid
import EntityWaterAnimal
import FileIOThread
import ItemBlock
import NBTTagCompound
import NBTTagList
import PacketPlayInResourcePackStatus
import PacketPlayInUseEntity
import PacketPlayOutPlayerListHeaderFooter
@ -68,6 +70,7 @@ import PathfinderWater
import PersistentVillage
import RemoteControlListener
import TileEntityEnderChest
import TileEntityLootable
import WorldProvider
cd "$workdir/Spigot/Spigot-Server/"