diff --git a/Spigot-API-Patches/LootTable-API.patch b/Spigot-API-Patches/LootTable-API.patch new file mode 100644 index 0000000000..af94a0c25c --- /dev/null +++ b/Spigot-API-Patches/LootTable-API.patch @@ -0,0 +1,352 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +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 + + } +-- \ No newline at end of file diff --git a/Spigot-Server-Patches/LootTable-API-Replenishable-Lootables-Feature.patch b/Spigot-Server-Patches/LootTable-API-Replenishable-Lootables-Feature.patch new file mode 100644 index 0000000000..ccb95f4117 --- /dev/null +++ b/Spigot-Server-Patches/LootTable-API-Replenishable-Lootables-Feature.patch @@ -0,0 +1,699 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Aikar +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 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 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 transaction = new java.util.ArrayList(); +@@ -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) { +-- \ No newline at end of file diff --git a/Spigot-Server-Patches/MC-Utils.patch b/Spigot-Server-Patches/MC-Utils.patch index 491f6270cf..7e054c0e7f 100644 --- a/Spigot-Server-Patches/MC-Utils.patch +++ b/Spigot-Server-Patches/MC-Utils.patch @@ -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 map = Maps.newHashMap(); ++ public Map 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 list = Lists.newArrayList(); ++ public List list = Lists.newArrayList(); // Paper + private byte type = 0; + + public NBTTagList() {} -- \ No newline at end of file diff --git a/Spigot-Server-Patches/Paper-config-files.patch b/Spigot-Server-Patches/Paper-config-files.patch index e492e843fa..3c43718e0a 100644 --- a/Spigot-Server-Patches/Paper-config-files.patch +++ b/Spigot-Server-Patches/Paper-config-files.patch @@ -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); + } diff --git a/Spigot-Server-Patches/Timings-v2.patch b/Spigot-Server-Patches/Timings-v2.patch index eb97c2ce18..32407dc595 100644 --- a/Spigot-Server-Patches/Timings-v2.patch +++ b/Spigot-Server-Patches/Timings-v2.patch @@ -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 diff --git a/Spigot-Server-Patches/Toggle-for-player-interact-limiter.patch b/Spigot-Server-Patches/Toggle-for-player-interact-limiter.patch index 13dfedc66f..05ce70c982 100644 --- a/Spigot-Server-Patches/Toggle-for-player-interact-limiter.patch +++ b/Spigot-Server-Patches/Toggle-for-player-interact-limiter.patch @@ -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; diff --git a/scripts/importmcdev.sh b/scripts/importmcdev.sh index eb40d8f5c8..d12a8cccb6 100755 --- a/scripts/importmcdev.sh +++ b/scripts/importmcdev.sh @@ -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/"