getCriteria();
++
++ /**
++ * Gets the display properties of this advancement
++ *
++ * @return The display properties
++ */
++ @Nullable
++ AdvancementDisplay getDisplay();
+ }
+diff --git a/src/main/java/org/bukkit/advancement/AdvancementDisplay.java b/src/main/java/org/bukkit/advancement/AdvancementDisplay.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..bca3d112e2397b26ba6ccb6cd41e406caae27c5c
+--- /dev/null
++++ b/src/main/java/org/bukkit/advancement/AdvancementDisplay.java
+@@ -0,0 +1,53 @@
++package org.bukkit.advancement;
++
++import org.jetbrains.annotations.NotNull;
++
++public interface AdvancementDisplay {
++ /**
++ * Get the title of this advancement
++ *
++ * @return Title text
++ */
++ @NotNull
++ String getTitle();
++
++ /**
++ * Get the description of this advancement
++ *
++ * @return Description text
++ */
++ @NotNull
++ String getDescription();
++
++ /**
++ * Get the frame type of this advancement
++ *
++ * @return Frame type
++ */
++ @NotNull
++ FrameType getFrameType();
++
++ /**
++ * Get if this advancement should be announced in chat when completed
++ *
++ * @return True if should announce when completed
++ */
++ boolean shouldAnnounceToChat();
++
++ /**
++ * Set if this advancement should be announced in chat when completed
++ *
++ * @param announce True or false
++ *
++ */
++ void setShouldAnnounceToChat(boolean announce);
++
++ /**
++ * Get if this advancement (and all it's children) is hidden from the advancement screen until it has been completed
++ *
++ * This has no effect on root advancements themselves, but will alter their children
++ *
++ * @return True if hidden until completed
++ */
++ boolean isHidden();
++}
+diff --git a/src/main/java/org/bukkit/advancement/FrameType.java b/src/main/java/org/bukkit/advancement/FrameType.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..d1757f3d456ff9efce26ce8baa1d16d896908cc2
+--- /dev/null
++++ b/src/main/java/org/bukkit/advancement/FrameType.java
+@@ -0,0 +1,27 @@
++package org.bukkit.advancement;
++
++import org.bukkit.ChatColor;
++import org.jetbrains.annotations.NotNull;
++
++public enum FrameType {
++ TASK(ChatColor.GREEN),
++ CHALLENGE(ChatColor.DARK_PURPLE),
++ GOAL(ChatColor.GREEN);
++
++ private final ChatColor color;
++
++ FrameType(ChatColor color) {
++ this.color = color;
++ }
++
++ @NotNull
++ public ChatColor getColor() {
++ return color;
++ }
++
++ @NotNull
++ @Override
++ public String toString() {
++ return "FrameType[name=" + name() + ",color=" + color + "]";
++ }
++}
diff --git a/patches/Purpur/patches/api/0005-Llama-API.patch b/patches/Purpur/patches/api/0005-Llama-API.patch
new file mode 100644
index 00000000..8b336a47
--- /dev/null
+++ b/patches/Purpur/patches/api/0005-Llama-API.patch
@@ -0,0 +1,191 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Fri, 18 Oct 2019 22:50:05 -0500
+Subject: [PATCH] Llama API
+
+
+diff --git a/src/main/java/net/pl3x/purpur/event/entity/LlamaJoinCaravanEvent.java b/src/main/java/net/pl3x/purpur/event/entity/LlamaJoinCaravanEvent.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..6e68c1399bf30eeef6ce0385867f0cf258698eae
+--- /dev/null
++++ b/src/main/java/net/pl3x/purpur/event/entity/LlamaJoinCaravanEvent.java
+@@ -0,0 +1,61 @@
++package net.pl3x.purpur.event.entity;
++
++import org.bukkit.entity.Llama;
++import org.bukkit.event.Cancellable;
++import org.bukkit.event.HandlerList;
++import org.bukkit.event.entity.EntityEvent;
++import org.jetbrains.annotations.NotNull;
++
++/**
++ * Called when a Llama tries to join a caravan.
++ *
++ * Cancelling the event will not let the Llama join. To prevent future attempts
++ * at joining a caravan use {@link Llama#setShouldJoinCaravan(boolean)}.
++ */
++public class LlamaJoinCaravanEvent extends EntityEvent implements Cancellable {
++ private static final HandlerList handlers = new HandlerList();
++ private boolean canceled;
++ private final Llama head;
++
++ public LlamaJoinCaravanEvent(@NotNull Llama llama, @NotNull Llama head) {
++ super(llama);
++ this.head = head;
++ }
++
++ @Override
++ @NotNull
++ public Llama getEntity() {
++ return (Llama) entity;
++ }
++
++ /**
++ * Get the Llama that this Llama is about to follow
++ *
++ * @return Llama about to be followed
++ */
++ @NotNull
++ public Llama getHead() {
++ return head;
++ }
++
++ @Override
++ public boolean isCancelled() {
++ return canceled;
++ }
++
++ @Override
++ public void setCancelled(boolean cancel) {
++ canceled = cancel;
++ }
++
++ @Override
++ @NotNull
++ public HandlerList getHandlers() {
++ return handlers;
++ }
++
++ @NotNull
++ public static HandlerList getHandlerList() {
++ return handlers;
++ }
++}
+diff --git a/src/main/java/net/pl3x/purpur/event/entity/LlamaLeaveCaravanEvent.java b/src/main/java/net/pl3x/purpur/event/entity/LlamaLeaveCaravanEvent.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..ec8d978c22835e2789ebaaeddf0d13588ed1122a
+--- /dev/null
++++ b/src/main/java/net/pl3x/purpur/event/entity/LlamaLeaveCaravanEvent.java
+@@ -0,0 +1,34 @@
++package net.pl3x.purpur.event.entity;
++
++import org.bukkit.entity.Llama;
++import org.bukkit.event.HandlerList;
++import org.bukkit.event.entity.EntityEvent;
++import org.jetbrains.annotations.NotNull;
++
++/**
++ * Called when a Llama leaves a caravan
++ */
++public class LlamaLeaveCaravanEvent extends EntityEvent {
++ private static final HandlerList handlers = new HandlerList();
++
++ public LlamaLeaveCaravanEvent(@NotNull Llama llama) {
++ super(llama);
++ }
++
++ @Override
++ @NotNull
++ public Llama getEntity() {
++ return (Llama) entity;
++ }
++
++ @Override
++ @NotNull
++ public HandlerList getHandlers() {
++ return handlers;
++ }
++
++ @NotNull
++ public static HandlerList getHandlerList() {
++ return handlers;
++ }
++}
+diff --git a/src/main/java/org/bukkit/entity/Llama.java b/src/main/java/org/bukkit/entity/Llama.java
+index d23226ccb0f6c25028f000ce31346cd0a8898e6a..1ef9479c962b3f4f6fed46671a1209c34040d16d 100644
+--- a/src/main/java/org/bukkit/entity/Llama.java
++++ b/src/main/java/org/bukkit/entity/Llama.java
+@@ -3,6 +3,7 @@ package org.bukkit.entity;
+ import com.destroystokyo.paper.entity.RangedEntity;
+ import org.bukkit.inventory.LlamaInventory;
+ import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Nullable; // Purpur
+
+ /**
+ * Represents a Llama.
+@@ -67,4 +68,65 @@ public interface Llama extends ChestedHorse, RangedEntity { // Paper
+ @NotNull
+ @Override
+ LlamaInventory getInventory();
++
++ // Purpur start
++
++ /**
++ * Check if this Llama should attempt to join a caravan
++ *
++ * @return True if Llama is allowed to join a caravan
++ */
++ boolean shouldJoinCaravan();
++
++ /**
++ * Set if this Llama should attempt to join a caravan
++ *
++ * @param shouldJoinCaravan True to allow joining a caravan
++ */
++ void setShouldJoinCaravan(boolean shouldJoinCaravan);
++
++ /**
++ * Check if Llama is in a caravan
++ *
++ * @return True if in caravan
++ */
++ boolean inCaravan();
++
++ /**
++ * Join a caravan
++ *
++ * @param llama Head of caravan to join
++ */
++ void joinCaravan(@NotNull Llama llama);
++
++ /**
++ * Leave current caravan if in one
++ */
++ void leaveCaravan();
++
++ /**
++ * Check if another Llama is following this Llama
++ *
++ * @return True if being followed in the caravan
++ */
++ boolean hasCaravanTail();
++
++ /**
++ * Get the Llama that this Llama is following
++ *
++ * Does not necessarily mean the leader of the entire caravan
++ *
++ * @return The Llama being followed
++ */
++ @Nullable
++ Llama getCaravanHead();
++
++ /**
++ * Get the Llama following this Llama, if any
++ *
++ * @return The Llama following this one
++ */
++ @Nullable
++ Llama getCaravanTail();
++ // Purpur end
+ }
diff --git a/patches/Purpur/patches/api/0006-AFK-API.patch b/patches/Purpur/patches/api/0006-AFK-API.patch
new file mode 100644
index 00000000..1873b51a
--- /dev/null
+++ b/patches/Purpur/patches/api/0006-AFK-API.patch
@@ -0,0 +1,112 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sat, 10 Aug 2019 22:19:56 -0500
+Subject: [PATCH] AFK API
+
+
+diff --git a/src/main/java/net/pl3x/purpur/event/PlayerAFKEvent.java b/src/main/java/net/pl3x/purpur/event/PlayerAFKEvent.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0c8b3e5e4ba412624357ea5662a78862bd9fc4be
+--- /dev/null
++++ b/src/main/java/net/pl3x/purpur/event/PlayerAFKEvent.java
+@@ -0,0 +1,70 @@
++package net.pl3x.purpur.event;
++
++import org.bukkit.entity.Player;
++import org.bukkit.event.Cancellable;
++import org.bukkit.event.HandlerList;
++import org.bukkit.event.player.PlayerEvent;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Nullable;
++
++public class PlayerAFKEvent extends PlayerEvent implements Cancellable {
++ private static final HandlerList handlers = new HandlerList();
++ private final boolean setAfk;
++ private boolean shouldKick;
++ private String broadcast;
++ private boolean cancel;
++
++ public PlayerAFKEvent(@NotNull Player player, boolean setAfk, boolean shouldKick, @Nullable String broadcast, boolean async) {
++ super(player, async);
++ this.setAfk = setAfk;
++ this.shouldKick = shouldKick;
++ this.broadcast = broadcast;
++ }
++
++ /**
++ * Whether player is going afk or coming back
++ *
++ * @return True if going afk. False is coming back
++ */
++ public boolean isGoingAfk() {
++ return setAfk;
++ }
++
++ public boolean shouldKick() {
++ return shouldKick;
++ }
++
++ public void setShouldKick(boolean shouldKick) {
++ this.shouldKick = shouldKick;
++ }
++
++ @Nullable
++ public String getBroadcastMsg() {
++ return broadcast;
++ }
++
++ public void setBroadcastMsg(@Nullable String broadcast) {
++ this.broadcast = broadcast;
++ }
++
++ @Override
++ public boolean isCancelled() {
++ return cancel;
++ }
++
++ @Override
++ public void setCancelled(boolean cancel) {
++ this.cancel = cancel;
++ }
++
++ @Override
++ @NotNull
++ public HandlerList getHandlers() {
++ return handlers;
++ }
++
++ @NotNull
++ public static HandlerList getHandlerList() {
++ return handlers;
++ }
++}
+diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
+index cb6464c89e02d29484554a9a2184996a256925d2..7fd2085fa24779df1eab354532611d3642b37a27 100644
+--- a/src/main/java/org/bukkit/entity/Player.java
++++ b/src/main/java/org/bukkit/entity/Player.java
+@@ -1938,4 +1938,25 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+ @Override
+ Spigot spigot();
+ // Spigot end
++
++ // Purpur start
++ /**
++ * Check if player is AFK
++ *
++ * @return True if AFK
++ */
++ boolean isAfk();
++
++ /**
++ * Set player as AFK
++ *
++ * @param setAfk Whether to set AFK or not
++ */
++ void setAfk(boolean setAfk);
++
++ /**
++ * Reset the idle timer back to 0
++ */
++ void resetIdleTimer();
++ // Purpur end
+ }
diff --git a/patches/Purpur/patches/api/0007-Bring-back-server-name.patch b/patches/Purpur/patches/api/0007-Bring-back-server-name.patch
new file mode 100644
index 00000000..8fed27d7
--- /dev/null
+++ b/patches/Purpur/patches/api/0007-Bring-back-server-name.patch
@@ -0,0 +1,44 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sun, 26 May 2019 15:18:40 -0500
+Subject: [PATCH] Bring back server name
+
+
+diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java
+index fecd7b14d317f55eb1ce7b5c6af9913917971427..d070b420f4cb90610d7018a7fbbc88074268b02b 100644
+--- a/src/main/java/org/bukkit/Bukkit.java
++++ b/src/main/java/org/bukkit/Bukkit.java
+@@ -1817,4 +1817,15 @@ public final class Bukkit {
+ public static Server.Spigot spigot() {
+ return server.spigot();
+ }
++
++ // Purpur start
++ /**
++ * Get the name of this server
++ * @return the name of the server
++ */
++ @NotNull
++ public static String getServerName() {
++ return server.getServerName();
++ }
++ // Purpur end
+ }
+diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java
+index c69143ba156f6aa3cf3ffb9ee3f6d461867982c5..dce7d8126c4a628270b09608e4561c419238b0f4 100644
+--- a/src/main/java/org/bukkit/Server.java
++++ b/src/main/java/org/bukkit/Server.java
+@@ -1602,4 +1602,13 @@ public interface Server extends PluginMessageRecipient {
+ @NotNull
+ com.destroystokyo.paper.entity.ai.MobGoals getMobGoals();
+ // Paper end
++
++ // Purpur start
++ /**
++ * Get the name of this server
++ * @return the name of the server
++ */
++ @NotNull
++ String getServerName();
++ // Purpur end
+ }
diff --git a/patches/Purpur/patches/api/0008-ExecuteCommandEvent.patch b/patches/Purpur/patches/api/0008-ExecuteCommandEvent.patch
new file mode 100644
index 00000000..d5aca414
--- /dev/null
+++ b/patches/Purpur/patches/api/0008-ExecuteCommandEvent.patch
@@ -0,0 +1,175 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Fri, 31 May 2019 00:08:28 -0500
+Subject: [PATCH] ExecuteCommandEvent
+
+
+diff --git a/src/main/java/net/pl3x/purpur/event/ExecuteCommandEvent.java b/src/main/java/net/pl3x/purpur/event/ExecuteCommandEvent.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..3250bd4dc29a0cf79b08833d95a3321d1a6733f6
+--- /dev/null
++++ b/src/main/java/net/pl3x/purpur/event/ExecuteCommandEvent.java
+@@ -0,0 +1,130 @@
++package net.pl3x.purpur.event;
++
++import org.apache.commons.lang.Validate;
++import org.bukkit.command.Command;
++import org.bukkit.command.CommandSender;
++import org.bukkit.event.Cancellable;
++import org.bukkit.event.Event;
++import org.bukkit.event.HandlerList;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Nullable;
++
++/**
++ * This event is called whenever someone runs a command
++ */
++public class ExecuteCommandEvent extends Event implements Cancellable {
++ private static final HandlerList handlers = new HandlerList();
++ private boolean cancel = false;
++ private CommandSender sender;
++ private Command command;
++ private String label;
++ private String[] args;
++
++ public ExecuteCommandEvent(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @Nullable String[] args) {
++ this.sender = sender;
++ this.command = command;
++ this.label = label;
++ this.args = args;
++ }
++
++ /**
++ * Gets the command that the player is attempting to execute.
++ *
++ * @return Command the player is attempting to execute
++ */
++ @NotNull
++ public Command getCommand() {
++ return command;
++ }
++
++ /**
++ * Sets the command that the player will execute.
++ *
++ * @param command New command that the player will execute
++ * @throws IllegalArgumentException if command is null or empty
++ */
++ public void setCommand(@NotNull Command command) throws IllegalArgumentException {
++ Validate.notNull(command, "Command cannot be null");
++ this.command = command;
++ }
++
++ /**
++ * Gets the sender that this command will be executed as.
++ *
++ * @return Sender this command will be executed as
++ */
++ @NotNull
++ public CommandSender getSender() {
++ return sender;
++ }
++
++ /**
++ * Sets the sender that this command will be executed as.
++ *
++ * @param sender New sender which this event will execute as
++ * @throws IllegalArgumentException if the sender provided is null
++ */
++ public void setSender(@NotNull final CommandSender sender) throws IllegalArgumentException {
++ Validate.notNull(sender, "Sender cannot be null");
++ this.sender = sender;
++ }
++
++ /**
++ * Get the label used to execute this command
++ *
++ * @return Label used to execute this command
++ */
++ @NotNull
++ public String getLabel() {
++ return label;
++ }
++
++ /**
++ * Set the label used to execute this command
++ *
++ * @param label Label used
++ */
++ public void setLabel(@NotNull String label) {
++ this.label = label;
++ }
++
++ /**
++ * Get the args passed to the command
++ *
++ * @return Args passed to the command
++ */
++ @NotNull
++ public String[] getArgs() {
++ return args;
++ }
++
++ /**
++ * Set the args passed to the command
++ *
++ * @param args Args passed to the command
++ */
++ public void setArgs(@NotNull String[] args) {
++ this.args = args;
++ }
++
++ @Override
++ public boolean isCancelled() {
++ return cancel;
++ }
++
++ @Override
++ public void setCancelled(boolean cancel) {
++ this.cancel = cancel;
++ }
++
++ @NotNull
++ @Override
++ public HandlerList getHandlers() {
++ return handlers;
++ }
++
++ @NotNull
++ public static HandlerList getHandlerList() {
++ return handlers;
++ }
++}
+diff --git a/src/main/java/org/bukkit/command/SimpleCommandMap.java b/src/main/java/org/bukkit/command/SimpleCommandMap.java
+index 460fda05a62b12db2edcfb7ea8b2a5dd8e4b110d..1e0eb099933dded131d3c4db8f3cca2b6ed8e064 100644
+--- a/src/main/java/org/bukkit/command/SimpleCommandMap.java
++++ b/src/main/java/org/bukkit/command/SimpleCommandMap.java
+@@ -147,6 +147,19 @@ public class SimpleCommandMap implements CommandMap {
+ return false;
+ }
+
++ // Purpur start
++ String[] parsedArgs = Arrays.copyOfRange(args, 1, args.length);
++ net.pl3x.purpur.event.ExecuteCommandEvent event = new net.pl3x.purpur.event.ExecuteCommandEvent(sender, target, sentCommandLabel, parsedArgs);
++ if (!event.callEvent()) {
++ return true; // cancelled
++ }
++
++ sender = event.getSender();
++ target = event.getCommand();
++ sentCommandLabel = event.getLabel();
++ parsedArgs = event.getArgs();
++ // Purpur end
++
+ // Paper start - Plugins do weird things to workaround normal registration
+ if (target.timings == null) {
+ target.timings = co.aikar.timings.TimingsManager.getCommandTiming(null, target);
+@@ -156,7 +169,7 @@ public class SimpleCommandMap implements CommandMap {
+ try {
+ try (co.aikar.timings.Timing ignored = target.timings.startTiming()) { // Paper - use try with resources
+ // Note: we don't return the result of target.execute as thats success / failure, we return handled (true) or not handled (false)
+- target.execute(sender, sentCommandLabel, Arrays.copyOfRange(args, 1, args.length));
++ target.execute(sender, sentCommandLabel, parsedArgs); // Purpur
+ } // target.timings.stopTiming(); // Spigot // Paper
+ } catch (CommandException ex) {
+ server.getPluginManager().callEvent(new ServerExceptionEvent(new ServerCommandException(ex, target, sender, args))); // Paper
diff --git a/patches/Purpur/patches/api/0009-LivingEntity-safeFallDistance.patch b/patches/Purpur/patches/api/0009-LivingEntity-safeFallDistance.patch
new file mode 100644
index 00000000..71e010cd
--- /dev/null
+++ b/patches/Purpur/patches/api/0009-LivingEntity-safeFallDistance.patch
@@ -0,0 +1,31 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sun, 5 May 2019 12:58:19 -0500
+Subject: [PATCH] LivingEntity safeFallDistance
+
+
+diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java
+index 9f0645dc5f76ee9ef73d88f768025429e5a9edf7..4ccbb3ef3c597ef9da2c6744f410283a1dc2538c 100644
+--- a/src/main/java/org/bukkit/entity/LivingEntity.java
++++ b/src/main/java/org/bukkit/entity/LivingEntity.java
+@@ -850,4 +850,20 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource
+ */
+ void setHurtDirection(float hurtDirection);
+ // Paper end
++
++ // Purpur start
++ /**
++ * Gets the distance (in blocks) this entity can safely fall without taking damage
++ *
++ * @return Safe fall distance
++ */
++ float getSafeFallDistance();
++
++ /**
++ * Set the distance (in blocks) this entity can safely fall without taking damage
++ *
++ * @param safeFallDistance Safe fall distance
++ */
++ void setSafeFallDistance(float safeFallDistance);
++ // Purpur end
+ }
diff --git a/patches/Purpur/patches/api/0010-Lagging-threshold.patch b/patches/Purpur/patches/api/0010-Lagging-threshold.patch
new file mode 100644
index 00000000..a0e4cefa
--- /dev/null
+++ b/patches/Purpur/patches/api/0010-Lagging-threshold.patch
@@ -0,0 +1,42 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Tue, 23 Jul 2019 10:07:24 -0500
+Subject: [PATCH] Lagging threshold
+
+
+diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java
+index d070b420f4cb90610d7018a7fbbc88074268b02b..ba8eb67291c9848b367419f4c8110161ac7fab0d 100644
+--- a/src/main/java/org/bukkit/Bukkit.java
++++ b/src/main/java/org/bukkit/Bukkit.java
+@@ -1827,5 +1827,14 @@ public final class Bukkit {
+ public static String getServerName() {
+ return server.getServerName();
+ }
++
++ /**
++ * Check if server is lagging according to laggy threshold setting
++ *
++ * @return True if lagging
++ */
++ public static boolean isLagging() {
++ return server.isLagging();
++ }
+ // Purpur end
+ }
+diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java
+index dce7d8126c4a628270b09608e4561c419238b0f4..05b47c2462a00451fc64c79c3eda116fc8003a9f 100644
+--- a/src/main/java/org/bukkit/Server.java
++++ b/src/main/java/org/bukkit/Server.java
+@@ -1610,5 +1610,12 @@ public interface Server extends PluginMessageRecipient {
+ */
+ @NotNull
+ String getServerName();
++
++ /**
++ * Check if server is lagging according to laggy threshold setting
++ *
++ * @return True if lagging
++ */
++ boolean isLagging();
+ // Purpur end
+ }
diff --git a/patches/Purpur/patches/api/0011-ItemFactory-getMonsterEgg.patch b/patches/Purpur/patches/api/0011-ItemFactory-getMonsterEgg.patch
new file mode 100644
index 00000000..f475acc8
--- /dev/null
+++ b/patches/Purpur/patches/api/0011-ItemFactory-getMonsterEgg.patch
@@ -0,0 +1,26 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Fri, 5 Jul 2019 16:37:04 -0500
+Subject: [PATCH] ItemFactory#getMonsterEgg
+
+
+diff --git a/src/main/java/org/bukkit/inventory/ItemFactory.java b/src/main/java/org/bukkit/inventory/ItemFactory.java
+index 3f23927e58e0ccf8cf04d4beb4d83346e3f84730..23d55f756b2bb5a557bfae102d7039d8394fbe69 100644
+--- a/src/main/java/org/bukkit/inventory/ItemFactory.java
++++ b/src/main/java/org/bukkit/inventory/ItemFactory.java
+@@ -215,4 +215,15 @@ public interface ItemFactory {
+ @NotNull
+ net.md_5.bungee.api.chat.hover.content.Content hoverContentOf(@NotNull org.bukkit.entity.Entity entity, @NotNull net.md_5.bungee.api.chat.BaseComponent[] customName);
+ // Paper end
++
++ // Purpur start
++ /**
++ * Get a monster egg ItemStack from an EntityType
++ *
++ * @param type EntityType
++ * @return ItemStack spawner egg
++ */
++ @Nullable
++ ItemStack getMonsterEgg(@Nullable org.bukkit.entity.EntityType type);
++ // Purpur end
+ }
diff --git a/patches/Purpur/patches/api/0012-PlayerSetSpawnerTypeWithEggEvent.patch b/patches/Purpur/patches/api/0012-PlayerSetSpawnerTypeWithEggEvent.patch
new file mode 100644
index 00000000..51843969
--- /dev/null
+++ b/patches/Purpur/patches/api/0012-PlayerSetSpawnerTypeWithEggEvent.patch
@@ -0,0 +1,97 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Fri, 5 Jul 2019 18:21:15 -0500
+Subject: [PATCH] PlayerSetSpawnerTypeWithEggEvent
+
+
+diff --git a/src/main/java/net/pl3x/purpur/event/PlayerSetSpawnerTypeWithEggEvent.java b/src/main/java/net/pl3x/purpur/event/PlayerSetSpawnerTypeWithEggEvent.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..c050b75e9a11ac728868fe95e3f89e6b99de6ad2
+--- /dev/null
++++ b/src/main/java/net/pl3x/purpur/event/PlayerSetSpawnerTypeWithEggEvent.java
+@@ -0,0 +1,85 @@
++package net.pl3x.purpur.event;
++
++import org.bukkit.block.Block;
++import org.bukkit.block.CreatureSpawner;
++import org.bukkit.entity.EntityType;
++import org.bukkit.entity.Player;
++import org.bukkit.event.Cancellable;
++import org.bukkit.event.HandlerList;
++import org.bukkit.event.player.PlayerEvent;
++import org.jetbrains.annotations.NotNull;
++
++public class PlayerSetSpawnerTypeWithEggEvent extends PlayerEvent implements Cancellable {
++ private static final HandlerList handlers = new HandlerList();
++ private final Block block;
++ private final CreatureSpawner spawner;
++ private EntityType type;
++ private boolean cancel;
++
++ public PlayerSetSpawnerTypeWithEggEvent(@NotNull Player player, @NotNull Block block, @NotNull CreatureSpawner spawner, @NotNull EntityType type) {
++ super(player);
++ this.block = block;
++ this.spawner = spawner;
++ this.type = type;
++ }
++
++ /**
++ * Get the spawner Block in the world
++ *
++ * @return Spawner Block
++ */
++ @NotNull
++ public Block getBlock() {
++ return block;
++ }
++
++ /**
++ * Get the spawner state
++ *
++ * @return Spawner state
++ */
++ @NotNull
++ public CreatureSpawner getSpawner() {
++ return spawner;
++ }
++
++ /**
++ * Gets the EntityType being set on the spawner
++ *
++ * @return EntityType being set
++ */
++ @NotNull
++ public EntityType getEntityType() {
++ return type;
++ }
++
++ /**
++ * Sets the EntityType being set on the spawner
++ *
++ * @param type EntityType to set
++ */
++ public void setEntityType(@NotNull EntityType type) {
++ this.type = type;
++ }
++
++ @Override
++ public boolean isCancelled() {
++ return cancel;
++ }
++
++ @Override
++ public void setCancelled(boolean cancel) {
++ this.cancel = cancel;
++ }
++
++ @Override
++ @NotNull
++ public HandlerList getHandlers() {
++ return handlers;
++ }
++
++ @NotNull
++ public static HandlerList getHandlerList() {
++ return handlers;
++ }
++}
diff --git a/patches/Purpur/patches/api/0013-EMC-MonsterEggSpawnEvent.patch b/patches/Purpur/patches/api/0013-EMC-MonsterEggSpawnEvent.patch
new file mode 100644
index 00000000..8846d22f
--- /dev/null
+++ b/patches/Purpur/patches/api/0013-EMC-MonsterEggSpawnEvent.patch
@@ -0,0 +1,79 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Aikar
+Date: Mon, 21 Nov 2016 17:02:11 -0500
+Subject: [PATCH] EMC - MonsterEggSpawnEvent
+
+
+diff --git a/src/main/java/net/pl3x/purpur/event/entity/MonsterEggSpawnEvent.java b/src/main/java/net/pl3x/purpur/event/entity/MonsterEggSpawnEvent.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..983d67234b15c83c3785d9fbc191da70cf67ccab
+--- /dev/null
++++ b/src/main/java/net/pl3x/purpur/event/entity/MonsterEggSpawnEvent.java
+@@ -0,0 +1,67 @@
++package net.pl3x.purpur.event.entity;
++
++import org.bukkit.entity.HumanEntity;
++import org.bukkit.entity.LivingEntity;
++import org.bukkit.entity.Player;
++import org.bukkit.event.Cancellable;
++import org.bukkit.event.Event;
++import org.bukkit.event.HandlerList;
++import org.bukkit.inventory.ItemStack;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Nullable;
++
++public class MonsterEggSpawnEvent extends Event implements Cancellable {
++ private static final HandlerList handlers = new HandlerList();
++ private boolean canceled;
++
++ private final Player player;
++ private LivingEntity entity;
++ private final ItemStack item;
++
++ public MonsterEggSpawnEvent(@Nullable HumanEntity player, @NotNull LivingEntity entity, @NotNull ItemStack item) {
++ this.player = (Player) player;
++ this.entity = entity;
++ this.item = item;
++ }
++
++ @Nullable
++ public Player getPlayer() {
++ return player;
++ }
++
++ @NotNull
++ public LivingEntity getEntity() {
++ return entity;
++ }
++
++ public void setEntity(@Nullable LivingEntity entity) {
++ if (entity == null) {
++ canceled = true;
++ return;
++ }
++ this.entity = entity;
++ }
++
++ @NotNull
++ public ItemStack getItem() {
++ return item;
++ }
++
++ public boolean isCancelled() {
++ return canceled;
++ }
++
++ public void setCancelled(boolean cancel) {
++ canceled = cancel;
++ }
++
++ @NotNull
++ public HandlerList getHandlers() {
++ return handlers;
++ }
++
++ @NotNull
++ public static HandlerList getHandlerList() {
++ return handlers;
++ }
++}
diff --git a/patches/Purpur/patches/api/0014-Villager-resetOffers.patch b/patches/Purpur/patches/api/0014-Villager-resetOffers.patch
new file mode 100644
index 00000000..ae3bbedf
--- /dev/null
+++ b/patches/Purpur/patches/api/0014-Villager-resetOffers.patch
@@ -0,0 +1,24 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Mon, 7 Oct 2019 00:15:28 -0500
+Subject: [PATCH] Villager#resetOffers
+
+
+diff --git a/src/main/java/org/bukkit/entity/Villager.java b/src/main/java/org/bukkit/entity/Villager.java
+index c8777a476e38ef5e72b6709761990a339eb43d2b..ed703af452cd7db5e47608b4ff6ec049f76ed03a 100644
+--- a/src/main/java/org/bukkit/entity/Villager.java
++++ b/src/main/java/org/bukkit/entity/Villager.java
+@@ -113,6 +113,13 @@ public interface Villager extends AbstractVillager {
+ */
+ public void wakeup();
+
++ // Purpur start
++ /**
++ * Reset this villager's trade offers
++ */
++ public void resetOffers();
++ // Purpur end
++
+ /**
+ * Represents Villager type, usually corresponding to what biome they spawn
+ * in.
diff --git a/patches/Purpur/patches/api/0015-PaperPR-PlayerItemCooldownEvent.patch b/patches/Purpur/patches/api/0015-PaperPR-PlayerItemCooldownEvent.patch
new file mode 100644
index 00000000..1844d2c1
--- /dev/null
+++ b/patches/Purpur/patches/api/0015-PaperPR-PlayerItemCooldownEvent.patch
@@ -0,0 +1,89 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: KennyTV
+Date: Mon, 20 Apr 2020 13:57:13 +0200
+Subject: [PATCH] PaperPR - PlayerItemCooldownEvent
+
+
+diff --git a/src/main/java/net/pl3x/purpur/event/player/PlayerItemCooldownEvent.java b/src/main/java/net/pl3x/purpur/event/player/PlayerItemCooldownEvent.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..2002909f30d2bd833dc13cf09b0bc4bdae0d6757
+--- /dev/null
++++ b/src/main/java/net/pl3x/purpur/event/player/PlayerItemCooldownEvent.java
+@@ -0,0 +1,77 @@
++package net.pl3x.purpur.event.player;
++
++import com.google.common.base.Preconditions;
++import org.bukkit.Material;
++import org.bukkit.entity.Player;
++import org.bukkit.event.Cancellable;
++import org.bukkit.event.HandlerList;
++import org.bukkit.event.player.PlayerEvent;
++import org.jetbrains.annotations.NotNull;
++
++/**
++ * Called when a player receives a cooldown on an item.
++ */
++public final class PlayerItemCooldownEvent extends PlayerEvent implements Cancellable {
++ private static final HandlerList handlers = new HandlerList();
++ @NotNull
++ private final Material type;
++ private int cooldown;
++ private boolean cancelled;
++
++ public PlayerItemCooldownEvent(@NotNull Player player, @NotNull Material type, int cooldown) {
++ super(player);
++ this.type = type;
++ this.cooldown = cooldown;
++ }
++
++ /**
++ * Get the material affected by the cooldown.
++ *
++ * @return material affected by the cooldown
++ */
++ @NotNull
++ public Material getType() {
++ return type;
++ }
++
++ /**
++ * Gets the cooldown in ticks.
++ *
++ * @return cooldown in ticks
++ */
++ public int getCooldown() {
++ return cooldown;
++ }
++
++ /**
++ * Sets the cooldown of the material in ticks.
++ * Setting the cooldown to 0 results in removing an already existing cooldown for the material.
++ *
++ * @param cooldown cooldown in ticks, has to be a positive number
++ */
++ public void setCooldown(int cooldown) {
++ Preconditions.checkArgument(cooldown >= 0, "The cooldown has to be equal to or greater than 0!");
++ this.cooldown = cooldown;
++ }
++
++ @Override
++ public boolean isCancelled() {
++ return cancelled;
++ }
++
++ @Override
++ public void setCancelled(boolean cancel) {
++ cancelled = cancel;
++ }
++
++ @Override
++ @NotNull
++ public HandlerList getHandlers() {
++ return handlers;
++ }
++
++ @NotNull
++ public static HandlerList getHandlerList() {
++ return handlers;
++ }
++}
diff --git a/patches/Purpur/patches/api/0016-EntityMoveEvent.patch b/patches/Purpur/patches/api/0016-EntityMoveEvent.patch
new file mode 100644
index 00000000..7329ace3
--- /dev/null
+++ b/patches/Purpur/patches/api/0016-EntityMoveEvent.patch
@@ -0,0 +1,107 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Tue, 11 Feb 2020 21:56:38 -0600
+Subject: [PATCH] EntityMoveEvent
+
+
+diff --git a/src/main/java/net/pl3x/purpur/event/entity/EntityMoveEvent.java b/src/main/java/net/pl3x/purpur/event/entity/EntityMoveEvent.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..c48c525b8ee527a5766ac679619fd88956002d64
+--- /dev/null
++++ b/src/main/java/net/pl3x/purpur/event/entity/EntityMoveEvent.java
+@@ -0,0 +1,95 @@
++package net.pl3x.purpur.event.entity;
++
++import com.google.common.base.Preconditions;
++import org.bukkit.Location;
++import org.bukkit.entity.LivingEntity;
++import org.bukkit.event.Cancellable;
++import org.bukkit.event.HandlerList;
++import org.bukkit.event.entity.EntityEvent;
++import org.jetbrains.annotations.NotNull;
++
++/**
++ * Holds information for living entity movement events
++ */
++public class EntityMoveEvent extends EntityEvent implements Cancellable {
++ private static final HandlerList handlers = new HandlerList();
++ private boolean canceled;
++ private Location from;
++ private Location to;
++
++ public EntityMoveEvent(@NotNull LivingEntity entity, @NotNull Location from, @NotNull Location to) {
++ super(entity);
++ this.from = from;
++ this.to = to;
++ }
++
++ @Override
++ @NotNull
++ public LivingEntity getEntity() {
++ return (LivingEntity) entity;
++ }
++
++ public boolean isCancelled() {
++ return canceled;
++ }
++
++ public void setCancelled(boolean cancel) {
++ canceled = cancel;
++ }
++
++ /**
++ * Gets the location this entity moved from
++ *
++ * @return Location the entity moved from
++ */
++ @NotNull
++ public Location getFrom() {
++ return from;
++ }
++
++ /**
++ * Sets the location to mark as where the entity moved from
++ *
++ * @param from New location to mark as the entity's previous location
++ */
++ public void setFrom(@NotNull Location from) {
++ validateLocation(from);
++ this.from = from;
++ }
++
++ /**
++ * Gets the location this entity moved to
++ *
++ * @return Location the entity moved to
++ */
++ @NotNull
++ public Location getTo() {
++ return to;
++ }
++
++ /**
++ * Sets the location that this entity will move to
++ *
++ * @param to New Location this entity will move to
++ */
++ public void setTo(@NotNull Location to) {
++ validateLocation(to);
++ this.to = to;
++ }
++
++ private void validateLocation(@NotNull Location loc) {
++ Preconditions.checkArgument(loc != null, "Cannot use null location!");
++ Preconditions.checkArgument(loc.getWorld() != null, "Cannot use null location with null world!");
++ }
++
++ @Override
++ @NotNull
++ public HandlerList getHandlers() {
++ return handlers;
++ }
++
++ @NotNull
++ public static HandlerList getHandlerList() {
++ return handlers;
++ }
++}
diff --git a/patches/Purpur/patches/api/0017-Player-invulnerabilities.patch b/patches/Purpur/patches/api/0017-Player-invulnerabilities.patch
new file mode 100644
index 00000000..24d115ce
--- /dev/null
+++ b/patches/Purpur/patches/api/0017-Player-invulnerabilities.patch
@@ -0,0 +1,37 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sat, 2 May 2020 20:55:31 -0500
+Subject: [PATCH] Player invulnerabilities
+
+
+diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
+index 7fd2085fa24779df1eab354532611d3642b37a27..a79703115da811397ee6b7c6c079846af537fd12 100644
+--- a/src/main/java/org/bukkit/entity/Player.java
++++ b/src/main/java/org/bukkit/entity/Player.java
+@@ -1958,5 +1958,26 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+ * Reset the idle timer back to 0
+ */
+ void resetIdleTimer();
++
++ /**
++ * Check if player is invulnerable from recently spawning or accepting a resource pack
++ *
++ * @return True if invulnerable
++ */
++ boolean isSpawnInvulnerable();
++
++ /**
++ * Get invulnerable ticks remaining
++ *
++ * @return Invulnerable ticks
++ */
++ int getSpawnInvulnerableTicks();
++
++ /**
++ * Set invulnerable ticks remaining
++ *
++ * @param invulnerableTicks Invulnerable ticks remaining
++ */
++ void setSpawnInvulnerableTicks(int invulnerableTicks);
+ // Purpur end
+ }
diff --git a/patches/Purpur/patches/api/0018-Anvil-API.patch b/patches/Purpur/patches/api/0018-Anvil-API.patch
new file mode 100644
index 00000000..f660b4e7
--- /dev/null
+++ b/patches/Purpur/patches/api/0018-Anvil-API.patch
@@ -0,0 +1,124 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sun, 19 Apr 2020 00:25:09 -0500
+Subject: [PATCH] Anvil API
+
+
+diff --git a/src/main/java/net/pl3x/purpur/event/inventory/AnvilTakeResultEvent.java b/src/main/java/net/pl3x/purpur/event/inventory/AnvilTakeResultEvent.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..85663c0a44695f7b7f01a68693cac3d99f4b56ca
+--- /dev/null
++++ b/src/main/java/net/pl3x/purpur/event/inventory/AnvilTakeResultEvent.java
+@@ -0,0 +1,52 @@
++package net.pl3x.purpur.event.inventory;
++
++import org.bukkit.entity.HumanEntity;
++import org.bukkit.entity.Player;
++import org.bukkit.event.HandlerList;
++import org.bukkit.event.inventory.InventoryEvent;
++import org.bukkit.inventory.AnvilInventory;
++import org.bukkit.inventory.InventoryView;
++import org.bukkit.inventory.ItemStack;
++import org.jetbrains.annotations.NotNull;
++
++/**
++ * Called when a player takes the result item out of an anvil
++ */
++public class AnvilTakeResultEvent extends InventoryEvent {
++ private static final HandlerList handlers = new HandlerList();
++ private final Player player;
++ private final ItemStack result;
++
++ public AnvilTakeResultEvent(@NotNull HumanEntity player, @NotNull InventoryView view, @NotNull ItemStack result) {
++ super(view);
++ this.player = (Player) player;
++ this.result = result;
++ }
++
++ @NotNull
++ public Player getPlayer() {
++ return player;
++ }
++
++ @NotNull
++ public ItemStack getResult() {
++ return result;
++ }
++
++ @NotNull
++ @Override
++ public AnvilInventory getInventory() {
++ return (AnvilInventory) super.getInventory();
++ }
++
++ @NotNull
++ @Override
++ public HandlerList getHandlers() {
++ return handlers;
++ }
++
++ @NotNull
++ public static HandlerList getHandlerList() {
++ return handlers;
++ }
++}
+diff --git a/src/main/java/net/pl3x/purpur/event/inventory/AnvilUpdateResultEvent.java b/src/main/java/net/pl3x/purpur/event/inventory/AnvilUpdateResultEvent.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..2717ad82ccc0d39c5a69b8890303c245e9a17f83
+--- /dev/null
++++ b/src/main/java/net/pl3x/purpur/event/inventory/AnvilUpdateResultEvent.java
+@@ -0,0 +1,35 @@
++package net.pl3x.purpur.event.inventory;
++
++import org.bukkit.event.HandlerList;
++import org.bukkit.event.inventory.InventoryEvent;
++import org.bukkit.inventory.AnvilInventory;
++import org.bukkit.inventory.InventoryView;
++import org.jetbrains.annotations.NotNull;
++
++/**
++ * Called when anvil slots change, triggering the result slot to be updated
++ */
++public class AnvilUpdateResultEvent extends InventoryEvent {
++ private static final HandlerList handlers = new HandlerList();
++
++ public AnvilUpdateResultEvent(@NotNull InventoryView view) {
++ super(view);
++ }
++
++ @NotNull
++ @Override
++ public AnvilInventory getInventory() {
++ return (AnvilInventory) super.getInventory();
++ }
++
++ @NotNull
++ @Override
++ public HandlerList getHandlers() {
++ return handlers;
++ }
++
++ @NotNull
++ public static HandlerList getHandlerList() {
++ return handlers;
++ }
++}
+diff --git a/src/main/java/org/bukkit/inventory/AnvilInventory.java b/src/main/java/org/bukkit/inventory/AnvilInventory.java
+index b95e563b5454306a9188ae3295309ee86a756477..435026e533ea9edb8c1800d35c63543ca023a904 100644
+--- a/src/main/java/org/bukkit/inventory/AnvilInventory.java
++++ b/src/main/java/org/bukkit/inventory/AnvilInventory.java
+@@ -109,4 +109,14 @@ public interface AnvilInventory extends Inventory {
+ setItem(2, result);
+ }
+ // Paper end
++
++ // Purpur start
++ boolean canBypassCost();
++
++ void setBypassCost(boolean bypassCost);
++
++ boolean canDoUnsafeEnchants();
++
++ void setDoUnsafeEnchants(boolean canDoUnsafeEnchants);
++ // Purpur end
+ }
diff --git a/patches/Purpur/patches/api/0019-ItemStack-convenience-methods.patch b/patches/Purpur/patches/api/0019-ItemStack-convenience-methods.patch
new file mode 100644
index 00000000..1d5242c5
--- /dev/null
+++ b/patches/Purpur/patches/api/0019-ItemStack-convenience-methods.patch
@@ -0,0 +1,698 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sun, 15 Mar 2020 20:52:12 -0500
+Subject: [PATCH] ItemStack convenience methods
+
+
+diff --git a/src/main/java/org/bukkit/Material.java b/src/main/java/org/bukkit/Material.java
+index 4ba991b79f13219182df35b4ce0c5cf57cbd208b..a2e476f154344f9473dd9b48866505448de56d84 100644
+--- a/src/main/java/org/bukkit/Material.java
++++ b/src/main/java/org/bukkit/Material.java
+@@ -8645,4 +8645,36 @@ public enum Material implements Keyed {
+ //
+ }
+ }
++
++ // Purpur start
++ public boolean isArmor() {
++ switch (this) {
++ //
++ case LEATHER_BOOTS:
++ case LEATHER_CHESTPLATE:
++ case LEATHER_HELMET:
++ case LEATHER_LEGGINGS:
++ case CHAINMAIL_BOOTS:
++ case CHAINMAIL_CHESTPLATE:
++ case CHAINMAIL_HELMET:
++ case CHAINMAIL_LEGGINGS:
++ case IRON_BOOTS:
++ case IRON_CHESTPLATE:
++ case IRON_HELMET:
++ case IRON_LEGGINGS:
++ case GOLDEN_BOOTS:
++ case GOLDEN_CHESTPLATE:
++ case GOLDEN_HELMET:
++ case GOLDEN_LEGGINGS:
++ case DIAMOND_BOOTS:
++ case DIAMOND_CHESTPLATE:
++ case DIAMOND_HELMET:
++ case DIAMOND_LEGGINGS:
++ case TURTLE_HELMET:
++ return true;
++ default:
++ return false;
++ }
++ }
++ // Purpur end
+ }
+diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java
+index 4f2520f7a4ca6d57a85924ada1068a055b9a01fb..23cef1e67236a879525f39da994efc9a9c5cd289 100644
+--- a/src/main/java/org/bukkit/inventory/ItemStack.java
++++ b/src/main/java/org/bukkit/inventory/ItemStack.java
+@@ -17,6 +17,18 @@ import org.bukkit.inventory.meta.ItemMeta;
+ import org.bukkit.material.MaterialData;
+ import org.jetbrains.annotations.NotNull;
+ import org.jetbrains.annotations.Nullable;
++// Purpur start
++import com.google.common.collect.Multimap;
++import java.util.Collection;
++import org.bukkit.attribute.Attribute;
++import org.bukkit.attribute.AttributeModifier;
++import org.bukkit.block.data.BlockData;
++import org.bukkit.inventory.meta.BlockDataMeta;
++import org.bukkit.inventory.meta.Repairable;
++import org.bukkit.persistence.PersistentDataContainer;
++import org.bukkit.persistence.PersistentDataHolder;
++import com.destroystokyo.paper.Namespaced;
++// Purpur end
+
+ /**
+ * Represents a stack of items.
+@@ -792,4 +804,627 @@ public class ItemStack implements Cloneable, ConfigurationSerializable {
+ return itemMeta.hasItemFlag(flag);
+ }
+ // Paper end
++
++ // Purpur start
++ /**
++ * Gets the display name that is set.
++ *
++ * Plugins should check that hasDisplayName() returns true
++ * before calling this method.
++ *
++ * @return the display name that is set
++ */
++ @NotNull
++ public String getDisplayName() {
++ return getItemMeta().getDisplayName();
++ }
++
++ /**
++ * Sets the display name.
++ *
++ * @param name the name to set
++ */
++ public void setDisplayName(@Nullable String name) {
++ ItemMeta itemMeta = getItemMeta();
++ itemMeta.setDisplayName(name);
++ setItemMeta(itemMeta);
++ }
++
++ /**
++ * Checks for existence of a display name.
++ *
++ * @return true if this has a display name
++ */
++ public boolean hasDisplayName() {
++ return getItemMeta().hasDisplayName();
++ }
++
++ /**
++ * Gets the localized display name that is set.
++ *
++ * Plugins should check that hasLocalizedName() returns true
++ * before calling this method.
++ *
++ * @return the localized name that is set
++ */
++ @NotNull
++ public String getLocalizedName() {
++ return getItemMeta().getLocalizedName();
++ }
++
++ /**
++ * Sets the localized name.
++ *
++ * @param name the name to set
++ */
++ public void setLocalizedName(@Nullable String name) {
++ ItemMeta itemMeta = getItemMeta();
++ itemMeta.setLocalizedName(name);
++ setItemMeta(itemMeta);
++ }
++
++ /**
++ * Checks for existence of a localized name.
++ *
++ * @return true if this has a localized name
++ */
++ public boolean hasLocalizedName() {
++ return getItemMeta().hasLocalizedName();
++ }
++
++ /**
++ * Checks for existence of lore.
++ *
++ * @return true if this has lore
++ */
++ public boolean hasLore() {
++ return getItemMeta().hasLore();
++ }
++
++ /**
++ * Checks for existence of the specified enchantment.
++ *
++ * @param ench enchantment to check
++ * @return true if this enchantment exists for this meta
++ */
++ public boolean hasEnchant(@NotNull Enchantment ench) {
++ return getItemMeta().hasEnchant(ench);
++ }
++
++ /**
++ * Checks for the level of the specified enchantment.
++ *
++ * @param ench enchantment to check
++ * @return The level that the specified enchantment has, or 0 if none
++ */
++ public int getEnchantLevel(@NotNull Enchantment ench) {
++ return getItemMeta().getEnchantLevel(ench);
++ }
++
++ /**
++ * Returns a copy the enchantments in this ItemMeta.
++ * Returns an empty map if none.
++ *
++ * @return An immutable copy of the enchantments
++ */
++ @NotNull
++ public Map getEnchants() {
++ return getItemMeta().getEnchants();
++ }
++
++ /**
++ * Adds the specified enchantment to this item meta.
++ *
++ * @param ench Enchantment to add
++ * @param level Level for the enchantment
++ * @param ignoreLevelRestriction this indicates the enchantment should be
++ * applied, ignoring the level limit
++ * @return true if the item meta changed as a result of this call, false
++ * otherwise
++ */
++ public boolean addEnchant(@NotNull Enchantment ench, int level, boolean ignoreLevelRestriction) {
++ ItemMeta itemMeta = getItemMeta();
++ boolean result = itemMeta.addEnchant(ench, level, ignoreLevelRestriction);
++ setItemMeta(itemMeta);
++ return result;
++ }
++
++ /**
++ * Removes the specified enchantment from this item meta.
++ *
++ * @param ench Enchantment to remove
++ * @return true if the item meta changed as a result of this call, false
++ * otherwise
++ */
++ public boolean removeEnchant(@NotNull Enchantment ench) {
++ ItemMeta itemMeta = getItemMeta();
++ boolean result = itemMeta.removeEnchant(ench);
++ setItemMeta(itemMeta);
++ return result;
++ }
++
++ /**
++ * Checks for the existence of any enchantments.
++ *
++ * @return true if an enchantment exists on this meta
++ */
++ public boolean hasEnchants() {
++ return getItemMeta().hasEnchants();
++ }
++
++ /**
++ * Checks if the specified enchantment conflicts with any enchantments in
++ * this ItemMeta.
++ *
++ * @param ench enchantment to test
++ * @return true if the enchantment conflicts, false otherwise
++ */
++ public boolean hasConflictingEnchant(@NotNull Enchantment ench) {
++ return getItemMeta().hasConflictingEnchant(ench);
++ }
++
++ /**
++ * Sets the custom model data.
++ *
++ * CustomModelData is an integer that may be associated client side with a
++ * custom item model.
++ *
++ * @param data the data to set, or null to clear
++ */
++ public void setCustomModelData(@Nullable Integer data) {
++ ItemMeta itemMeta = getItemMeta();
++ itemMeta.setCustomModelData(data);
++ setItemMeta(itemMeta);
++ }
++
++ /**
++ * Gets the custom model data that is set.
++ *
++ * CustomModelData is an integer that may be associated client side with a
++ * custom item model.
++ *
++ * Plugins should check that hasCustomModelData() returns true
++ * before calling this method.
++ *
++ * @return the localized name that is set
++ */
++ public int getCustomModelData() {
++ return getItemMeta().getCustomModelData();
++ }
++
++ /**
++ * Checks for existence of custom model data.
++ *
++ * CustomModelData is an integer that may be associated client side with a
++ * custom item model.
++ *
++ * @return true if this has custom model data
++ */
++ public boolean hasCustomModelData() {
++ return getItemMeta().hasCustomModelData();
++ }
++
++ /**
++ * Returns whether the item has block data currently attached to it.
++ *
++ * @return whether block data is already attached
++ */
++ public boolean hasBlockData() {
++ return ((BlockDataMeta) getItemMeta()).hasBlockData();
++ }
++
++ /**
++ * Returns the currently attached block data for this item or creates a new
++ * one if one doesn't exist.
++ *
++ * The state is a copy, it must be set back (or to another item) with
++ * {@link #setBlockData(BlockData)}
++ *
++ * @param material the material we wish to get this data in the context of
++ * @return the attached data or new data
++ */
++ @NotNull
++ public BlockData getBlockData(@NotNull Material material) {
++ return ((BlockDataMeta) getItemMeta()).getBlockData(material);
++ }
++
++ /**
++ * Attaches a copy of the passed block data to the item.
++ *
++ * @param blockData the block data to attach to the block.
++ * @throws IllegalArgumentException if the blockData is null or invalid for
++ * this item.
++ */
++ public void setBlockData(@NotNull BlockData blockData) {
++ ItemMeta itemMeta = getItemMeta();
++ ((BlockDataMeta) itemMeta).setBlockData(blockData);
++ setItemMeta(itemMeta);
++ }
++
++ /**
++ * Gets the repair penalty
++ *
++ * @return the repair penalty
++ */
++ public int getRepairCost() {
++ return ((Repairable) getItemMeta()).getRepairCost();
++ }
++
++ /**
++ * Sets the repair penalty
++ *
++ * @param cost repair penalty
++ */
++ public void setRepairCost(int cost) {
++ ItemMeta itemMeta = getItemMeta();
++ ((Repairable) itemMeta).setRepairCost(cost);
++ setItemMeta(itemMeta);
++ }
++
++ /**
++ * Checks to see if this has a repair penalty
++ *
++ * @return true if this has a repair penalty
++ */
++ public boolean hasRepairCost() {
++ return ((Repairable) getItemMeta()).hasRepairCost();
++ }
++
++ /**
++ * Return if the unbreakable tag is true. An unbreakable item will not lose
++ * durability.
++ *
++ * @return true if the unbreakable tag is true
++ */
++ public boolean isUnbreakable() {
++ return getItemMeta().isUnbreakable();
++ }
++
++ /**
++ * Sets the unbreakable tag. An unbreakable item will not lose durability.
++ *
++ * @param unbreakable true if set unbreakable
++ */
++ public void setUnbreakable(boolean unbreakable) {
++ ItemMeta itemMeta = getItemMeta();
++ itemMeta.setUnbreakable(unbreakable);
++ setItemMeta(itemMeta);
++ }
++
++ /**
++ * Checks for the existence of any AttributeModifiers.
++ *
++ * @return true if any AttributeModifiers exist
++ */
++ public boolean hasAttributeModifiers() {
++ return getItemMeta().hasAttributeModifiers();
++ }
++
++ /**
++ * Return an immutable copy of all Attributes and
++ * their modifiers in this ItemMeta.
++ * Returns null if none exist.
++ *
++ * @return an immutable {@link Multimap} of Attributes
++ * and their AttributeModifiers, or null if none exist
++ */
++ @Nullable
++ public Multimap getAttributeModifiers() {
++ return getItemMeta().getAttributeModifiers();
++ }
++
++ /**
++ * Return an immutable copy of all {@link Attribute}s and their
++ * {@link AttributeModifier}s for a given {@link EquipmentSlot}.
++ * Any {@link AttributeModifier} that does have have a given
++ * {@link EquipmentSlot} will be returned. This is because
++ * AttributeModifiers without a slot are active in any slot.
++ * If there are no attributes set for the given slot, an empty map
++ * will be returned.
++ *
++ * @param slot the {@link EquipmentSlot} to check
++ * @return the immutable {@link Multimap} with the
++ * respective Attributes and modifiers, or an empty map
++ * if no attributes are set.
++ */
++ @NotNull
++ public Multimap getAttributeModifiers(@Nullable EquipmentSlot slot) {
++ return getItemMeta().getAttributeModifiers(slot);
++ }
++
++ /**
++ * Return an immutable copy of all {@link AttributeModifier}s
++ * for a given {@link Attribute}
++ *
++ * @param attribute the {@link Attribute}
++ * @return an immutable collection of {@link AttributeModifier}s
++ * or null if no AttributeModifiers exist for the Attribute.
++ * @throws NullPointerException if Attribute is null
++ */
++ @Nullable
++ public Collection getAttributeModifiers(@NotNull Attribute attribute) {
++ return getItemMeta().getAttributeModifiers(attribute);
++ }
++
++ /**
++ * Add an Attribute and it's Modifier.
++ * AttributeModifiers can now support {@link EquipmentSlot}s.
++ * If not set, the {@link AttributeModifier} will be active in ALL slots.
++ *
++ * Two {@link AttributeModifier}s that have the same {@link java.util.UUID}
++ * cannot exist on the same Attribute.
++ *
++ * @param attribute the {@link Attribute} to modify
++ * @param modifier the {@link AttributeModifier} specifying the modification
++ * @return true if the Attribute and AttributeModifier were
++ * successfully added
++ * @throws NullPointerException if Attribute is null
++ * @throws NullPointerException if AttributeModifier is null
++ * @throws IllegalArgumentException if AttributeModifier already exists
++ */
++ public boolean addAttributeModifier(@NotNull Attribute attribute, @NotNull AttributeModifier modifier) {
++ ItemMeta itemMeta = getItemMeta();
++ boolean result = itemMeta.addAttributeModifier(attribute, modifier);
++ setItemMeta(itemMeta);
++ return result;
++ }
++
++ /**
++ * Set all {@link Attribute}s and their {@link AttributeModifier}s.
++ * To clear all currently set Attributes and AttributeModifiers use
++ * null or an empty Multimap.
++ * If not null nor empty, this will filter all entries that are not-null
++ * and add them to the ItemStack.
++ *
++ * @param attributeModifiers the new Multimap containing the Attributes
++ * and their AttributeModifiers
++ */
++ public void setAttributeModifiers(@Nullable Multimap attributeModifiers) {
++ ItemMeta itemMeta = getItemMeta();
++ itemMeta.setAttributeModifiers(attributeModifiers);
++ setItemMeta(itemMeta);
++ }
++
++ /**
++ * Remove all {@link AttributeModifier}s associated with the given
++ * {@link Attribute}.
++ * This will return false if nothing was removed.
++ *
++ * @param attribute attribute to remove
++ * @return true if all modifiers were removed from a given
++ * Attribute. Returns false if no attributes were
++ * removed.
++ * @throws NullPointerException if Attribute is null
++ */
++ public boolean removeAttributeModifier(@NotNull Attribute attribute) {
++ ItemMeta itemMeta = getItemMeta();
++ boolean result = itemMeta.removeAttributeModifier(attribute);
++ setItemMeta(itemMeta);
++ return result;
++ }
++
++ /**
++ * Remove all {@link Attribute}s and {@link AttributeModifier}s for a
++ * given {@link EquipmentSlot}.
++ * If the given {@link EquipmentSlot} is null, this will remove all
++ * {@link AttributeModifier}s that do not have an EquipmentSlot set.
++ *
++ * @param slot the {@link EquipmentSlot} to clear all Attributes and
++ * their modifiers for
++ * @return true if all modifiers were removed that match the given
++ * EquipmentSlot.
++ */
++ public boolean removeAttributeModifier(@Nullable EquipmentSlot slot) {
++ ItemMeta itemMeta = getItemMeta();
++ boolean result = itemMeta.removeAttributeModifier(slot);
++ setItemMeta(itemMeta);
++ return result;
++ }
++
++ /**
++ * Remove a specific {@link Attribute} and {@link AttributeModifier}.
++ * AttributeModifiers are matched according to their {@link java.util.UUID}.
++ *
++ * @param attribute the {@link Attribute} to remove
++ * @param modifier the {@link AttributeModifier} to remove
++ * @return if any attribute modifiers were remove
++ *
++ * @throws NullPointerException if the Attribute is null
++ * @throws NullPointerException if the AttributeModifier is null
++ *
++ * @see AttributeModifier#getUniqueId()
++ */
++ public boolean removeAttributeModifier(@NotNull Attribute attribute, @NotNull AttributeModifier modifier) {
++ ItemMeta itemMeta = getItemMeta();
++ boolean result = itemMeta.removeAttributeModifier(attribute, modifier);
++ setItemMeta(itemMeta);
++ return result;
++ }
++
++ /**
++ * Returns a custom tag container capable of storing tags on the object.
++ *
++ * Note that the tags stored on this container are all stored under their
++ * own custom namespace therefore modifying default tags using this
++ * {@link PersistentDataHolder} is impossible.
++ *
++ * @return the persistent metadata container
++ */
++ @NotNull
++ public PersistentDataContainer getPersistentDataContainer() {
++ return getItemMeta().getPersistentDataContainer();
++ }
++
++ /**
++ * Checks to see if this item has damage
++ *
++ * @return true if this has damage
++ */
++ public boolean hasDamage() {
++ return ((Damageable) getItemMeta()).hasDamage();
++ }
++
++ /**
++ * Gets the damage
++ *
++ * @return the damage
++ */
++ public int getDamage() {
++ return ((Damageable) getItemMeta()).getDamage();
++ }
++
++ /**
++ * Sets the damage
++ *
++ * @param damage item damage
++ */
++ public void setDamage(int damage) {
++ ItemMeta itemMeta = getItemMeta();
++ ((Damageable) itemMeta).setDamage(damage);
++ setItemMeta(itemMeta);
++ }
++
++ /**
++ * Gets the collection of namespaced keys that the item can destroy in {@link org.bukkit.GameMode#ADVENTURE}
++ *
++ * @return Set of {@link com.destroystokyo.paper.Namespaced}
++ */
++ @NotNull
++ public Set getDestroyableKeys() {
++ return getItemMeta().getDestroyableKeys();
++ }
++
++ /**
++ * Sets the collection of namespaced keys that the item can destroy in {@link org.bukkit.GameMode#ADVENTURE}
++ *
++ * @param canDestroy Collection of {@link com.destroystokyo.paper.Namespaced}
++ */
++ public void setDestroyableKeys(@NotNull Collection canDestroy) {
++ ItemMeta itemMeta = getItemMeta();
++ itemMeta.setDestroyableKeys(canDestroy);
++ setItemMeta(itemMeta);
++ }
++
++ /**
++ * Gets the collection of namespaced keys that the item can be placed on in {@link org.bukkit.GameMode#ADVENTURE}
++ *
++ * @return Set of {@link com.destroystokyo.paper.Namespaced}
++ */
++ @NotNull
++ public Set getPlaceableKeys() {
++ return getItemMeta().getPlaceableKeys();
++ }
++
++ /**
++ * Sets the set of namespaced keys that the item can be placed on in {@link org.bukkit.GameMode#ADVENTURE}
++ *
++ * @param canPlaceOn Collection of {@link com.destroystokyo.paper.Namespaced}
++ */
++ @NotNull
++ public void setPlaceableKeys(@NotNull Collection canPlaceOn) {
++ ItemMeta itemMeta = getItemMeta();
++ itemMeta.setPlaceableKeys(canPlaceOn);
++ setItemMeta(itemMeta);
++ }
++
++ /**
++ * Checks for the existence of any keys that the item can be placed on
++ *
++ * @return true if this item has placeable keys
++ */
++ public boolean hasPlaceableKeys() {
++ return getItemMeta().hasPlaceableKeys();
++ }
++
++ /**
++ * Checks for the existence of any keys that the item can destroy
++ *
++ * @return true if this item has destroyable keys
++ */
++ public boolean hasDestroyableKeys() {
++ return getItemMeta().hasDestroyableKeys();
++ }
++
++ /**
++ * Repairs this item by 1 durability
++ */
++ public void repair() {
++ repair(1);
++ }
++
++ /**
++ * Damages this item by 1 durability
++ *
++ * @return True if damage broke the item
++ */
++ public boolean damage() {
++ return damage(1);
++ }
++
++ /**
++ * Repairs this item's durability by amount
++ *
++ * @param amount Amount of durability to repair
++ */
++ public void repair(int amount) {
++ damage(-amount);
++ }
++
++ /**
++ * Damages this item's durability by amount
++ *
++ * @param amount Amount of durability to damage
++ * @return True if damage broke the item
++ */
++ public boolean damage(int amount) {
++ return damage(amount, false);
++ }
++
++ /**
++ * Damages this item's durability by amount
++ *
++ * @param amount Amount of durability to damage
++ * @param ignoreUnbreaking Ignores unbreaking enchantment
++ * @return True if damage broke the item
++ */
++ public boolean damage(int amount, boolean ignoreUnbreaking) {
++ Damageable damageable = (Damageable) getItemMeta();
++ if (amount > 0) {
++ int unbreaking = getEnchantLevel(Enchantment.DURABILITY);
++ int reduce = 0;
++ for (int i = 0; unbreaking > 0 && i < amount; ++i) {
++ if (reduceDamage(java.util.concurrent.ThreadLocalRandom.current(), unbreaking)) {
++ ++reduce;
++ }
++ }
++ amount -= reduce;
++ if (amount <= 0) {
++ return isBroke(damageable.getDamage());
++ }
++ }
++ int damage = damageable.getDamage() + amount;
++ damageable.setDamage(damage);
++ setItemMeta((ItemMeta) damageable);
++ return isBroke(damage);
++ }
++
++ public boolean isBroke(int damage) {
++ if (damage > getType().getMaxDurability()) {
++ if (getAmount() > 0) {
++ // ensure it "breaks"
++ setAmount(0);
++ }
++ return true;
++ }
++ return false;
++ }
++
++ private boolean reduceDamage(java.util.Random random, int unbreaking) {
++ if (getType().isArmor()) {
++ return random.nextFloat() < 0.6F;
++ }
++ return random.nextInt(unbreaking + 1) > 0;
++ }
++
++ // Purpur end
+ }
diff --git a/patches/Purpur/patches/api/0020-Phantoms-attracted-to-crystals-and-crystals-shoot-ph.patch b/patches/Purpur/patches/api/0020-Phantoms-attracted-to-crystals-and-crystals-shoot-ph.patch
new file mode 100644
index 00000000..bdaed9c0
--- /dev/null
+++ b/patches/Purpur/patches/api/0020-Phantoms-attracted-to-crystals-and-crystals-shoot-ph.patch
@@ -0,0 +1,19 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sun, 28 Jun 2020 21:50:55 -0500
+Subject: [PATCH] Phantoms attracted to crystals and crystals shoot phantoms
+
+
+diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java b/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java
+index b42091752981a1f309ab350e9a394092cb334824..83c51bb5e09549a8205d27a53ff0102f9439d60a 100644
+--- a/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java
++++ b/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java
+@@ -206,4 +206,8 @@ public interface VanillaGoal extends Goal {
+ GoalKey ZOMBIE_ATTACK = GoalKey.of(Zombie.class, NamespacedKey.minecraft("zombie_attack"));
+ GoalKey STROLL_VILLAGE_GOLEM = GoalKey.of(Creature.class, NamespacedKey.minecraft("stroll_village_golem"));
+ GoalKey UNIVERSAL_ANGER_RESET = GoalKey.of(Mob.class, NamespacedKey.minecraft("universal_anger_reset"));
++ // Purpur start
++ GoalKey FIND_CRYSTAL_GOAL = GoalKey.of(Phantom.class, NamespacedKey.minecraft("find_crystal_goal"));
++ GoalKey ORBIT_CRYSTAL_GOAL = GoalKey.of(Phantom.class, NamespacedKey.minecraft("orbit_crystal_goal"));
++ // Purpur end
+ }
diff --git a/patches/Purpur/patches/api/0021-ChatColor-conveniences.patch b/patches/Purpur/patches/api/0021-ChatColor-conveniences.patch
new file mode 100644
index 00000000..9c0f19ba
--- /dev/null
+++ b/patches/Purpur/patches/api/0021-ChatColor-conveniences.patch
@@ -0,0 +1,41 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Fri, 10 Jul 2020 12:43:25 -0500
+Subject: [PATCH] ChatColor conveniences
+
+
+diff --git a/src/main/java/org/bukkit/ChatColor.java b/src/main/java/org/bukkit/ChatColor.java
+index 4594701d77c5d0f744bece871b98d9f6f73eb5a7..499b222dee1f11d497a29a9a263a5596401ca1eb 100644
+--- a/src/main/java/org/bukkit/ChatColor.java
++++ b/src/main/java/org/bukkit/ChatColor.java
+@@ -413,4 +413,30 @@ public enum ChatColor {
+ BY_CHAR.put(color.code, color);
+ }
+ }
++
++ // Purpur start
++ public static final Pattern HEX_PATTERN = Pattern.compile("(#[A-Fa-f0-9]{6})");
++
++ @Nullable
++ public static String replaceHex(@Nullable String str) {
++ if (str != null) {
++ java.util.regex.Matcher matcher = HEX_PATTERN.matcher(str);
++ while (matcher.find()) {
++ String group = matcher.group(1);
++ str = str.replace(group, net.md_5.bungee.api.ChatColor.of(group).toString());
++ }
++ }
++ return str;
++ }
++
++ @Nullable
++ public static String color(@Nullable String str) {
++ return color(str, true);
++ }
++
++ @Nullable
++ public static String color(@Nullable String str, boolean parseHex) {
++ return str != null ? net.md_5.bungee.api.ChatColor.translateAlternateColorCodes('&', parseHex ? replaceHex(str) : str) : str;
++ }
++ // Purpur end
+ }
diff --git a/patches/Purpur/patches/api/0022-DragonEggPlaceEvent.patch b/patches/Purpur/patches/api/0022-DragonEggPlaceEvent.patch
new file mode 100644
index 00000000..562967ff
--- /dev/null
+++ b/patches/Purpur/patches/api/0022-DragonEggPlaceEvent.patch
@@ -0,0 +1,59 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Thu, 30 Jul 2020 18:15:04 -0500
+Subject: [PATCH] DragonEggPlaceEvent
+
+
+diff --git a/src/main/java/net/pl3x/purpur/event/block/DragonEggPlaceEvent.java b/src/main/java/net/pl3x/purpur/event/block/DragonEggPlaceEvent.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..bdabfd2b5f64b0e65c4eb09958282962620cdda2
+--- /dev/null
++++ b/src/main/java/net/pl3x/purpur/event/block/DragonEggPlaceEvent.java
+@@ -0,0 +1,47 @@
++package net.pl3x.purpur.event.block;
++
++import org.bukkit.Location;
++import org.bukkit.event.Cancellable;
++import org.bukkit.event.Event;
++import org.bukkit.event.HandlerList;
++import org.jetbrains.annotations.NotNull;
++
++public class DragonEggPlaceEvent extends Event implements Cancellable {
++ private static final HandlerList handlers = new HandlerList();
++ private Location location;
++ private boolean cancelled;
++
++ public DragonEggPlaceEvent(@NotNull Location location) {
++ this.location = location;
++ }
++
++ @NotNull
++ public Location getLocation() {
++ return location;
++ }
++
++ public void setLocation(@NotNull Location location) {
++ this.location = location;
++ }
++
++ @Override
++ public boolean isCancelled() {
++ return cancelled;
++ }
++
++ @Override
++ public void setCancelled(boolean cancel) {
++ cancelled = cancel;
++ }
++
++ @Override
++ @NotNull
++ public HandlerList getHandlers() {
++ return handlers;
++ }
++
++ @NotNull
++ public static HandlerList getHandlerList() {
++ return handlers;
++ }
++}
diff --git a/patches/Purpur/patches/api/0023-Ridables.patch b/patches/Purpur/patches/api/0023-Ridables.patch
new file mode 100644
index 00000000..fc251fc3
--- /dev/null
+++ b/patches/Purpur/patches/api/0023-Ridables.patch
@@ -0,0 +1,210 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sat, 4 May 2019 00:57:16 -0500
+Subject: [PATCH] Ridables
+
+
+diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java b/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java
+index 83c51bb5e09549a8205d27a53ff0102f9439d60a..177143c9764e82981423879ed35625edd25d3ebf 100644
+--- a/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java
++++ b/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java
+@@ -209,5 +209,7 @@ public interface VanillaGoal extends Goal {
+ // Purpur start
+ GoalKey FIND_CRYSTAL_GOAL = GoalKey.of(Phantom.class, NamespacedKey.minecraft("find_crystal_goal"));
+ GoalKey ORBIT_CRYSTAL_GOAL = GoalKey.of(Phantom.class, NamespacedKey.minecraft("orbit_crystal_goal"));
++ GoalKey HAS_RIDER = GoalKey.of(Mob.class, NamespacedKey.minecraft("has_rider"));
++ GoalKey HORSE_HAS_RIDER = GoalKey.of(AbstractHorse.class, NamespacedKey.minecraft("horse_has_rider"));
+ // Purpur end
+ }
+diff --git a/src/main/java/net/pl3x/purpur/event/entity/RidableMoveEvent.java b/src/main/java/net/pl3x/purpur/event/entity/RidableMoveEvent.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..48e7ac392fe5efac8a4ce549e31a05ed817417e4
+--- /dev/null
++++ b/src/main/java/net/pl3x/purpur/event/entity/RidableMoveEvent.java
+@@ -0,0 +1,103 @@
++package net.pl3x.purpur.event.entity;
++
++import com.google.common.base.Preconditions;
++import org.bukkit.Location;
++import org.bukkit.entity.Mob;
++import org.bukkit.entity.Player;
++import org.bukkit.event.Cancellable;
++import org.bukkit.event.HandlerList;
++import org.bukkit.event.entity.EntityEvent;
++import org.jetbrains.annotations.NotNull;
++
++/**
++ * Triggered when a ridable mob moves with a rider
++ */
++public class RidableMoveEvent extends EntityEvent implements Cancellable {
++ private static final HandlerList handlers = new HandlerList();
++ private boolean canceled;
++ private final Player rider;
++ private Location from;
++ private Location to;
++
++ public RidableMoveEvent(@NotNull Mob entity, @NotNull Player rider, @NotNull Location from, @NotNull Location to) {
++ super(entity);
++ this.rider = rider;
++ this.from = from;
++ this.to = to;
++ }
++
++ @Override
++ @NotNull
++ public Mob getEntity() {
++ return (Mob) entity;
++ }
++
++ @NotNull
++ public Player getRider() {
++ return rider;
++ }
++
++ public boolean isCancelled() {
++ return canceled;
++ }
++
++ public void setCancelled(boolean cancel) {
++ canceled = cancel;
++ }
++
++ /**
++ * Gets the location this entity moved from
++ *
++ * @return Location the entity moved from
++ */
++ @NotNull
++ public Location getFrom() {
++ return from;
++ }
++
++ /**
++ * Sets the location to mark as where the entity moved from
++ *
++ * @param from New location to mark as the entity's previous location
++ */
++ public void setFrom(@NotNull Location from) {
++ validateLocation(from);
++ this.from = from;
++ }
++
++ /**
++ * Gets the location this entity moved to
++ *
++ * @return Location the entity moved to
++ */
++ @NotNull
++ public Location getTo() {
++ return to;
++ }
++
++ /**
++ * Sets the location that this entity will move to
++ *
++ * @param to New Location this entity will move to
++ */
++ public void setTo(@NotNull Location to) {
++ validateLocation(to);
++ this.to = to;
++ }
++
++ private void validateLocation(@NotNull Location loc) {
++ Preconditions.checkArgument(loc != null, "Cannot use null location!");
++ Preconditions.checkArgument(loc.getWorld() != null, "Cannot use null location with null world!");
++ }
++
++ @Override
++ @NotNull
++ public HandlerList getHandlers() {
++ return handlers;
++ }
++
++ @NotNull
++ public static HandlerList getHandlerList() {
++ return handlers;
++ }
++}
+diff --git a/src/main/java/net/pl3x/purpur/event/entity/RidableSpacebarEvent.java b/src/main/java/net/pl3x/purpur/event/entity/RidableSpacebarEvent.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..c0ec5a130985e8da4cc9e596a6b70503d2550f77
+--- /dev/null
++++ b/src/main/java/net/pl3x/purpur/event/entity/RidableSpacebarEvent.java
+@@ -0,0 +1,37 @@
++package net.pl3x.purpur.event.entity;
++
++import org.bukkit.entity.Entity;
++import org.bukkit.event.Cancellable;
++import org.bukkit.event.HandlerList;
++import org.bukkit.event.entity.EntityEvent;
++import org.jetbrains.annotations.NotNull;
++
++public class RidableSpacebarEvent extends EntityEvent implements Cancellable {
++ private static final HandlerList handlers = new HandlerList();
++ private boolean cancelled;
++
++ public RidableSpacebarEvent(@NotNull Entity entity) {
++ super(entity);
++ }
++
++ @Override
++ public boolean isCancelled() {
++ return cancelled;
++ }
++
++ @Override
++ public void setCancelled(boolean cancel) {
++ cancelled = cancel;
++ }
++
++ @Override
++ @NotNull
++ public HandlerList getHandlers() {
++ return handlers;
++ }
++
++ @NotNull
++ public static HandlerList getHandlerList() {
++ return handlers;
++ }
++}
+diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java
+index 76e857c364fe79e20cf9bde54b65e5b7108174fd..5f7947cd6f3bf9f76e8b3bac339f61b9afadaaad 100644
+--- a/src/main/java/org/bukkit/entity/Entity.java
++++ b/src/main/java/org/bukkit/entity/Entity.java
+@@ -698,4 +698,35 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent
+ */
+ public boolean isTicking();
+ // Paper end
++
++ // Purpur start
++ /**
++ * Get the riding player
++ *
++ * @return Riding player
++ */
++ @Nullable
++ Player getRider();
++
++ /**
++ * Check if entity is being ridden
++ *
++ * @return True if being ridden
++ */
++ boolean hasRider();
++
++ /**
++ * Check if entity is ridable
++ *
++ * @return True if ridable
++ */
++ boolean isRidable();
++
++ /**
++ * Check if entity is ridable in water
++ *
++ * @return True if ridable in water
++ */
++ boolean isRidableInWater();
++ // Purpur end
+ }
diff --git a/patches/Purpur/patches/api/0024-Configurable-permission-message-upgrades.patch b/patches/Purpur/patches/api/0024-Configurable-permission-message-upgrades.patch
new file mode 100644
index 00000000..bda8c7b7
--- /dev/null
+++ b/patches/Purpur/patches/api/0024-Configurable-permission-message-upgrades.patch
@@ -0,0 +1,27 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sat, 15 Aug 2020 11:18:27 -0500
+Subject: [PATCH] Configurable permission message upgrades
+
+This allows the configurable permission message in paper.yml to be blank and also support newlines
+
+diff --git a/src/main/java/org/bukkit/command/Command.java b/src/main/java/org/bukkit/command/Command.java
+index c10fc8d2386301bc2caddcdb1cd18566bcaa8689..882c565ac2be3df976e7bbeb4dc80c9ac474a8b1 100644
+--- a/src/main/java/org/bukkit/command/Command.java
++++ b/src/main/java/org/bukkit/command/Command.java
+@@ -184,9 +184,13 @@ public abstract class Command {
+ return true;
+ }
+
++ // Purpur start
++ String permissionMessage = this.permissionMessage;
+ if (permissionMessage == null) {
+- target.sendMessage(Bukkit.getPermissionMessage()); // Paper
+- } else if (permissionMessage.length() != 0) {
++ permissionMessage = Bukkit.getPermissionMessage();
++ }
++ if (permissionMessage.length() != 0) {
++ // Purpur end
+ for (String line : permissionMessage.replace("", permission).split("\n")) {
+ target.sendMessage(line);
+ }
diff --git a/patches/Purpur/patches/api/0025-LivingEntity-broadcastItemBreak.patch b/patches/Purpur/patches/api/0025-LivingEntity-broadcastItemBreak.patch
new file mode 100644
index 00000000..0a327c0a
--- /dev/null
+++ b/patches/Purpur/patches/api/0025-LivingEntity-broadcastItemBreak.patch
@@ -0,0 +1,23 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Mon, 17 Aug 2020 21:50:32 -0500
+Subject: [PATCH] LivingEntity#broadcastItemBreak
+
+
+diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java
+index 4ccbb3ef3c597ef9da2c6744f410283a1dc2538c..42811d18ff304082f74f45794344891208599c04 100644
+--- a/src/main/java/org/bukkit/entity/LivingEntity.java
++++ b/src/main/java/org/bukkit/entity/LivingEntity.java
+@@ -865,5 +865,12 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource
+ * @param safeFallDistance Safe fall distance
+ */
+ void setSafeFallDistance(float safeFallDistance);
++
++ /**
++ * Play item break animation for the item in specified equipment slot
++ *
++ * @param slot Equipment slot to play break animation for
++ */
++ void broadcastItemBreak(@NotNull org.bukkit.inventory.EquipmentSlot slot);
+ // Purpur end
+ }
diff --git a/patches/Purpur/patches/api/0026-Item-entity-immunities.patch b/patches/Purpur/patches/api/0026-Item-entity-immunities.patch
new file mode 100644
index 00000000..7356fb0b
--- /dev/null
+++ b/patches/Purpur/patches/api/0026-Item-entity-immunities.patch
@@ -0,0 +1,59 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sat, 22 Aug 2020 17:42:08 -0500
+Subject: [PATCH] Item entity immunities
+
+
+diff --git a/src/main/java/org/bukkit/entity/Item.java b/src/main/java/org/bukkit/entity/Item.java
+index 0ee072645ecf1bf5feb74de6960947ef76db366e..69dae9157053c521a9e2bbdd7f89c17fc8d24840 100644
+--- a/src/main/java/org/bukkit/entity/Item.java
++++ b/src/main/java/org/bukkit/entity/Item.java
+@@ -120,4 +120,48 @@ public interface Item extends Entity {
+ */
+ public void setWillAge(boolean willAge);
+ // Paper end
++
++ // Purpur start
++ /**
++ * Set whether or not this item is immune to cactus
++ *
++ * @param immuneToCactus True to make immune to cactus
++ */
++ void setImmuneToCactus(boolean immuneToCactus);
++
++ /**
++ * Check if item is immune to cactus
++ *
++ * @return True if immune to cactus
++ */
++ boolean isImmuneToCactus();
++
++ /**
++ * Set whether or not this item is immune to explosions
++ *
++ * @param immuneToExplosion True to make immune to explosions
++ */
++ void setImmuneToExplosion(boolean immuneToExplosion);
++
++ /**
++ * Check if item is immune to explosions
++ *
++ * @return True if immune to explosions
++ */
++ boolean isImmuneToExplosion();
++
++ /**
++ * Set whether or not this item is immune to fire
++ *
++ * @param immuneToFire True to make immune to fire
++ */
++ void setImmuneToFire(boolean immuneToFire);
++
++ /**
++ * Check if item is immune to fire
++ *
++ * @return True if immune to fire
++ */
++ boolean isImmuneToFire();
++ // Purpur end
+ }
diff --git a/patches/Purpur/patches/api/0027-Spigot-Improve-output-of-plugins-command.patch b/patches/Purpur/patches/api/0027-Spigot-Improve-output-of-plugins-command.patch
new file mode 100644
index 00000000..bb371528
--- /dev/null
+++ b/patches/Purpur/patches/api/0027-Spigot-Improve-output-of-plugins-command.patch
@@ -0,0 +1,114 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Parker Hawke
+Date: Sat, 27 Jun 2020 18:43:37 -0400
+Subject: [PATCH] Spigot - Improve output of plugins command
+
+
+diff --git a/src/main/java/org/bukkit/command/defaults/PluginsCommand.java b/src/main/java/org/bukkit/command/defaults/PluginsCommand.java
+index 1aa58c59e1e8738bbdc77752885ff3b18b29de42..4974fc518c3645e6e060ff52e71a47a86d52ec5c 100644
+--- a/src/main/java/org/bukkit/command/defaults/PluginsCommand.java
++++ b/src/main/java/org/bukkit/command/defaults/PluginsCommand.java
+@@ -11,6 +11,15 @@ import org.bukkit.ChatColor;
+ import org.bukkit.command.CommandSender;
+ import org.bukkit.plugin.Plugin;
+ import org.jetbrains.annotations.NotNull;
++// Spigot start
++import net.md_5.bungee.api.chat.BaseComponent;
++import net.md_5.bungee.api.chat.ClickEvent;
++import net.md_5.bungee.api.chat.ComponentBuilder;
++import net.md_5.bungee.api.chat.HoverEvent;
++import net.md_5.bungee.api.chat.ComponentBuilder.FormatRetention;
++import org.bukkit.entity.Player;
++import org.bukkit.plugin.PluginDescriptionFile;
++// Spigot end
+
+ public class PluginsCommand extends BukkitCommand {
+ public PluginsCommand(@NotNull String name) {
+@@ -25,7 +34,13 @@ public class PluginsCommand extends BukkitCommand {
+ public boolean execute(@NotNull CommandSender sender, @NotNull String currentAlias, @NotNull String[] args) {
+ if (!testPermission(sender)) return true;
+
+- sender.sendMessage("Plugins " + getPluginList());
++ // Spigot start
++ if (sender instanceof Player && sender.hasPermission("bukkit.command.version")) {
++ sender.spigot().sendMessage(getPluginListSpigot());
++ } else {
++ sender.sendMessage("Plugins " + getPluginList());
++ }
++ // Spigot end
+ return true;
+ }
+
+@@ -71,4 +86,72 @@ public class PluginsCommand extends BukkitCommand {
+ // Paper end
+ }
+
++ // Spigot start
++ @NotNull
++ private BaseComponent[] getPluginListSpigot() {
++ Plugin[] plugins = Bukkit.getPluginManager().getPlugins();
++ ComponentBuilder pluginList = new ComponentBuilder("Plugins (" + plugins.length + "): ");
++
++ int index = 0;
++ for (Plugin plugin : plugins) {
++ if (index++ > 0) {
++ pluginList.append(", ", FormatRetention.NONE).color(net.md_5.bungee.api.ChatColor.WHITE);
++ }
++
++ // Event components
++ PluginDescriptionFile description = plugin.getDescription();
++ ComponentBuilder hoverEventComponents = new ComponentBuilder();
++ hoverEventComponents.append("Version: ").color(net.md_5.bungee.api.ChatColor.WHITE).append(description.getVersion()).color(net.md_5.bungee.api.ChatColor.GREEN);
++
++ if (description.getDescription() != null) {
++ hoverEventComponents.append("\nDescription: ").color(net.md_5.bungee.api.ChatColor.WHITE).append(description.getDescription()).color(net.md_5.bungee.api.ChatColor.GREEN);
++ }
++
++ if (description.getWebsite() != null) {
++ hoverEventComponents.append("\nWebsite: ").color(net.md_5.bungee.api.ChatColor.WHITE).append(description.getWebsite()).color(net.md_5.bungee.api.ChatColor.GREEN);
++ }
++
++ if (!description.getAuthors().isEmpty()) {
++ if (description.getAuthors().size() == 1) {
++ hoverEventComponents.append("\nAuthor: ");
++ } else {
++ hoverEventComponents.append("\nAuthors: ");
++ }
++
++ hoverEventComponents.color(net.md_5.bungee.api.ChatColor.WHITE).append(getAuthors(description));
++ }
++
++ HoverEvent hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_TEXT, hoverEventComponents.create());
++ ClickEvent clickEvent = new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/version " + description.getName());
++
++ // Plugin list entry
++ pluginList.append(plugin.getDescription().getName());
++ pluginList.color(plugin.isEnabled() ? net.md_5.bungee.api.ChatColor.GREEN : net.md_5.bungee.api.ChatColor.RED);
++ pluginList.event(hoverEvent).event(clickEvent);
++
++ if (plugin.getDescription().getProvides().size() > 0) {
++ pluginList.append("( ", FormatRetention.NONE).color(net.md_5.bungee.api.ChatColor.WHITE).append(String.join(", ", plugin.getDescription().getProvides())).append(")");
++ }
++ }
++
++ return pluginList.create();
++ }
++
++ @NotNull
++ private BaseComponent[] getAuthors(@NotNull final PluginDescriptionFile description) {
++ ComponentBuilder result = new ComponentBuilder();
++ List authors = description.getAuthors();
++
++ for (int i = 0; i < authors.size(); i++) {
++ if (i > 0) {
++ result.append(i < authors.size() - 1 ? ", " : " and ", FormatRetention.NONE);
++ result.color(net.md_5.bungee.api.ChatColor.WHITE);
++ }
++
++ result.append(authors.get(i)).color(net.md_5.bungee.api.ChatColor.GREEN);
++ }
++
++ return result.create();
++ }
++ // Spigot end
+ }
diff --git a/patches/Purpur/patches/api/0028-Add-option-to-disable-zombie-aggressiveness-towards-.patch b/patches/Purpur/patches/api/0028-Add-option-to-disable-zombie-aggressiveness-towards-.patch
new file mode 100644
index 00000000..45b72c6b
--- /dev/null
+++ b/patches/Purpur/patches/api/0028-Add-option-to-disable-zombie-aggressiveness-towards-.patch
@@ -0,0 +1,19 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: nitricspace
+Date: Wed, 23 Sep 2020 22:14:38 +0100
+Subject: [PATCH] Add option to disable zombie aggressiveness towards villagers
+ when lagging
+
+
+diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java b/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java
+index 177143c9764e82981423879ed35625edd25d3ebf..da638f9745aceebe4f2ca90823308c6879c75ae7 100644
+--- a/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java
++++ b/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java
+@@ -211,5 +211,7 @@ public interface VanillaGoal extends Goal {
+ GoalKey ORBIT_CRYSTAL_GOAL = GoalKey.of(Phantom.class, NamespacedKey.minecraft("orbit_crystal_goal"));
+ GoalKey HAS_RIDER = GoalKey.of(Mob.class, NamespacedKey.minecraft("has_rider"));
+ GoalKey HORSE_HAS_RIDER = GoalKey.of(AbstractHorse.class, NamespacedKey.minecraft("horse_has_rider"));
++ GoalKey DROWNED_ATTACK_VILLAGER = GoalKey.of(Drowned.class, NamespacedKey.minecraft("drowned_attack_villager"));
++ GoalKey ZOMBIE_ATTACK_VILLAGER = GoalKey.of(Zombie.class, NamespacedKey.minecraft("zombie_attack_villager"));
+ // Purpur end
+ }
diff --git a/patches/Purpur/patches/api/0029-Add-predicate-to-recipe-s-ExactChoice-ingredient.patch b/patches/Purpur/patches/api/0029-Add-predicate-to-recipe-s-ExactChoice-ingredient.patch
new file mode 100644
index 00000000..008ba61e
--- /dev/null
+++ b/patches/Purpur/patches/api/0029-Add-predicate-to-recipe-s-ExactChoice-ingredient.patch
@@ -0,0 +1,52 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Fri, 2 Oct 2020 17:43:24 -0500
+Subject: [PATCH] Add predicate to recipe's ExactChoice ingredient
+
+
+diff --git a/src/main/java/org/bukkit/inventory/RecipeChoice.java b/src/main/java/org/bukkit/inventory/RecipeChoice.java
+index 3d325cab6b106ce8617e321d7a733eca91ba93e5..4dedbdc1cc8b34b73a1a32b35d1985284da6fc08 100644
+--- a/src/main/java/org/bukkit/inventory/RecipeChoice.java
++++ b/src/main/java/org/bukkit/inventory/RecipeChoice.java
+@@ -10,6 +10,7 @@ import java.util.function.Predicate;
+ import org.bukkit.Material;
+ import org.bukkit.Tag;
+ import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Nullable; // Purpur
+
+ /**
+ * Represents a potential item match within a recipe. All choices within a
+@@ -155,6 +156,7 @@ public interface RecipeChoice extends Predicate, Cloneable {
+ public static class ExactChoice implements RecipeChoice {
+
+ private List choices;
++ private Predicate predicate; // Purpur
+
+ public ExactChoice(@NotNull ItemStack stack) {
+ this(Arrays.asList(stack));
+@@ -199,6 +201,7 @@ public interface RecipeChoice extends Predicate, Cloneable {
+
+ @Override
+ public boolean test(@NotNull ItemStack t) {
++ if (predicate != null) return predicate.test(t); // Purpur
+ for (ItemStack match : choices) {
+ if (t.isSimilar(match)) {
+ return true;
+@@ -208,6 +211,17 @@ public interface RecipeChoice extends Predicate, Cloneable {
+ return false;
+ }
+
++ // Purpur start
++ @Nullable
++ public Predicate getPredicate() {
++ return predicate;
++ }
++
++ public void setPredicate(@Nullable Predicate predicate) {
++ this.predicate = predicate;
++ }
++ // Purpur end
++
+ @Override
+ public int hashCode() {
+ int hash = 7;
diff --git a/patches/Purpur/patches/api/0030-Add-critical-hit-check-to-EntityDamagedByEntityEvent.patch b/patches/Purpur/patches/api/0030-Add-critical-hit-check-to-EntityDamagedByEntityEvent.patch
new file mode 100644
index 00000000..ebfe2021
--- /dev/null
+++ b/patches/Purpur/patches/api/0030-Add-critical-hit-check-to-EntityDamagedByEntityEvent.patch
@@ -0,0 +1,56 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: BillyGalbreath
+Date: Tue, 24 Nov 2020 04:30:34 -0600
+Subject: [PATCH] Add critical hit check to EntityDamagedByEntityEvent
+
+
+diff --git a/src/main/java/org/bukkit/event/entity/EntityDamageByEntityEvent.java b/src/main/java/org/bukkit/event/entity/EntityDamageByEntityEvent.java
+index 869bad7405ec7fa67728e90d8b9f2e11b542611f..05fde759bbdf6068f140b4428bbcb355e22d6b28 100644
+--- a/src/main/java/org/bukkit/event/entity/EntityDamageByEntityEvent.java
++++ b/src/main/java/org/bukkit/event/entity/EntityDamageByEntityEvent.java
+@@ -10,15 +10,28 @@ import org.jetbrains.annotations.NotNull;
+ */
+ public class EntityDamageByEntityEvent extends EntityDamageEvent {
+ private final Entity damager;
++ private final boolean isCritical; // Purpur
+
+ public EntityDamageByEntityEvent(@NotNull final Entity damager, @NotNull final Entity damagee, @NotNull final DamageCause cause, final double damage) {
++ // Purpur start
++ this(damager, damagee, cause, damage, false);
++ }
++ public EntityDamageByEntityEvent(@NotNull final Entity damager, @NotNull final Entity damagee, @NotNull final DamageCause cause, final double damage, boolean isCritical) {
++ // Purpur end
+ super(damagee, cause, damage);
+ this.damager = damager;
++ this.isCritical = isCritical; // Purpur
+ }
+
+ public EntityDamageByEntityEvent(@NotNull final Entity damager, @NotNull final Entity damagee, @NotNull final DamageCause cause, @NotNull final Map modifiers, @NotNull final Map> modifierFunctions) {
++ // Purpur start
++ this(damager, damagee, cause, modifiers, modifierFunctions, false);
++ }
++ public EntityDamageByEntityEvent(@NotNull final Entity damager, @NotNull final Entity damagee, @NotNull final DamageCause cause, @NotNull final Map modifiers, @NotNull final Map> modifierFunctions, boolean isCritical) {
++ // Purpur end
+ super(damagee, cause, modifiers, modifierFunctions);
+ this.damager = damager;
++ this.isCritical = isCritical; // Purpur
+ }
+
+ /**
+@@ -30,4 +43,16 @@ public class EntityDamageByEntityEvent extends EntityDamageEvent {
+ public Entity getDamager() {
+ return damager;
+ }
++
++ // Purpur start
++
++ /**
++ * Whether this damage was done by a critical hit
++ *
++ * @return True if critical hit
++ */
++ public boolean isCritical() {
++ return this.isCritical;
++ }
++ // Purpur end
+ }
diff --git a/patches/Purpur/patches/api/0031-Left-handed-API.patch b/patches/Purpur/patches/api/0031-Left-handed-API.patch
new file mode 100644
index 00000000..f5bb5e3c
--- /dev/null
+++ b/patches/Purpur/patches/api/0031-Left-handed-API.patch
@@ -0,0 +1,31 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: BillyGalbreath
+Date: Mon, 30 Nov 2020 06:02:54 -0600
+Subject: [PATCH] Left handed API
+
+
+diff --git a/src/main/java/org/bukkit/entity/Mob.java b/src/main/java/org/bukkit/entity/Mob.java
+index d726453c041a980576312b6bee96a07837f37974..9d4eae5cf0bf8d01954db85b431bcdca8490ee8f 100644
+--- a/src/main/java/org/bukkit/entity/Mob.java
++++ b/src/main/java/org/bukkit/entity/Mob.java
+@@ -64,4 +64,20 @@ public interface Mob extends LivingEntity, Lootable {
+ * @return whether the mob is aware
+ */
+ public boolean isAware();
++
++ // Purpur start
++ /**
++ * Check if Mob is left-handed
++ *
++ * @return True if left-handed
++ */
++ public boolean isLeftHanded();
++
++ /**
++ * Set if Mob is left-handed
++ *
++ * @param leftHanded True if left-handed
++ */
++ public void setLeftHanded(boolean leftHanded);
++ // Purpur end
+ }
diff --git a/patches/Purpur/patches/api/0032-Alphabetize-in-game-plugins-list.patch b/patches/Purpur/patches/api/0032-Alphabetize-in-game-plugins-list.patch
new file mode 100644
index 00000000..0e1b879b
--- /dev/null
+++ b/patches/Purpur/patches/api/0032-Alphabetize-in-game-plugins-list.patch
@@ -0,0 +1,27 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: BillyGalbreath
+Date: Tue, 8 Dec 2020 09:48:18 -0600
+Subject: [PATCH] Alphabetize in-game /plugins list
+
+
+diff --git a/src/main/java/org/bukkit/command/defaults/PluginsCommand.java b/src/main/java/org/bukkit/command/defaults/PluginsCommand.java
+index 4974fc518c3645e6e060ff52e71a47a86d52ec5c..37cc5d7e9db89e4ef7ab16da1b159bd19134a4ff 100644
+--- a/src/main/java/org/bukkit/command/defaults/PluginsCommand.java
++++ b/src/main/java/org/bukkit/command/defaults/PluginsCommand.java
+@@ -2,6 +2,7 @@ package org.bukkit.command.defaults;
+
+ import java.util.Arrays;
+ import java.util.Collections;
++import java.util.Comparator;
+ import java.util.List;
+ import java.util.Map;
+ import java.util.TreeMap;
+@@ -89,7 +90,7 @@ public class PluginsCommand extends BukkitCommand {
+ // Spigot start
+ @NotNull
+ private BaseComponent[] getPluginListSpigot() {
+- Plugin[] plugins = Bukkit.getPluginManager().getPlugins();
++ Plugin[] plugins = Arrays.stream(Bukkit.getPluginManager().getPlugins()).sorted(Comparator.comparing(plugin -> plugin.getName().toLowerCase())).toArray(Plugin[]::new); // Purpur
+ ComponentBuilder pluginList = new ComponentBuilder("Plugins (" + plugins.length + "): ");
+
+ int index = 0;
diff --git a/patches/Purpur/patches/api/0033-Rabid-Wolf-API.patch b/patches/Purpur/patches/api/0033-Rabid-Wolf-API.patch
new file mode 100644
index 00000000..a3c3a541
--- /dev/null
+++ b/patches/Purpur/patches/api/0033-Rabid-Wolf-API.patch
@@ -0,0 +1,42 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Encode42
+Date: Tue, 8 Dec 2020 17:15:15 -0500
+Subject: [PATCH] Rabid Wolf API
+
+
+diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java b/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java
+index da638f9745aceebe4f2ca90823308c6879c75ae7..39f77041133228c4bd4cec2427ad0bae8e739d4a 100644
+--- a/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java
++++ b/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java
+@@ -213,5 +213,6 @@ public interface VanillaGoal extends Goal {
+ GoalKey HORSE_HAS_RIDER = GoalKey.of(AbstractHorse.class, NamespacedKey.minecraft("horse_has_rider"));
+ GoalKey DROWNED_ATTACK_VILLAGER = GoalKey.of(Drowned.class, NamespacedKey.minecraft("drowned_attack_villager"));
+ GoalKey ZOMBIE_ATTACK_VILLAGER = GoalKey.of(Zombie.class, NamespacedKey.minecraft("zombie_attack_villager"));
++ GoalKey AVOID_RABID_WOLVES = GoalKey.of(Wolf.class, NamespacedKey.minecraft("avoid_rabid_wolves"));
+ // Purpur end
+ }
+diff --git a/src/main/java/org/bukkit/entity/Wolf.java b/src/main/java/org/bukkit/entity/Wolf.java
+index 0e5decadf31140d6cb121c298f935ccc12c7a7e7..c1fd30fe4cd4eec11eb8298f059d14584b7dd7ec 100644
+--- a/src/main/java/org/bukkit/entity/Wolf.java
++++ b/src/main/java/org/bukkit/entity/Wolf.java
+@@ -39,4 +39,20 @@ public interface Wolf extends Tameable, Sittable {
+ * @param color the color to apply
+ */
+ public void setCollarColor(@NotNull DyeColor color);
++
++ // Purpur start
++ /**
++ * Checks if this wolf is rabid
++ *
++ * @return whether the wolf is rabid
++ */
++ public boolean isRabid();
++
++ /**
++ * Sets this wolf to be rabid or not
++ *
++ * @param rabid whether the wolf should be rabid
++ */
++ public void setRabid(boolean rabid);
++ // Purpur end
+ }
diff --git a/patches/Purpur/patches/api/0034-Fix-javadoc-warnings-missing-param-and-return.patch b/patches/Purpur/patches/api/0034-Fix-javadoc-warnings-missing-param-and-return.patch
new file mode 100644
index 00000000..91716e00
--- /dev/null
+++ b/patches/Purpur/patches/api/0034-Fix-javadoc-warnings-missing-param-and-return.patch
@@ -0,0 +1,1588 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: BillyGalbreath
+Date: Fri, 18 Dec 2020 21:21:48 -0600
+Subject: [PATCH] Fix javadoc warnings (missing @param and @return)
+
+
+diff --git a/src/main/java/com/destroystokyo/paper/ClientOption.java b/src/main/java/com/destroystokyo/paper/ClientOption.java
+index 9dad814cf51bc59ec5dfbf14474fea6557de38aa..7baf7ee3b62135eda8f0d9c1d761b79f596061f1 100644
+--- a/src/main/java/com/destroystokyo/paper/ClientOption.java
++++ b/src/main/java/com/destroystokyo/paper/ClientOption.java
+@@ -4,6 +4,11 @@ import org.jetbrains.annotations.NotNull;
+
+ import org.bukkit.inventory.MainHand;
+
++/**
++ * Represents a client option
++ *
++ * @param Client option type
++ */
+ public final class ClientOption {
+
+ public static final ClientOption SKIN_PARTS = new ClientOption<>(SkinParts.class);
+@@ -19,6 +24,11 @@ public final class ClientOption {
+ this.type = type;
+ }
+
++ /**
++ * Get the option's type
++ *
++ * @return Option's type
++ */
+ @NotNull
+ public Class getType() {
+ return type;
+diff --git a/src/main/java/com/destroystokyo/paper/MaterialSetTag.java b/src/main/java/com/destroystokyo/paper/MaterialSetTag.java
+index a02a02aa0c87e0f0ed9e509e4dcab01565b3d92a..6c99f4b4960f8f982557bb42717a2868d57ce4b7 100644
+--- a/src/main/java/com/destroystokyo/paper/MaterialSetTag.java
++++ b/src/main/java/com/destroystokyo/paper/MaterialSetTag.java
+@@ -21,10 +21,14 @@ import java.util.stream.Stream;
+ import org.jetbrains.annotations.NotNull;
+ import org.jetbrains.annotations.Nullable;
+
++/**
++ * Material set tag
++ */
+ public class MaterialSetTag extends BaseTag {
+
+ /**
+ * @deprecated Use NamespacedKey version of constructor
++ * @param filter Filter predicate
+ */
+ @Deprecated
+ public MaterialSetTag(@NotNull Predicate filter) {
+@@ -33,6 +37,7 @@ public class MaterialSetTag extends BaseTag {
+
+ /**
+ * @deprecated Use NamespacedKey version of constructor
++ * @param materials Materials to include
+ */
+ @Deprecated
+ public MaterialSetTag(@NotNull Collection materials) {
+@@ -41,6 +46,7 @@ public class MaterialSetTag extends BaseTag {
+
+ /**
+ * @deprecated Use NamespacedKey version of constructor
++ * @param materials Materials to include
+ */
+ @Deprecated
+ public MaterialSetTag(@NotNull Material... materials) {
+diff --git a/src/main/java/com/destroystokyo/paper/SkinParts.java b/src/main/java/com/destroystokyo/paper/SkinParts.java
+index 4a0c39405d4fbed457787e3c6ded4cc6591bc8c2..9b269a51928bc5cc35431855c79bd33ce9031bf1 100644
+--- a/src/main/java/com/destroystokyo/paper/SkinParts.java
++++ b/src/main/java/com/destroystokyo/paper/SkinParts.java
+@@ -1,5 +1,8 @@
+ package com.destroystokyo.paper;
+
++/**
++ * Skin parts
++ */
+ public interface SkinParts {
+
+ boolean hasCapeEnabled();
+diff --git a/src/main/java/com/destroystokyo/paper/block/TargetBlockInfo.java b/src/main/java/com/destroystokyo/paper/block/TargetBlockInfo.java
+index 18a96dbb01d3b34476652264b2d6be3782a154ec..1bae8e5df78bb88deec212eedf855cc69bfbe913 100644
+--- a/src/main/java/com/destroystokyo/paper/block/TargetBlockInfo.java
++++ b/src/main/java/com/destroystokyo/paper/block/TargetBlockInfo.java
+@@ -46,6 +46,9 @@ public class TargetBlockInfo {
+ return block.getRelative(blockFace);
+ }
+
++ /**
++ * Fluid mode
++ */
+ public enum FluidMode {
+ NEVER,
+ SOURCE_ONLY,
+diff --git a/src/main/java/com/destroystokyo/paper/entity/RangedEntity.java b/src/main/java/com/destroystokyo/paper/entity/RangedEntity.java
+index f2e3233a3d1744e32fb76d3731b9858ef0067e30..e66e8b28c673216dd1587582f44c26f6ca877c23 100644
+--- a/src/main/java/com/destroystokyo/paper/entity/RangedEntity.java
++++ b/src/main/java/com/destroystokyo/paper/entity/RangedEntity.java
+@@ -4,6 +4,9 @@ import org.bukkit.entity.LivingEntity;
+ import org.bukkit.entity.Mob;
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * Represents an entity with ranged attacks
++ */
+ public interface RangedEntity extends Mob {
+ /**
+ * Attack the specified entity using a ranged attack.
+diff --git a/src/main/java/com/destroystokyo/paper/event/block/TNTPrimeEvent.java b/src/main/java/com/destroystokyo/paper/event/block/TNTPrimeEvent.java
+index 73dabb82c7fbea3f0cccade0a2944b11a80ede06..b065f8f8af6ed9cc7b1c8a671488a6424662d14c 100644
+--- a/src/main/java/com/destroystokyo/paper/event/block/TNTPrimeEvent.java
++++ b/src/main/java/com/destroystokyo/paper/event/block/TNTPrimeEvent.java
+@@ -83,6 +83,9 @@ public class TNTPrimeEvent extends BlockEvent implements Cancellable {
+ return handlers;
+ }
+
++ /**
++ * TnT prime reason
++ */
+ public enum PrimeReason {
+ /**
+ * When TNT prime was caused by other explosion (chain reaction)
+diff --git a/src/main/java/com/destroystokyo/paper/event/entity/EndermanEscapeEvent.java b/src/main/java/com/destroystokyo/paper/event/entity/EndermanEscapeEvent.java
+index 806112a8b5a7ce31166675f5b074ceaf42e364b6..a2635e3b76780a51f87f6329b0db3a08dd08b0e6 100644
+--- a/src/main/java/com/destroystokyo/paper/event/entity/EndermanEscapeEvent.java
++++ b/src/main/java/com/destroystokyo/paper/event/entity/EndermanEscapeEvent.java
+@@ -8,6 +8,9 @@ import org.bukkit.event.HandlerList;
+ import org.bukkit.event.entity.EntityEvent;
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * Called when an enderman tries to teleport to escape
++ */
+ public class EndermanEscapeEvent extends EntityEvent implements Cancellable {
+ @NotNull private final Reason reason;
+
+@@ -62,6 +65,9 @@ public class EndermanEscapeEvent extends EntityEvent implements Cancellable {
+ cancelled = cancel;
+ }
+
++ /**
++ * Escape reason
++ */
+ public enum Reason {
+ /**
+ * The enderman has stopped attacking and ran away
+diff --git a/src/main/java/com/destroystokyo/paper/event/entity/EntityTransformedEvent.java b/src/main/java/com/destroystokyo/paper/event/entity/EntityTransformedEvent.java
+index 12194f1fc7f03ca6785904b6187b3dfd03b16461..e974323d0193e6b5a6fe43979c6c24d78107a5cc 100644
+--- a/src/main/java/com/destroystokyo/paper/event/entity/EntityTransformedEvent.java
++++ b/src/main/java/com/destroystokyo/paper/event/entity/EntityTransformedEvent.java
+@@ -66,6 +66,9 @@ public class EntityTransformedEvent extends EntityEvent implements Cancellable {
+ cancelled = cancel;
+ }
+
++ /**
++ * Transformed reason
++ */
+ public enum TransformedReason {
+ /**
+ * When a zombie drowns
+diff --git a/src/main/java/com/destroystokyo/paper/event/entity/WitchReadyPotionEvent.java b/src/main/java/com/destroystokyo/paper/event/entity/WitchReadyPotionEvent.java
+index 5351b523defa054ba56ae3fb591029283ca7510d..f00594fba37c8b6264f940c84ed5c40c09879d2f 100644
+--- a/src/main/java/com/destroystokyo/paper/event/entity/WitchReadyPotionEvent.java
++++ b/src/main/java/com/destroystokyo/paper/event/entity/WitchReadyPotionEvent.java
+@@ -9,6 +9,9 @@ import org.bukkit.inventory.ItemStack;
+ import org.jetbrains.annotations.NotNull;
+ import org.jetbrains.annotations.Nullable;
+
++/**
++ * Called when a witch prepares a potion
++ */
+ public class WitchReadyPotionEvent extends EntityEvent implements Cancellable {
+ private ItemStack potion;
+
+diff --git a/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java b/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java
+index 5b28e9b1daba7834af67dbc193dd656bedd9a994..dbda663489e82b89646975b56462c4ff38a5fde9 100644
+--- a/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java
++++ b/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java
+@@ -11,6 +11,9 @@ import org.bukkit.event.Listener;
+ import org.bukkit.plugin.EventExecutor;
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * Method handle event executor
++ */
+ public class MethodHandleEventExecutor implements EventExecutor {
+ private final Class extends Event> eventClass;
+ private final MethodHandle handle;
+diff --git a/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java b/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java
+index c83672427324bd068ed52916f700b68446a226f6..d28f8f0fb6dcf34453a75af9e9efd18a89b2b94f 100644
+--- a/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java
++++ b/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java
+@@ -15,6 +15,9 @@ import org.bukkit.event.Listener;
+ import org.bukkit.plugin.EventExecutor;
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * Static method handle event executor
++ */
+ public class StaticMethodHandleEventExecutor implements EventExecutor {
+ private final Class extends Event> eventClass;
+ private final MethodHandle handle;
+diff --git a/src/main/java/com/destroystokyo/paper/event/executor/asm/ASMEventExecutorGenerator.java b/src/main/java/com/destroystokyo/paper/event/executor/asm/ASMEventExecutorGenerator.java
+index b6e7d8ee8d903ebf975d60bec0e08603d9a49fdb..55770ba8a2461c782f311aeb8abc79ec5f53ea81 100644
+--- a/src/main/java/com/destroystokyo/paper/event/executor/asm/ASMEventExecutorGenerator.java
++++ b/src/main/java/com/destroystokyo/paper/event/executor/asm/ASMEventExecutorGenerator.java
+@@ -11,6 +11,9 @@ import org.objectweb.asm.commons.GeneratorAdapter;
+
+ import static org.objectweb.asm.Opcodes.*;
+
++/**
++ * ASM event executor generator
++ */
+ public class ASMEventExecutorGenerator {
+ @NotNull
+ public static byte[] generateEventExecutor(@NotNull Method m, @NotNull String name) {
+diff --git a/src/main/java/com/destroystokyo/paper/event/executor/asm/ClassDefiner.java b/src/main/java/com/destroystokyo/paper/event/executor/asm/ClassDefiner.java
+index f79685b48bb581277a6891927988b6f7a4389dc4..75810a098791b5f758a3fbb212d80643b1cb505b 100644
+--- a/src/main/java/com/destroystokyo/paper/event/executor/asm/ClassDefiner.java
++++ b/src/main/java/com/destroystokyo/paper/event/executor/asm/ClassDefiner.java
+@@ -2,6 +2,9 @@ package com.destroystokyo.paper.event.executor.asm;
+
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * Class definer
++ */
+ public interface ClassDefiner {
+
+ /**
+diff --git a/src/main/java/com/destroystokyo/paper/event/executor/asm/SafeClassDefiner.java b/src/main/java/com/destroystokyo/paper/event/executor/asm/SafeClassDefiner.java
+index ac99477e9f2c08041aeff31abc1d1edee58d0a67..d2bd9211046dea646f0c0954a932859ba1d0fb15 100644
+--- a/src/main/java/com/destroystokyo/paper/event/executor/asm/SafeClassDefiner.java
++++ b/src/main/java/com/destroystokyo/paper/event/executor/asm/SafeClassDefiner.java
+@@ -9,6 +9,9 @@ import com.google.common.collect.MapMaker;
+ import org.jetbrains.annotations.NotNull;
+ import org.objectweb.asm.Type;
+
++/**
++ * Safe class definer
++ */
+ public class SafeClassDefiner implements ClassDefiner {
+ /* default */ static final SafeClassDefiner INSTANCE = new SafeClassDefiner();
+
+diff --git a/src/main/java/com/destroystokyo/paper/event/player/PlayerArmorChangeEvent.java b/src/main/java/com/destroystokyo/paper/event/player/PlayerArmorChangeEvent.java
+index e406ce639a2e88b78f82f25e71678a669d0a958b..4cc1012c33c6f76255ac075ace1d8ee638091eed 100644
+--- a/src/main/java/com/destroystokyo/paper/event/player/PlayerArmorChangeEvent.java
++++ b/src/main/java/com/destroystokyo/paper/event/player/PlayerArmorChangeEvent.java
+@@ -80,6 +80,9 @@ public class PlayerArmorChangeEvent extends PlayerEvent {
+ return HANDLERS;
+ }
+
++ /**
++ * Armor slot type
++ */
+ public enum SlotType {
+ HEAD(NETHERITE_HELMET, DIAMOND_HELMET, GOLDEN_HELMET, IRON_HELMET, CHAINMAIL_HELMET, LEATHER_HELMET, CARVED_PUMPKIN, PLAYER_HEAD, SKELETON_SKULL, ZOMBIE_HEAD, CREEPER_HEAD, WITHER_SKELETON_SKULL, TURTLE_HELMET),
+ CHEST(NETHERITE_CHESTPLATE, DIAMOND_CHESTPLATE, GOLDEN_CHESTPLATE, IRON_CHESTPLATE, CHAINMAIL_CHESTPLATE, LEATHER_CHESTPLATE, ELYTRA),
+diff --git a/src/main/java/com/destroystokyo/paper/event/player/PlayerConnectionCloseEvent.java b/src/main/java/com/destroystokyo/paper/event/player/PlayerConnectionCloseEvent.java
+index 12c1c6fe9dc8dc5f5faf6dcf99f6857219ef22b8..1f623010d3eddc2bee8f4b8fb410a9509a57b5ae 100644
+--- a/src/main/java/com/destroystokyo/paper/event/player/PlayerConnectionCloseEvent.java
++++ b/src/main/java/com/destroystokyo/paper/event/player/PlayerConnectionCloseEvent.java
+@@ -59,7 +59,9 @@ public class PlayerConnectionCloseEvent extends Event {
+ }
+
+ /**
+- * Returns the {@code UUID} of the player disconnecting.
++ * Get the {@code UUID} of the player disconnecting.
++ *
++ * @return the {@code UUID} of the player disconnecting
+ */
+ @NotNull
+ public UUID getPlayerUniqueId() {
+@@ -67,7 +69,9 @@ public class PlayerConnectionCloseEvent extends Event {
+ }
+
+ /**
+- * Returns the name of the player disconnecting.
++ * Get the name of the player disconnecting.
++ *
++ * @return the name of the player disconnecting
+ */
+ @NotNull
+ public String getPlayerName() {
+@@ -75,7 +79,9 @@ public class PlayerConnectionCloseEvent extends Event {
+ }
+
+ /**
+- * Returns the player's IP address.
++ * Get the player's IP address.
++ *
++ * @return the player's IP address
+ */
+ @NotNull
+ public InetAddress getIpAddress() {
+diff --git a/src/main/java/com/destroystokyo/paper/event/player/PlayerUseUnknownEntityEvent.java b/src/main/java/com/destroystokyo/paper/event/player/PlayerUseUnknownEntityEvent.java
+index 09cfdf48ead8f03f3497646537292174241b0868..20f8201d2278f6fcac38913638510f33b0750b28 100644
+--- a/src/main/java/com/destroystokyo/paper/event/player/PlayerUseUnknownEntityEvent.java
++++ b/src/main/java/com/destroystokyo/paper/event/player/PlayerUseUnknownEntityEvent.java
+@@ -6,6 +6,9 @@ import org.bukkit.event.player.PlayerEvent;
+ import org.bukkit.inventory.EquipmentSlot;
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * Called when a player uses (interacts with) an unknown entity
++ */
+ public class PlayerUseUnknownEntityEvent extends PlayerEvent {
+
+ private static final HandlerList handlers = new HandlerList();
+diff --git a/src/main/java/com/destroystokyo/paper/event/server/GS4QueryEvent.java b/src/main/java/com/destroystokyo/paper/event/server/GS4QueryEvent.java
+index 77a19995f6792a182c5a43d6714e7bda0f42df5b..cb43359e012630b3126564c068d81e9b20845a80 100644
+--- a/src/main/java/com/destroystokyo/paper/event/server/GS4QueryEvent.java
++++ b/src/main/java/com/destroystokyo/paper/event/server/GS4QueryEvent.java
+@@ -105,6 +105,9 @@ public final class GS4QueryEvent extends Event {
+ ;
+ }
+
++ /**
++ * Query response
++ */
+ public final static class QueryResponse {
+ private final String motd;
+ private final String gameVersion;
+diff --git a/src/main/java/com/destroystokyo/paper/event/server/ServerTickStartEvent.java b/src/main/java/com/destroystokyo/paper/event/server/ServerTickStartEvent.java
+index eac85f1f49088bb71afb01eff4d5f53887306461..3ae058eb9433885920715408ebbe303d02f99c47 100644
+--- a/src/main/java/com/destroystokyo/paper/event/server/ServerTickStartEvent.java
++++ b/src/main/java/com/destroystokyo/paper/event/server/ServerTickStartEvent.java
+@@ -4,6 +4,9 @@ import org.bukkit.event.Event;
+ import org.bukkit.event.HandlerList;
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * Called at the beginning of the server tick
++ */
+ public class ServerTickStartEvent extends Event {
+
+ private static final HandlerList handlers = new HandlerList();
+diff --git a/src/main/java/com/destroystokyo/paper/inventory/meta/ArmorStandMeta.java b/src/main/java/com/destroystokyo/paper/inventory/meta/ArmorStandMeta.java
+index 7e4acfff16db80a75e1ff2fee1972b16955b0918..333812f322f1f0a06d654581103aeb0123daef60 100644
+--- a/src/main/java/com/destroystokyo/paper/inventory/meta/ArmorStandMeta.java
++++ b/src/main/java/com/destroystokyo/paper/inventory/meta/ArmorStandMeta.java
+@@ -2,6 +2,9 @@ package com.destroystokyo.paper.inventory.meta;
+
+ import org.bukkit.inventory.meta.ItemMeta;
+
++/**
++ * Armor stand meta
++ */
+ public interface ArmorStandMeta extends ItemMeta {
+
+ /**
+diff --git a/src/main/java/com/destroystokyo/paper/loottable/LootableInventoryReplenishEvent.java b/src/main/java/com/destroystokyo/paper/loottable/LootableInventoryReplenishEvent.java
+index fd184f13f5e8ee5cf829fff4f44696e1f760430b..302059e9fc048a63fd9cd2e1ed5aa27ef5811637 100644
+--- a/src/main/java/com/destroystokyo/paper/loottable/LootableInventoryReplenishEvent.java
++++ b/src/main/java/com/destroystokyo/paper/loottable/LootableInventoryReplenishEvent.java
+@@ -6,6 +6,9 @@ import org.bukkit.event.HandlerList;
+ import org.bukkit.event.player.PlayerEvent;
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * Called when a lootable inventory replenishes it's contents
++ */
+ public class LootableInventoryReplenishEvent extends PlayerEvent implements Cancellable {
+ @NotNull private final LootableInventory inventory;
+
+diff --git a/src/main/java/com/destroystokyo/paper/util/SneakyThrow.java b/src/main/java/com/destroystokyo/paper/util/SneakyThrow.java
+index 9db0056ab94145819628b3ad8d8d26130d117fcf..680410d8404a6d3b0ac91aa5fc4cd9d7f5c8fa93 100644
+--- a/src/main/java/com/destroystokyo/paper/util/SneakyThrow.java
++++ b/src/main/java/com/destroystokyo/paper/util/SneakyThrow.java
+@@ -2,6 +2,9 @@ package com.destroystokyo.paper.util;
+
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * Sneaky sneaky
++ */
+ public class SneakyThrow {
+
+ public static void sneaky(@NotNull Throwable exception) {
+diff --git a/src/main/java/com/destroystokyo/paper/util/VersionFetcher.java b/src/main/java/com/destroystokyo/paper/util/VersionFetcher.java
+index 2a2651299e8dc631938ba4b4078dc694764d784c..62fafc206e7f1d8fdc0b0dfa2c1c6f1d280f4f5e 100644
+--- a/src/main/java/com/destroystokyo/paper/util/VersionFetcher.java
++++ b/src/main/java/com/destroystokyo/paper/util/VersionFetcher.java
+@@ -3,6 +3,9 @@ package com.destroystokyo.paper.util;
+ import org.bukkit.Bukkit;
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * Version fetcher
++ */
+ public interface VersionFetcher {
+ /**
+ * Amount of time to cache results for in milliseconds
+@@ -25,6 +28,9 @@ public interface VersionFetcher {
+ @NotNull
+ String getVersionMessage(@NotNull String serverVersion);
+
++ /**
++ * Dummy version fetcher
++ */
+ class DummyVersionFetcher implements VersionFetcher {
+
+ @Override
+diff --git a/src/main/java/com/destroystokyo/paper/utils/CachedSizeConcurrentLinkedQueue.java b/src/main/java/com/destroystokyo/paper/utils/CachedSizeConcurrentLinkedQueue.java
+index 5bb677ce585b856b3d3e589e29786a29619c56a7..613f00fa387dcc5af3191e550dea9d4d76fda02f 100644
+--- a/src/main/java/com/destroystokyo/paper/utils/CachedSizeConcurrentLinkedQueue.java
++++ b/src/main/java/com/destroystokyo/paper/utils/CachedSizeConcurrentLinkedQueue.java
+@@ -5,6 +5,11 @@ import java.util.concurrent.atomic.LongAdder;
+ import org.jetbrains.annotations.NotNull;
+ import org.jetbrains.annotations.Nullable;
+
++/**
++ * Cached size concurrent linked queue
++ *
++ * @param Element type
++ */
+ public class CachedSizeConcurrentLinkedQueue extends ConcurrentLinkedQueue {
+ private final LongAdder cachedSize = new LongAdder();
+
+diff --git a/src/main/java/io/papermc/paper/world/MoonPhase.java b/src/main/java/io/papermc/paper/world/MoonPhase.java
+index df05153397b42930cd53d37b30824c7e5f008f7e..ebf70fea04a9d37aa5f2ad8e7d6cef73cd3a4541 100644
+--- a/src/main/java/io/papermc/paper/world/MoonPhase.java
++++ b/src/main/java/io/papermc/paper/world/MoonPhase.java
+@@ -5,6 +5,9 @@ import org.jetbrains.annotations.NotNull;
+ import java.util.HashMap;
+ import java.util.Map;
+
++/**
++ * Moon phase
++ */
+ public enum MoonPhase {
+ FULL_MOON(0L),
+ WANING_GIBBOUS(1L),
+diff --git a/src/main/java/org/bukkit/Fluid.java b/src/main/java/org/bukkit/Fluid.java
+index 525ede42137cc27cf20cf713478e85292455676e..a0279fcbfc76ad97a61fc191424e876d517acb46 100644
+--- a/src/main/java/org/bukkit/Fluid.java
++++ b/src/main/java/org/bukkit/Fluid.java
+@@ -3,6 +3,9 @@ package org.bukkit;
+ import java.util.Locale;
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * Represents a fluid
++ */
+ public enum Fluid implements Keyed {
+
+ WATER,
+diff --git a/src/main/java/org/bukkit/Nameable.java b/src/main/java/org/bukkit/Nameable.java
+index fee814e01a653d2b53c56e8b566383ca44aa5346..cda8b54d73edf47ef8d5a1bba25478b187fe1cde 100644
+--- a/src/main/java/org/bukkit/Nameable.java
++++ b/src/main/java/org/bukkit/Nameable.java
+@@ -2,6 +2,9 @@ package org.bukkit;
+
+ import org.jetbrains.annotations.Nullable;
+
++/**
++ * Represents something that can be named
++ */
+ public interface Nameable {
+
+ /**
+diff --git a/src/main/java/org/bukkit/OfflinePlayer.java b/src/main/java/org/bukkit/OfflinePlayer.java
+index 3afd5f5c0208a4ee93b5dbfc2aab2b9d2e8a7544..7838731e0e16bdccfb79e74ceb64148f7c52db79 100644
+--- a/src/main/java/org/bukkit/OfflinePlayer.java
++++ b/src/main/java/org/bukkit/OfflinePlayer.java
+@@ -9,6 +9,9 @@ import org.bukkit.permissions.ServerOperator;
+ import org.jetbrains.annotations.NotNull;
+ import org.jetbrains.annotations.Nullable;
+
++/**
++ * Represents an offline player
++ */
+ public interface OfflinePlayer extends ServerOperator, AnimalTamer, ConfigurationSerializable {
+
+ /**
+diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java
+index 05b47c2462a00451fc64c79c3eda116fc8003a9f..26c1953d23efd370ac7fd47fc3432edba4724139 100644
+--- a/src/main/java/org/bukkit/Server.java
++++ b/src/main/java/org/bukkit/Server.java
+@@ -1455,6 +1455,9 @@ public interface Server extends PluginMessageRecipient {
+ UnsafeValues getUnsafe();
+
+ // Spigot start
++ /**
++ * Spigot stuffs
++ */
+ public class Spigot {
+
+ @NotNull
+diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java
+index 931ffa38faab86445a5d63364a47cb653ca3d4ed..97b9ade0e771eae663fb42f91e15545034d58fc9 100644
+--- a/src/main/java/org/bukkit/UnsafeValues.java
++++ b/src/main/java/org/bukkit/UnsafeValues.java
+@@ -80,6 +80,8 @@ public interface UnsafeValues {
+
+ /**
+ * Called once by the version command on first use, then cached.
++ *
++ * @return Paper's VersionFetcher
+ */
+ default com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() {
+ return new com.destroystokyo.paper.util.VersionFetcher.DummyVersionFetcher();
+@@ -98,6 +100,8 @@ public interface UnsafeValues {
+ /**
+ * Return the translation key for the Material, so the client can translate it into the active
+ * locale when using a TranslatableComponent.
++ *
++ * @param mat Material to check
+ * @return the translation key
+ */
+ String getTranslationKey(Material mat);
+@@ -105,6 +109,8 @@ public interface UnsafeValues {
+ /**
+ * Return the translation key for the Block, so the client can translate it into the active
+ * locale when using a TranslatableComponent.
++ *
++ * @param block Block to check
+ * @return the translation key
+ */
+ String getTranslationKey(org.bukkit.block.Block block);
+@@ -113,6 +119,8 @@ public interface UnsafeValues {
+ * Return the translation key for the EntityType, so the client can translate it into the active
+ * locale when using a TranslatableComponent.
+ * This is null
, when the EntityType isn't known to NMS (custom entities)
++ *
++ * @param type EntityType to check
+ * @return the translation key
+ */
+ String getTranslationKey(org.bukkit.entity.EntityType type);
+@@ -121,6 +129,8 @@ public interface UnsafeValues {
+ * Creates and returns the next EntityId available.
+ *
+ * Use this when sending custom packets, so that there are no collisions on the client or server.
++ *
++ * @return the next available entity id
+ */
+ public int nextEntityId();
+
+diff --git a/src/main/java/org/bukkit/WorldBorder.java b/src/main/java/org/bukkit/WorldBorder.java
+index afb7b136b461202026290624836446cff9f9e45d..087579fdff09237409c9f80446e7a15a78f9040c 100644
+--- a/src/main/java/org/bukkit/WorldBorder.java
++++ b/src/main/java/org/bukkit/WorldBorder.java
+@@ -2,6 +2,9 @@ package org.bukkit;
+
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * Represents a world border
++ */
+ public interface WorldBorder {
+
+ /**
+diff --git a/src/main/java/org/bukkit/WorldCreator.java b/src/main/java/org/bukkit/WorldCreator.java
+index 6e6945dd4c770be04ec09da3958fae751717527a..7d1030d4573e3843cae9ad4e39cef8baa2af39b0 100644
+--- a/src/main/java/org/bukkit/WorldCreator.java
++++ b/src/main/java/org/bukkit/WorldCreator.java
+@@ -238,11 +238,8 @@ public class WorldCreator {
+ * is as follows:
+ * {"structures": {"structures": {"village": {"salt": 8015723, "spacing": 32, "separation": 8}}}, "layers": [{"block": "stone", "height": 1}, {"block": "grass", "height": 1}], "biome":"plains"}
+ *
+- * @see Custom
+- * dimension (scroll to "When the generator ID type is
+- * minecraft:flat
)"
+- * @param generatorSettings The settings that should be used by the
+- * generator
++ * @see Custom dimension
++ * @param generatorSettings The settings that should be used by the generator
+ * @return This object, for chaining
+ */
+ @NotNull
+diff --git a/src/main/java/org/bukkit/advancement/AdvancementDisplay.java b/src/main/java/org/bukkit/advancement/AdvancementDisplay.java
+index bca3d112e2397b26ba6ccb6cd41e406caae27c5c..f4e076d6f3b05c9de85dcd65b95c1088a094249c 100644
+--- a/src/main/java/org/bukkit/advancement/AdvancementDisplay.java
++++ b/src/main/java/org/bukkit/advancement/AdvancementDisplay.java
+@@ -2,6 +2,9 @@ package org.bukkit.advancement;
+
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * Represents an advancement's display
++ */
+ public interface AdvancementDisplay {
+ /**
+ * Get the title of this advancement
+diff --git a/src/main/java/org/bukkit/advancement/FrameType.java b/src/main/java/org/bukkit/advancement/FrameType.java
+index d1757f3d456ff9efce26ce8baa1d16d896908cc2..a5db52386e11e4b5511ae417a0e7ac92e001de71 100644
+--- a/src/main/java/org/bukkit/advancement/FrameType.java
++++ b/src/main/java/org/bukkit/advancement/FrameType.java
+@@ -3,6 +3,9 @@ package org.bukkit.advancement;
+ import org.bukkit.ChatColor;
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * Represents an advancement's display's frame type
++ */
+ public enum FrameType {
+ TASK(ChatColor.GREEN),
+ CHALLENGE(ChatColor.DARK_PURPLE),
+diff --git a/src/main/java/org/bukkit/block/Block.java b/src/main/java/org/bukkit/block/Block.java
+index 0c72d00ad238ab69d7ae0941e3ecb6c86e71624d..73cf7437795ef185860bfefe51d9481fa297a939 100644
+--- a/src/main/java/org/bukkit/block/Block.java
++++ b/src/main/java/org/bukkit/block/Block.java
+@@ -183,6 +183,9 @@ public interface Block extends Metadatable {
+ * {@code int z = (int) ((packed << 10) >> 37);}
+ *
+ *
++ * @param x X coordinate
++ * @param y Y coordinate
++ * @param z Z coordinate
+ * @return This block's x, y, and z coordinates packed into a long value
+ */
+ public static long getBlockKey(int x, int y, int z) {
+diff --git a/src/main/java/org/bukkit/block/Lidded.java b/src/main/java/org/bukkit/block/Lidded.java
+index 9da2566e02e63be1a0188deaa27b841fa61688ea..882980b3ada3d6048019a90159fa37639b773d5e 100644
+--- a/src/main/java/org/bukkit/block/Lidded.java
++++ b/src/main/java/org/bukkit/block/Lidded.java
+@@ -1,5 +1,8 @@
+ package org.bukkit.block;
+
++/**
++ * Represents something that has a lid
++ */
+ public interface Lidded {
+
+ /**
+diff --git a/src/main/java/org/bukkit/boss/BarColor.java b/src/main/java/org/bukkit/boss/BarColor.java
+index e191d9ffe8d6fdeaef77313535a697b6038a0550..2ce3201079701de65c434b8f1e390bed27364370 100644
+--- a/src/main/java/org/bukkit/boss/BarColor.java
++++ b/src/main/java/org/bukkit/boss/BarColor.java
+@@ -1,5 +1,8 @@
+ package org.bukkit.boss;
+
++/**
++ * Bar color
++ */
+ public enum BarColor {
+ PINK,
+ BLUE,
+diff --git a/src/main/java/org/bukkit/boss/BarFlag.java b/src/main/java/org/bukkit/boss/BarFlag.java
+index 69e02998d062f5b52ef4e5cdd4dbb29384eb9f3c..0d8f617dc33b828bdadf3e8112b4c545625a55b2 100644
+--- a/src/main/java/org/bukkit/boss/BarFlag.java
++++ b/src/main/java/org/bukkit/boss/BarFlag.java
+@@ -1,5 +1,8 @@
+ package org.bukkit.boss;
+
++/**
++ * Bar flag
++ */
+ public enum BarFlag {
+
+ /**
+diff --git a/src/main/java/org/bukkit/boss/BarStyle.java b/src/main/java/org/bukkit/boss/BarStyle.java
+index 3e499eb77957851ca67ca37bd116c617b44b6478..6907889ba91a32583cf62749a3148d3d2cd93925 100644
+--- a/src/main/java/org/bukkit/boss/BarStyle.java
++++ b/src/main/java/org/bukkit/boss/BarStyle.java
+@@ -1,5 +1,8 @@
+ package org.bukkit.boss;
+
++/**
++ * Boss bar style
++ */
+ public enum BarStyle {
+ /**
+ * Makes the boss bar solid (no segments)
+diff --git a/src/main/java/org/bukkit/boss/BossBar.java b/src/main/java/org/bukkit/boss/BossBar.java
+index 70274f2e2a1d6f27c4febd9d5d5fa3ee1b49f100..3b98e6e3e6dea0df5fb9462c78e8c142fde64723 100644
+--- a/src/main/java/org/bukkit/boss/BossBar.java
++++ b/src/main/java/org/bukkit/boss/BossBar.java
+@@ -5,6 +5,9 @@ import org.bukkit.entity.Player;
+ import org.jetbrains.annotations.NotNull;
+ import org.jetbrains.annotations.Nullable;
+
++/**
++ * Represents a boss bar
++ */
+ public interface BossBar {
+
+ /**
+diff --git a/src/main/java/org/bukkit/command/CommandSender.java b/src/main/java/org/bukkit/command/CommandSender.java
+index de4370233e0358da30d3704145044a99d8369f52..29d2077926620786c74b1f2f6ec6067a818d6e82 100644
+--- a/src/main/java/org/bukkit/command/CommandSender.java
++++ b/src/main/java/org/bukkit/command/CommandSender.java
+@@ -55,6 +55,9 @@ public interface CommandSender extends Permissible {
+ public String getName();
+
+ // Spigot start
++ /**
++ * Spigot stuffs
++ */
+ public class Spigot {
+
+ /**
+diff --git a/src/main/java/org/bukkit/conversations/Conversation.java b/src/main/java/org/bukkit/conversations/Conversation.java
+index bf2407c838bc20197802687c150d513f4e86ed2b..ae09abfe9fe2979e89cfb4bb5c9cc0e7760943e7 100644
+--- a/src/main/java/org/bukkit/conversations/Conversation.java
++++ b/src/main/java/org/bukkit/conversations/Conversation.java
+@@ -295,6 +295,9 @@ public class Conversation {
+ }
+ }
+
++ /**
++ * Conversation state
++ */
+ public enum ConversationState {
+ UNSTARTED,
+ STARTED,
+diff --git a/src/main/java/org/bukkit/entity/AbstractArrow.java b/src/main/java/org/bukkit/entity/AbstractArrow.java
+index b1d8007eed489aa061c1a6813bcdafc101231e56..eb847e3bb110f73695ba9b4191e69e6ea8a6ffc8 100644
+--- a/src/main/java/org/bukkit/entity/AbstractArrow.java
++++ b/src/main/java/org/bukkit/entity/AbstractArrow.java
+@@ -176,6 +176,9 @@ public interface AbstractArrow extends Projectile {
+ this.setPickupStatus(PickupStatus.valueOf(rule.name()));
+ }
+
++ /**
++ * Pickup rule
++ */
+ @Deprecated
+ enum PickupRule {
+ DISALLOWED,
+diff --git a/src/main/java/org/bukkit/entity/AnimalTamer.java b/src/main/java/org/bukkit/entity/AnimalTamer.java
+index 2e17b2d4f759531fbe9ee8e9b00c839186af09ca..9382234722792b5920a2456187e079581c2e2f2a 100644
+--- a/src/main/java/org/bukkit/entity/AnimalTamer.java
++++ b/src/main/java/org/bukkit/entity/AnimalTamer.java
+@@ -4,6 +4,9 @@ import java.util.UUID;
+ import org.jetbrains.annotations.NotNull;
+ import org.jetbrains.annotations.Nullable;
+
++/**
++ * Represents an animal tamer
++ */
+ public interface AnimalTamer {
+
+ /**
+diff --git a/src/main/java/org/bukkit/entity/ArmorStand.java b/src/main/java/org/bukkit/entity/ArmorStand.java
+index 8ca6c9eba926f436203af211c6e274a59ddb15e8..f61419d3ce15bd553a864e4e9cd988b57d8f9695 100644
+--- a/src/main/java/org/bukkit/entity/ArmorStand.java
++++ b/src/main/java/org/bukkit/entity/ArmorStand.java
+@@ -7,6 +7,9 @@ import org.bukkit.util.EulerAngle;
+ import org.jetbrains.annotations.NotNull;
+ import org.jetbrains.annotations.Nullable;
+
++/**
++ * Represents an armor stand
++ */
+ public interface ArmorStand extends LivingEntity {
+
+ /**
+diff --git a/src/main/java/org/bukkit/entity/Arrow.java b/src/main/java/org/bukkit/entity/Arrow.java
+index ec8443b67014c0129256c9227cc89686422b9217..6b41e09d6ed075aae5455929b5b29efb2c6287f6 100644
+--- a/src/main/java/org/bukkit/entity/Arrow.java
++++ b/src/main/java/org/bukkit/entity/Arrow.java
+@@ -8,6 +8,9 @@ import org.bukkit.potion.PotionEffectType;
+ import org.jetbrains.annotations.NotNull;
+ import org.jetbrains.annotations.Nullable;
+
++/**
++ * Represents an arrow
++ */
+ public interface Arrow extends AbstractArrow {
+
+ /**
+diff --git a/src/main/java/org/bukkit/entity/Dolphin.java b/src/main/java/org/bukkit/entity/Dolphin.java
+index f00eaadcdde7ceef95def2d8ec6eb63a76c177bd..a4a645799d82c730e3280519facf1347d26a859f 100644
+--- a/src/main/java/org/bukkit/entity/Dolphin.java
++++ b/src/main/java/org/bukkit/entity/Dolphin.java
+@@ -1,3 +1,6 @@
+ package org.bukkit.entity;
+
++/**
++ * Represents a dolphin
++ */
+ public interface Dolphin extends WaterMob { }
+diff --git a/src/main/java/org/bukkit/entity/DragonFireball.java b/src/main/java/org/bukkit/entity/DragonFireball.java
+index 6c475a3723721b33bb7709d8c1bbf487a10f9bbe..210d955e9bbb669c8ce644c935c1607ae8e7419b 100644
+--- a/src/main/java/org/bukkit/entity/DragonFireball.java
++++ b/src/main/java/org/bukkit/entity/DragonFireball.java
+@@ -1,3 +1,6 @@
+ package org.bukkit.entity;
+
++/**
++ * Represents a dragon's fireball
++ */
+ public interface DragonFireball extends Fireball {}
+diff --git a/src/main/java/org/bukkit/entity/Endermite.java b/src/main/java/org/bukkit/entity/Endermite.java
+index d9be83961b28b927a587f6dbb339b531520e4865..1ff4c5e283ac05c405c09bd4b853066452614696 100644
+--- a/src/main/java/org/bukkit/entity/Endermite.java
++++ b/src/main/java/org/bukkit/entity/Endermite.java
+@@ -1,5 +1,8 @@
+ package org.bukkit.entity;
+
++/**
++ * Represents an endermite
++ */
+ public interface Endermite extends Monster {
+
+ /**
+diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java
+index 5f7947cd6f3bf9f76e8b3bac339f61b9afadaaad..08dbe8208fad174f03a0e08c26bb48a0729ec0ce 100644
+--- a/src/main/java/org/bukkit/entity/Entity.java
++++ b/src/main/java/org/bukkit/entity/Entity.java
+@@ -622,6 +622,9 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent
+ Pose getPose();
+
+ // Spigot start
++ /**
++ * Spigot stuffs
++ */
+ public class Spigot extends CommandSender.Spigot {
+
+ }
+@@ -665,36 +668,50 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent
+
+ /**
+ * Check if entity is in rain
++ *
++ * @return True if entity is in rain
+ */
+ public boolean isInRain();
+
+ /**
+ * Check if entity is in bubble column
++ *
++ * @return True if entity is in bubble column
+ */
+ public boolean isInBubbleColumn();
+
+ /**
+ * Check if entity is in water or rain
++ *
++ * @return True if entity is in water or rain
+ */
+ public boolean isInWaterOrRain();
+
+ /**
+ * Check if entity is in water or bubble column
++ *
++ * @return True if entity is in water or bubble column
+ */
+ public boolean isInWaterOrBubbleColumn();
+
+ /**
+ * Check if entity is in water or rain or bubble column
++ *
++ * @return True if entity is in water or rain or bubble column
+ */
+ public boolean isInWaterOrRainOrBubbleColumn();
+
+ /**
+ * Check if entity is in lava
++ *
++ * @return True if entity is in lava
+ */
+ public boolean isInLava();
+
+ /**
+ * Check if entity is inside a ticking chunk
++ *
++ * @return True if entity is ticking
+ */
+ public boolean isTicking();
+ // Paper end
+diff --git a/src/main/java/org/bukkit/entity/EntityType.java b/src/main/java/org/bukkit/entity/EntityType.java
+index 692b75eb78405874077c850bfc72e247ccc80860..31fc511edc33635438e93d3c14292305ac30a38f 100644
+--- a/src/main/java/org/bukkit/entity/EntityType.java
++++ b/src/main/java/org/bukkit/entity/EntityType.java
+@@ -20,6 +20,9 @@ import org.jetbrains.annotations.Contract;
+ import org.jetbrains.annotations.NotNull;
+ import org.jetbrains.annotations.Nullable;
+
++/**
++ * Entity type
++ */
+ public enum EntityType implements Keyed {
+
+ // These strings MUST match the strings in nms.EntityTypes and are case sensitive.
+diff --git a/src/main/java/org/bukkit/entity/Firework.java b/src/main/java/org/bukkit/entity/Firework.java
+index d616d5941b3c7b85e350e845901da798601b9a3c..14bdddb7ced9c4cd92a8ad96d97a08a6ed4c25bf 100644
+--- a/src/main/java/org/bukkit/entity/Firework.java
++++ b/src/main/java/org/bukkit/entity/Firework.java
+@@ -3,6 +3,9 @@ package org.bukkit.entity;
+ import org.bukkit.inventory.meta.FireworkMeta;
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * Represents a firework
++ */
+ public interface Firework extends Projectile {
+
+ /**
+diff --git a/src/main/java/org/bukkit/entity/Guardian.java b/src/main/java/org/bukkit/entity/Guardian.java
+index 4da9f3c5f1423bf8f9eeb490736cabf027853e60..082e90859e6c965029606d7d395673a81bff2cb4 100644
+--- a/src/main/java/org/bukkit/entity/Guardian.java
++++ b/src/main/java/org/bukkit/entity/Guardian.java
+@@ -1,5 +1,8 @@
+ package org.bukkit.entity;
+
++/**
++ * Represents a guardian
++ */
+ public interface Guardian extends Monster {
+
+ /**
+diff --git a/src/main/java/org/bukkit/entity/LightningStrike.java b/src/main/java/org/bukkit/entity/LightningStrike.java
+index 2c81a3f685588431a3c7675c84b35a28975232af..efb308c82580722e5106d5d1c7512d99c38e536a 100644
+--- a/src/main/java/org/bukkit/entity/LightningStrike.java
++++ b/src/main/java/org/bukkit/entity/LightningStrike.java
+@@ -15,6 +15,9 @@ public interface LightningStrike extends Entity {
+ public boolean isEffect();
+
+ // Spigot start
++ /**
++ * Spigot stuffs
++ */
+ public class Spigot extends Entity.Spigot {
+
+ /*
+diff --git a/src/main/java/org/bukkit/entity/Panda.java b/src/main/java/org/bukkit/entity/Panda.java
+index a6a7429ed2e1eefb2b12b7480ed74fcc3963a864..e8027e1d505dda6effbb1698550016e87b2e581f 100644
+--- a/src/main/java/org/bukkit/entity/Panda.java
++++ b/src/main/java/org/bukkit/entity/Panda.java
+@@ -37,6 +37,9 @@ public interface Panda extends Animals {
+ */
+ void setHiddenGene(@NotNull Gene gene);
+
++ /**
++ * Panda gene type
++ */
+ public enum Gene {
+
+ NORMAL(false),
+diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
+index a79703115da811397ee6b7c6c079846af537fd12..5ad9de1f34b1cca3e31b8a142e3831739a3e824a 100644
+--- a/src/main/java/org/bukkit/entity/Player.java
++++ b/src/main/java/org/bukkit/entity/Player.java
+@@ -1776,6 +1776,8 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+ void resetCooldown();
+
+ /**
++ * @param ClientOption type
++ * @param option ClientOption
+ * @return the client option value of the player
+ */
+ @NotNull
+@@ -1807,6 +1809,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+ // Paper end
+
+ // Spigot start
++ /**
++ * Spigot stuffs
++ */
+ public class Spigot extends Entity.Spigot {
+
+ /**
+diff --git a/src/main/java/org/bukkit/entity/Rabbit.java b/src/main/java/org/bukkit/entity/Rabbit.java
+index e88154283a8ef594e160d25005870053de15568a..24c81708dc6691e220e278e92c07b9d51072fb88 100644
+--- a/src/main/java/org/bukkit/entity/Rabbit.java
++++ b/src/main/java/org/bukkit/entity/Rabbit.java
+@@ -2,6 +2,9 @@ package org.bukkit.entity;
+
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * Represents a rabbit
++ */
+ public interface Rabbit extends Animals {
+
+ /**
+diff --git a/src/main/java/org/bukkit/entity/Raider.java b/src/main/java/org/bukkit/entity/Raider.java
+index 9a99b8ca1ec9c3c88b29275c88b1221e1b22bcef..f1763f75d5f223ef70b968e4633616731b727df5 100644
+--- a/src/main/java/org/bukkit/entity/Raider.java
++++ b/src/main/java/org/bukkit/entity/Raider.java
+@@ -3,6 +3,9 @@ package org.bukkit.entity;
+ import org.bukkit.block.Block;
+ import org.jetbrains.annotations.Nullable;
+
++/**
++ * Represents a raider entity
++ */
+ public interface Raider extends Monster {
+
+ /**
+diff --git a/src/main/java/org/bukkit/entity/Shulker.java b/src/main/java/org/bukkit/entity/Shulker.java
+index 3441bdb7fcb99dab67bfe9dad5ed989009e443ad..52927a7d84a2bfcae04526f38bf5efd75b3459bb 100644
+--- a/src/main/java/org/bukkit/entity/Shulker.java
++++ b/src/main/java/org/bukkit/entity/Shulker.java
+@@ -2,4 +2,7 @@ package org.bukkit.entity;
+
+ import org.bukkit.material.Colorable;
+
++/**
++ * Represents a shulker
++ */
+ public interface Shulker extends Golem, Colorable {}
+diff --git a/src/main/java/org/bukkit/entity/ShulkerBullet.java b/src/main/java/org/bukkit/entity/ShulkerBullet.java
+index 4623e0d767b343cbdc6fcf20b3b2ff7ff14863cf..ca3f98a8272bab3c9f57f59b077b206c6503de80 100644
+--- a/src/main/java/org/bukkit/entity/ShulkerBullet.java
++++ b/src/main/java/org/bukkit/entity/ShulkerBullet.java
+@@ -2,6 +2,9 @@ package org.bukkit.entity;
+
+ import org.jetbrains.annotations.Nullable;
+
++/**
++ * Represents a shulker bullet
++ */
+ public interface ShulkerBullet extends Projectile {
+
+ /**
+diff --git a/src/main/java/org/bukkit/entity/Skeleton.java b/src/main/java/org/bukkit/entity/Skeleton.java
+index 1c367f78eadf24850061a84ce63b950b79d3c435..9021865244a7eacf0477b0db790e0ff41fc8ddfd 100644
+--- a/src/main/java/org/bukkit/entity/Skeleton.java
++++ b/src/main/java/org/bukkit/entity/Skeleton.java
+@@ -27,7 +27,9 @@ public interface Skeleton extends Monster, RangedEntity { // Paper
+ @Contract("_ -> fail")
+ public void setSkeletonType(SkeletonType type);
+
+- /*
++ /**
++ * Skeleton type
++ *
+ * @deprecated classes are different types
+ */
+ @Deprecated
+diff --git a/src/main/java/org/bukkit/entity/Tameable.java b/src/main/java/org/bukkit/entity/Tameable.java
+index 65e68da98ab66ed781bce2f0dbe0913be48d2990..cb708ae66f60a36ac0f529614743e33511e4bd90 100644
+--- a/src/main/java/org/bukkit/entity/Tameable.java
++++ b/src/main/java/org/bukkit/entity/Tameable.java
+@@ -3,6 +3,9 @@ package org.bukkit.entity;
+ import org.jetbrains.annotations.NotNull;
+ import org.jetbrains.annotations.Nullable;
+
++/**
++ * Represents a tameable entity
++ */
+ public interface Tameable extends Animals {
+
+ /**
+diff --git a/src/main/java/org/bukkit/entity/ThrowableProjectile.java b/src/main/java/org/bukkit/entity/ThrowableProjectile.java
+index ceb3e2c5117740ce284e893fff8e41a002d78649..fab5c3c90f55c113cae2bca2354a94e88c1aaece 100644
+--- a/src/main/java/org/bukkit/entity/ThrowableProjectile.java
++++ b/src/main/java/org/bukkit/entity/ThrowableProjectile.java
+@@ -3,6 +3,9 @@ package org.bukkit.entity;
+ import org.bukkit.inventory.ItemStack;
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * Represents a throwable projectile
++ */
+ public interface ThrowableProjectile extends Projectile {
+
+ /**
+diff --git a/src/main/java/org/bukkit/entity/minecart/CommandMinecart.java b/src/main/java/org/bukkit/entity/minecart/CommandMinecart.java
+index 63c80b4ee1f7adc8a9efc3b607993104b1991f90..b5d6dc0d864833880b59fc52a0b49d37b0904f98 100644
+--- a/src/main/java/org/bukkit/entity/minecart/CommandMinecart.java
++++ b/src/main/java/org/bukkit/entity/minecart/CommandMinecart.java
+@@ -4,6 +4,9 @@ import org.bukkit.entity.Minecart;
+ import org.jetbrains.annotations.NotNull;
+ import org.jetbrains.annotations.Nullable;
+
++/**
++ * Represents a minecart with command block
++ */
+ public interface CommandMinecart extends Minecart {
+
+ /**
+diff --git a/src/main/java/org/bukkit/event/Cancellable.java b/src/main/java/org/bukkit/event/Cancellable.java
+index 799b0b0f3cd842edd2bc1005c2e848f9a0b7b43c..7f02db9d1660b7b33d8c3825114b5040e5461696 100644
+--- a/src/main/java/org/bukkit/event/Cancellable.java
++++ b/src/main/java/org/bukkit/event/Cancellable.java
+@@ -1,5 +1,8 @@
+ package org.bukkit.event;
+
++/**
++ * Represents a cancellable event
++ */
+ public interface Cancellable {
+
+ /**
+diff --git a/src/main/java/org/bukkit/event/Event.java b/src/main/java/org/bukkit/event/Event.java
+index 8ec56cd6b8e0f5c5dd8c7c88b4671e18dcf109d0..740bbce54140480039a637c9fee6ccb3f4da1027 100644
+--- a/src/main/java/org/bukkit/event/Event.java
++++ b/src/main/java/org/bukkit/event/Event.java
+@@ -95,6 +95,9 @@ public abstract class Event {
+ return async;
+ }
+
++ /**
++ * Event result
++ */
+ public enum Result {
+
+ /**
+diff --git a/src/main/java/org/bukkit/event/EventException.java b/src/main/java/org/bukkit/event/EventException.java
+index 84638e852501cc804c13c188c90c38b163657c36..a32f7d86407a36d34932101a8b46751c5bed0d75 100644
+--- a/src/main/java/org/bukkit/event/EventException.java
++++ b/src/main/java/org/bukkit/event/EventException.java
+@@ -1,5 +1,8 @@
+ package org.bukkit.event;
+
++/**
++ * Event exception
++ */
+ public class EventException extends Exception {
+ private static final long serialVersionUID = 3532808232324183999L;
+ private final Throwable cause;
+diff --git a/src/main/java/org/bukkit/event/block/Action.java b/src/main/java/org/bukkit/event/block/Action.java
+index 25d26e3fe713311e66d7e634a6c32af61f4cef59..2825263c102d3f9ed37f6884e09ec5efb8105fb9 100644
+--- a/src/main/java/org/bukkit/event/block/Action.java
++++ b/src/main/java/org/bukkit/event/block/Action.java
+@@ -1,5 +1,8 @@
+ package org.bukkit.event.block;
+
++/**
++ * Block action
++ */
+ public enum Action {
+
+ /**
+diff --git a/src/main/java/org/bukkit/event/block/CauldronLevelChangeEvent.java b/src/main/java/org/bukkit/event/block/CauldronLevelChangeEvent.java
+index 4aaa78afdda2d2351f8c4ed46a52e0cf77ec437c..4d2d821003840b7fc1ca412d71b841341c7b51ec 100644
+--- a/src/main/java/org/bukkit/event/block/CauldronLevelChangeEvent.java
++++ b/src/main/java/org/bukkit/event/block/CauldronLevelChangeEvent.java
+@@ -8,6 +8,9 @@ import org.bukkit.event.HandlerList;
+ import org.jetbrains.annotations.NotNull;
+ import org.jetbrains.annotations.Nullable;
+
++/**
++ * Called when a cauldron changes fluid level
++ */
+ public class CauldronLevelChangeEvent extends BlockEvent implements Cancellable {
+
+ private static final HandlerList handlers = new HandlerList();
+@@ -75,6 +78,9 @@ public class CauldronLevelChangeEvent extends BlockEvent implements Cancellable
+ return handlers;
+ }
+
++ /**
++ * Cauldron level change reason
++ */
+ public enum ChangeReason {
+ /**
+ * Player emptying the cauldron by filling their bucket.
+diff --git a/src/main/java/org/bukkit/event/entity/EntityTransformEvent.java b/src/main/java/org/bukkit/event/entity/EntityTransformEvent.java
+index 7d23333e71482920cc42a4d8f3f38a7525aefe1f..765aa67f4cbb535128070d3310d1be9ecede3bf8 100644
+--- a/src/main/java/org/bukkit/event/entity/EntityTransformEvent.java
++++ b/src/main/java/org/bukkit/event/entity/EntityTransformEvent.java
+@@ -79,6 +79,9 @@ public class EntityTransformEvent extends EntityEvent implements Cancellable {
+ return handlers;
+ }
+
++ /**
++ * Entity transform reason
++ */
+ public enum TransformReason {
+ /**
+ * When a zombie gets cured and a villager is spawned.
+diff --git a/src/main/java/org/bukkit/event/entity/EntityUnleashEvent.java b/src/main/java/org/bukkit/event/entity/EntityUnleashEvent.java
+index a33986a0c437a673435206fc337031a7eebdab3b..99a8e452904de21a5bd82f13f6b2d46537d07289 100644
+--- a/src/main/java/org/bukkit/event/entity/EntityUnleashEvent.java
++++ b/src/main/java/org/bukkit/event/entity/EntityUnleashEvent.java
+@@ -37,6 +37,9 @@ public class EntityUnleashEvent extends EntityEvent {
+ return handlers;
+ }
+
++ /**
++ * Entity unleash reason
++ */
+ public enum UnleashReason {
+ /**
+ * When the entity's leashholder has died or logged out, and so is
+diff --git a/src/main/java/org/bukkit/event/entity/ItemMergeEvent.java b/src/main/java/org/bukkit/event/entity/ItemMergeEvent.java
+index e378cc29b47238fe12ae9aff5171edcff6b456f5..f5b9fd0b6f9512e425e1cc6103f80ba198c6db5b 100644
+--- a/src/main/java/org/bukkit/event/entity/ItemMergeEvent.java
++++ b/src/main/java/org/bukkit/event/entity/ItemMergeEvent.java
+@@ -5,6 +5,9 @@ import org.bukkit.event.Cancellable;
+ import org.bukkit.event.HandlerList;
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * Called when an item merges with another
++ */
+ public class ItemMergeEvent extends EntityEvent implements Cancellable {
+
+ private static final HandlerList handlers = new HandlerList();
+diff --git a/src/main/java/org/bukkit/event/entity/VillagerCareerChangeEvent.java b/src/main/java/org/bukkit/event/entity/VillagerCareerChangeEvent.java
+index b550029cf3a7bc55137851eab734abab8965306d..d070baf9587edccdd95204771f59491f5c4ba10d 100644
+--- a/src/main/java/org/bukkit/event/entity/VillagerCareerChangeEvent.java
++++ b/src/main/java/org/bukkit/event/entity/VillagerCareerChangeEvent.java
+@@ -6,6 +6,9 @@ import org.bukkit.event.Cancellable;
+ import org.bukkit.event.HandlerList;
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * Called when a villager changes career
++ */
+ public class VillagerCareerChangeEvent extends EntityEvent implements Cancellable {
+
+ private static final HandlerList handlers = new HandlerList();
+diff --git a/src/main/java/org/bukkit/event/inventory/InventoryCloseEvent.java b/src/main/java/org/bukkit/event/inventory/InventoryCloseEvent.java
+index 21ad8888c0e403bfc63518502577d651c02dda05..295cbe558ace7b55c80fc84256808d2f505ea734 100644
+--- a/src/main/java/org/bukkit/event/inventory/InventoryCloseEvent.java
++++ b/src/main/java/org/bukkit/event/inventory/InventoryCloseEvent.java
+@@ -18,6 +18,9 @@ public class InventoryCloseEvent extends InventoryEvent {
+ return reason;
+ }
+
++ /**
++ * Inventory close reason
++ */
+ public enum Reason {
+ /**
+ * Unknown reason
+diff --git a/src/main/java/org/bukkit/event/inventory/InventoryType.java b/src/main/java/org/bukkit/event/inventory/InventoryType.java
+index 2351283df16dac808d77b840aa88732d7b28c0a1..007d3e45095a5b6e35b6af681e6e042830a5432d 100644
+--- a/src/main/java/org/bukkit/event/inventory/InventoryType.java
++++ b/src/main/java/org/bukkit/event/inventory/InventoryType.java
+@@ -171,6 +171,9 @@ public enum InventoryType {
+ return isCreatable;
+ }
+
++ /**
++ * Inventory slot type
++ */
+ public enum SlotType {
+ /**
+ * A result slot in a furnace or crafting inventory.
+diff --git a/src/main/java/org/bukkit/event/inventory/PrepareItemCraftEvent.java b/src/main/java/org/bukkit/event/inventory/PrepareItemCraftEvent.java
+index efd29d198dd847e22988963f70ad57e1b810aeb7..b5de6e77a030057f923a5d82ea0054b9e138009d 100644
+--- a/src/main/java/org/bukkit/event/inventory/PrepareItemCraftEvent.java
++++ b/src/main/java/org/bukkit/event/inventory/PrepareItemCraftEvent.java
+@@ -7,6 +7,9 @@ import org.bukkit.inventory.Recipe;
+ import org.jetbrains.annotations.NotNull;
+ import org.jetbrains.annotations.Nullable;
+
++/**
++ * Prepare item craft event
++ */
+ public class PrepareItemCraftEvent extends InventoryEvent {
+ private static final HandlerList handlers = new HandlerList();
+ private boolean repair;
+diff --git a/src/main/java/org/bukkit/event/player/PlayerQuitEvent.java b/src/main/java/org/bukkit/event/player/PlayerQuitEvent.java
+index af52a5dfb452da11e51cad9c882cae1533cba520..30a7ac800096866d2a09dd304ffd17c42c5b2d9a 100644
+--- a/src/main/java/org/bukkit/event/player/PlayerQuitEvent.java
++++ b/src/main/java/org/bukkit/event/player/PlayerQuitEvent.java
+@@ -60,6 +60,9 @@ public class PlayerQuitEvent extends PlayerEvent {
+ return this.reason;
+ }
+
++ /**
++ * Player quit reason
++ */
+ public enum QuitReason {
+ /**
+ * The player left on their own behalf.
+diff --git a/src/main/java/org/bukkit/event/player/PlayerTeleportEvent.java b/src/main/java/org/bukkit/event/player/PlayerTeleportEvent.java
+index 553d7740489fe729166c8ca8ef8c7834db3663ad..4a2d61912ffed137b2b3e4cc4d9b32a11207f6ba 100644
+--- a/src/main/java/org/bukkit/event/player/PlayerTeleportEvent.java
++++ b/src/main/java/org/bukkit/event/player/PlayerTeleportEvent.java
+@@ -33,6 +33,9 @@ public class PlayerTeleportEvent extends PlayerMoveEvent {
+ return cause;
+ }
+
++ /**
++ * Player teleport cause
++ */
+ public enum TeleportCause {
+ /**
+ * Indicates the teleporation was caused by a player throwing an Ender
+diff --git a/src/main/java/org/bukkit/event/raid/RaidStopEvent.java b/src/main/java/org/bukkit/event/raid/RaidStopEvent.java
+index 9e852ac973d7a38c075249360be483ed0e5f5ac6..55db1a074144c709489d7f6a4e353b8fd94d312e 100644
+--- a/src/main/java/org/bukkit/event/raid/RaidStopEvent.java
++++ b/src/main/java/org/bukkit/event/raid/RaidStopEvent.java
+@@ -40,6 +40,9 @@ public class RaidStopEvent extends RaidEvent {
+ return handlers;
+ }
+
++ /**
++ * Raid stop reason
++ */
+ public enum Reason {
+
+ /**
+diff --git a/src/main/java/org/bukkit/event/weather/LightningStrikeEvent.java b/src/main/java/org/bukkit/event/weather/LightningStrikeEvent.java
+index 418f9391d86fff0d0a75da0574edccbb29aa9931..921d964d7e40e7710b5a5db18bd9329ca40c1ece 100644
+--- a/src/main/java/org/bukkit/event/weather/LightningStrikeEvent.java
++++ b/src/main/java/org/bukkit/event/weather/LightningStrikeEvent.java
+@@ -67,6 +67,9 @@ public class LightningStrikeEvent extends WeatherEvent implements Cancellable {
+ return handlers;
+ }
+
++ /**
++ * Lightning strike cause
++ */
+ public enum Cause {
+ /**
+ * Triggered by the /summon command.
+diff --git a/src/main/java/org/bukkit/help/HelpTopicComparator.java b/src/main/java/org/bukkit/help/HelpTopicComparator.java
+index 75bb69283f509e8f4fec772714a509a51be9de19..e156847f5b7b86155a7a0a0b8cefd8ac1530171e 100644
+--- a/src/main/java/org/bukkit/help/HelpTopicComparator.java
++++ b/src/main/java/org/bukkit/help/HelpTopicComparator.java
+@@ -31,6 +31,9 @@ public final class HelpTopicComparator implements Comparator {
+ return tnc.compare(lhs.getName(), rhs.getName());
+ }
+
++ /**
++ * Topic name comparator
++ */
+ public static final class TopicNameComparator implements Comparator {
+ private TopicNameComparator(){}
+
+diff --git a/src/main/java/org/bukkit/inventory/ArmoredHorseInventory.java b/src/main/java/org/bukkit/inventory/ArmoredHorseInventory.java
+index 163ffe8ff76ded6265d865901d5110fb6a56950d..36145294db34d273bb767cc928453b765a30e9db 100644
+--- a/src/main/java/org/bukkit/inventory/ArmoredHorseInventory.java
++++ b/src/main/java/org/bukkit/inventory/ArmoredHorseInventory.java
+@@ -2,6 +2,9 @@ package org.bukkit.inventory;
+
+ import org.jetbrains.annotations.Nullable;
+
++/**
++ * Represents an armored horse's inventory
++ */
+ public interface ArmoredHorseInventory extends AbstractHorseInventory {
+
+ /**
+diff --git a/src/main/java/org/bukkit/inventory/EquipmentSlot.java b/src/main/java/org/bukkit/inventory/EquipmentSlot.java
+index 1e7d77118a55ca9db99eabb94894e6ef3409946b..ad7db4407c83b19bc8ecc9b849152af42d5c4ddb 100644
+--- a/src/main/java/org/bukkit/inventory/EquipmentSlot.java
++++ b/src/main/java/org/bukkit/inventory/EquipmentSlot.java
+@@ -1,5 +1,8 @@
+ package org.bukkit.inventory;
+
++/**
++ * Equipment slot
++ */
+ public enum EquipmentSlot {
+
+ HAND,
+diff --git a/src/main/java/org/bukkit/inventory/InventoryHolder.java b/src/main/java/org/bukkit/inventory/InventoryHolder.java
+index c7b17eabf07b829a02afe7c1f27a5127b6bfea70..d4e2bcf8ce8fc2af851b471490147f0092ea456a 100644
+--- a/src/main/java/org/bukkit/inventory/InventoryHolder.java
++++ b/src/main/java/org/bukkit/inventory/InventoryHolder.java
+@@ -2,6 +2,9 @@ package org.bukkit.inventory;
+
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * Represents an inventory holder
++ */
+ public interface InventoryHolder {
+
+ /**
+diff --git a/src/main/java/org/bukkit/inventory/ItemFactory.java b/src/main/java/org/bukkit/inventory/ItemFactory.java
+index 23d55f756b2bb5a557bfae102d7039d8394fbe69..50e58cf9a494c2cf17b7f55918e3d21f63437b3c 100644
+--- a/src/main/java/org/bukkit/inventory/ItemFactory.java
++++ b/src/main/java/org/bukkit/inventory/ItemFactory.java
+@@ -169,7 +169,7 @@ public interface ItemFactory {
+ /**
+ * Creates a {@link net.md_5.bungee.api.chat.hover.content.Content} of that ItemStack for displaying.
+ *
+- * @param itemStack
++ * @param itemStack ItemStack to check
+ * @return the {@link net.md_5.bungee.api.chat.hover.content.Content} of that ItemStack
+ */
+ @NotNull
+diff --git a/src/main/java/org/bukkit/inventory/SaddledHorseInventory.java b/src/main/java/org/bukkit/inventory/SaddledHorseInventory.java
+index 7944f26a3e2a92601c3be0e55c00c39cc16cf177..8e7bb66c96d34b73959c0653b2a8e7b422da35fe 100644
+--- a/src/main/java/org/bukkit/inventory/SaddledHorseInventory.java
++++ b/src/main/java/org/bukkit/inventory/SaddledHorseInventory.java
+@@ -1,3 +1,6 @@
+ package org.bukkit.inventory;
+
++/**
++ * Represents a saddled horse's inventory
++ */
+ public interface SaddledHorseInventory extends AbstractHorseInventory {}
+diff --git a/src/main/java/org/bukkit/inventory/meta/BannerMeta.java b/src/main/java/org/bukkit/inventory/meta/BannerMeta.java
+index 4739d2ecc26e7e4adc1b297013da98e12fe58783..45ebb3ca8d628b708419bd2beedd94ee4c819b8a 100644
+--- a/src/main/java/org/bukkit/inventory/meta/BannerMeta.java
++++ b/src/main/java/org/bukkit/inventory/meta/BannerMeta.java
+@@ -6,6 +6,9 @@ import org.bukkit.block.banner.Pattern;
+ import org.jetbrains.annotations.NotNull;
+ import org.jetbrains.annotations.Nullable;
+
++/**
++ * Represents metadata on a banner
++ */
+ public interface BannerMeta extends ItemMeta {
+
+ /**
+diff --git a/src/main/java/org/bukkit/inventory/meta/BlockDataMeta.java b/src/main/java/org/bukkit/inventory/meta/BlockDataMeta.java
+index 473c72dcd34d3f6be72e2ab87c5af51819a00e33..a73b59f40eb3c4d94074154591f9f6885fb287ca 100644
+--- a/src/main/java/org/bukkit/inventory/meta/BlockDataMeta.java
++++ b/src/main/java/org/bukkit/inventory/meta/BlockDataMeta.java
+@@ -4,6 +4,9 @@ import org.bukkit.Material;
+ import org.bukkit.block.data.BlockData;
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * Represents metadata on a block
++ */
+ public interface BlockDataMeta extends ItemMeta {
+
+ /**
+diff --git a/src/main/java/org/bukkit/inventory/meta/BlockStateMeta.java b/src/main/java/org/bukkit/inventory/meta/BlockStateMeta.java
+index e7d905b1146b2bdd2da5bdeb6bf3541fb181d59e..1fab68c9de96b0d362ebf85fd675cc19099aefa1 100644
+--- a/src/main/java/org/bukkit/inventory/meta/BlockStateMeta.java
++++ b/src/main/java/org/bukkit/inventory/meta/BlockStateMeta.java
+@@ -4,6 +4,9 @@ package org.bukkit.inventory.meta;
+ import org.bukkit.block.BlockState;
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * Represents metadata on a blockstate
++ */
+ public interface BlockStateMeta extends ItemMeta {
+
+ /**
+diff --git a/src/main/java/org/bukkit/inventory/meta/BookMeta.java b/src/main/java/org/bukkit/inventory/meta/BookMeta.java
+index 94852d50e88d0594b84b581cd627174043629995..c63257f38dffd05977e3676e7c341123f01fe282 100644
+--- a/src/main/java/org/bukkit/inventory/meta/BookMeta.java
++++ b/src/main/java/org/bukkit/inventory/meta/BookMeta.java
+@@ -188,6 +188,9 @@ public interface BookMeta extends ItemMeta {
+ BookMeta clone();
+
+ // Spigot start
++ /**
++ * Spigot stuffs
++ */
+ public class Spigot {
+
+ /**
+diff --git a/src/main/java/org/bukkit/inventory/meta/CrossbowMeta.java b/src/main/java/org/bukkit/inventory/meta/CrossbowMeta.java
+index 35c6594fd1040a1af1029e7260e5e3a9307b107d..47975b24ffa01c9872f6e910d14e1c8e0d0481b9 100644
+--- a/src/main/java/org/bukkit/inventory/meta/CrossbowMeta.java
++++ b/src/main/java/org/bukkit/inventory/meta/CrossbowMeta.java
+@@ -5,6 +5,9 @@ import org.bukkit.inventory.ItemStack;
+ import org.jetbrains.annotations.NotNull;
+ import org.jetbrains.annotations.Nullable;
+
++/**
++ * Represents metadata on a crossbow
++ */
+ public interface CrossbowMeta extends ItemMeta {
+
+ /**
+diff --git a/src/main/java/org/bukkit/inventory/meta/KnowledgeBookMeta.java b/src/main/java/org/bukkit/inventory/meta/KnowledgeBookMeta.java
+index 736c60c71d112e8c017473a93091b4e5336a996f..88c7b311128d605c8d33e1b075795a3a1a434fa5 100644
+--- a/src/main/java/org/bukkit/inventory/meta/KnowledgeBookMeta.java
++++ b/src/main/java/org/bukkit/inventory/meta/KnowledgeBookMeta.java
+@@ -4,6 +4,9 @@ import java.util.List;
+ import org.bukkit.NamespacedKey;
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * Represents metadata on a knowledge book
++ */
+ public interface KnowledgeBookMeta extends ItemMeta {
+
+ /**
+diff --git a/src/main/java/org/bukkit/material/CocoaPlant.java b/src/main/java/org/bukkit/material/CocoaPlant.java
+index b1b1c729d182b676d8ea69a8d3c942c6820863dd..222c2ae29bc150bbc44c74885b6565911a666911 100644
+--- a/src/main/java/org/bukkit/material/CocoaPlant.java
++++ b/src/main/java/org/bukkit/material/CocoaPlant.java
+@@ -12,6 +12,9 @@ import org.bukkit.block.BlockFace;
+ @Deprecated
+ public class CocoaPlant extends MaterialData implements Directional, Attachable {
+
++ /**
++ * Cocoa plant size
++ */
+ public enum CocoaPlantSize {
+ SMALL,
+ MEDIUM,
+diff --git a/src/main/java/org/bukkit/material/Directional.java b/src/main/java/org/bukkit/material/Directional.java
+index 8c1c7b0a258bd4e601955827c4f5a72b81a60db2..f188563dd0db1d7e1dab5e1cce5d76339061df3e 100644
+--- a/src/main/java/org/bukkit/material/Directional.java
++++ b/src/main/java/org/bukkit/material/Directional.java
+@@ -3,6 +3,9 @@ package org.bukkit.material;
+ import org.bukkit.block.BlockFace;
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * Represents something that can face a direction
++ */
+ public interface Directional {
+
+ /**
+diff --git a/src/main/java/org/bukkit/material/Openable.java b/src/main/java/org/bukkit/material/Openable.java
+index 0ae54f973d11df74abb3105cf9226afb130b4f33..6541bca9c6c4ccedf059d2297b54b738588a02dc 100644
+--- a/src/main/java/org/bukkit/material/Openable.java
++++ b/src/main/java/org/bukkit/material/Openable.java
+@@ -1,5 +1,8 @@
+ package org.bukkit.material;
+
++/**
++ * Represents something that can be opened
++ */
+ public interface Openable {
+
+ /**
+diff --git a/src/main/java/org/bukkit/material/PressureSensor.java b/src/main/java/org/bukkit/material/PressureSensor.java
+index de20bd39c532e94a11536a67c1af71bea203aedc..aa14be496bfe05bf3882f8ac50ef88b8ad655302 100644
+--- a/src/main/java/org/bukkit/material/PressureSensor.java
++++ b/src/main/java/org/bukkit/material/PressureSensor.java
+@@ -1,5 +1,8 @@
+ package org.bukkit.material;
+
++/**
++ * Represents a pressure sensor
++ */
+ public interface PressureSensor {
+ public boolean isPressed();
+ }
+diff --git a/src/main/java/org/bukkit/metadata/MetadataStore.java b/src/main/java/org/bukkit/metadata/MetadataStore.java
+index 29f86fa938c2758cbdf8dec22519a18c3e119818..8fca91925ce7d3fdcec838a3f1c9ba3e4ddc5a9c 100644
+--- a/src/main/java/org/bukkit/metadata/MetadataStore.java
++++ b/src/main/java/org/bukkit/metadata/MetadataStore.java
+@@ -4,6 +4,11 @@ import java.util.List;
+ import org.bukkit.plugin.Plugin;
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * Metadata store
++ *
++ * @param Type
++ */
+ public interface MetadataStore {
+ /**
+ * Adds a metadata value to an object.
+diff --git a/src/main/java/org/bukkit/metadata/MetadataStoreBase.java b/src/main/java/org/bukkit/metadata/MetadataStoreBase.java
+index abbe545af572687a0399c2387434863cd2b70f68..81024450c3cf28657e2c38fd164dad034f47af22 100644
+--- a/src/main/java/org/bukkit/metadata/MetadataStoreBase.java
++++ b/src/main/java/org/bukkit/metadata/MetadataStoreBase.java
+@@ -12,6 +12,11 @@ import org.apache.commons.lang.Validate;
+ import org.bukkit.plugin.Plugin;
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * Metadata store base
++ *
++ * @param Type
++ */
+ public abstract class MetadataStoreBase {
+ private Map> metadataMap = new java.util.concurrent.ConcurrentHashMap>(); // Paper
+
+diff --git a/src/main/java/org/bukkit/metadata/MetadataValue.java b/src/main/java/org/bukkit/metadata/MetadataValue.java
+index 4b4d57924b8b2aecf4ebf92edc805334ffa53d0e..9df3d1c71a399c4d3f610bcd96aa401b4ea0c708 100644
+--- a/src/main/java/org/bukkit/metadata/MetadataValue.java
++++ b/src/main/java/org/bukkit/metadata/MetadataValue.java
+@@ -4,6 +4,9 @@ import org.bukkit.plugin.Plugin;
+ import org.jetbrains.annotations.NotNull;
+ import org.jetbrains.annotations.Nullable;
+
++/**
++ * Metadata value
++ */
+ public interface MetadataValue {
+
+ /**
+diff --git a/src/main/java/org/bukkit/plugin/AuthorNagException.java b/src/main/java/org/bukkit/plugin/AuthorNagException.java
+index 6565a441467e323b3e1871485a9e09e4cfbea050..20985f022afa077ba0907f3404175cb4500fa29f 100644
+--- a/src/main/java/org/bukkit/plugin/AuthorNagException.java
++++ b/src/main/java/org/bukkit/plugin/AuthorNagException.java
+@@ -1,5 +1,8 @@
+ package org.bukkit.plugin;
+
++/**
++ * Author nag exception
++ */
+ @SuppressWarnings("serial")
+ public class AuthorNagException extends RuntimeException {
+ private final String message;
+diff --git a/src/main/java/org/bukkit/projectiles/BlockProjectileSource.java b/src/main/java/org/bukkit/projectiles/BlockProjectileSource.java
+index 21a3d767baf9f76746b2a5f2b3af134fe1e96e8a..6d7a29554f337333f4cf6095d9d0ca9e275f8f4f 100644
+--- a/src/main/java/org/bukkit/projectiles/BlockProjectileSource.java
++++ b/src/main/java/org/bukkit/projectiles/BlockProjectileSource.java
+@@ -3,6 +3,9 @@ package org.bukkit.projectiles;
+ import org.bukkit.block.Block;
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * Represents a block projectile source
++ */
+ public interface BlockProjectileSource extends ProjectileSource {
+
+ /**
diff --git a/patches/Purpur/patches/api/0035-PlayerBookTooLargeEvent.patch b/patches/Purpur/patches/api/0035-PlayerBookTooLargeEvent.patch
new file mode 100644
index 00000000..dc89fde7
--- /dev/null
+++ b/patches/Purpur/patches/api/0035-PlayerBookTooLargeEvent.patch
@@ -0,0 +1,77 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: BillyGalbreath
+Date: Wed, 23 Dec 2020 00:43:27 -0600
+Subject: [PATCH] PlayerBookTooLargeEvent
+
+
+diff --git a/src/main/java/net/pl3x/purpur/event/player/PlayerBookTooLargeEvent.java b/src/main/java/net/pl3x/purpur/event/player/PlayerBookTooLargeEvent.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..39378ee2bfadf42ff358cc7b42dd75ac61615e15
+--- /dev/null
++++ b/src/main/java/net/pl3x/purpur/event/player/PlayerBookTooLargeEvent.java
+@@ -0,0 +1,65 @@
++package net.pl3x.purpur.event.player;
++
++import org.bukkit.Bukkit;
++import org.bukkit.entity.Player;
++import org.bukkit.event.HandlerList;
++import org.bukkit.event.player.PlayerEvent;
++import org.bukkit.inventory.ItemStack;
++import org.jetbrains.annotations.NotNull;
++
++/**
++ * Called when a player tries to bypass book limitations
++ */
++public class PlayerBookTooLargeEvent extends PlayerEvent {
++ private static final HandlerList handlers = new HandlerList();
++ private final ItemStack book;
++ private boolean kickPlayer = true;
++
++ /**
++ * @param player The player
++ * @param book The book
++ */
++ public PlayerBookTooLargeEvent(@NotNull Player player, @NotNull ItemStack book) {
++ super(player, !Bukkit.isPrimaryThread());
++ this.book = book;
++ }
++
++ /**
++ * Get the book containing the wanted edits
++ *
++ * @return The book
++ */
++ @NotNull
++ public ItemStack getBook() {
++ return book;
++ }
++
++ /**
++ * Whether server should kick the player or not
++ *
++ * @return True to kick player
++ */
++ public boolean shouldKickPlayer() {
++ return kickPlayer;
++ }
++
++ /**
++ * Whether server should kick the player or not
++ *
++ * @param kickPlayer True to kick player
++ */
++ public void setShouldKickPlayer(boolean kickPlayer) {
++ this.kickPlayer = kickPlayer;
++ }
++
++ @Override
++ @NotNull
++ public HandlerList getHandlers() {
++ return handlers;
++ }
++
++ @NotNull
++ public static HandlerList getHandlerList() {
++ return handlers;
++ }
++}
diff --git a/patches/Purpur/patches/api/0036-Full-netherite-armor-grants-fire-resistance.patch b/patches/Purpur/patches/api/0036-Full-netherite-armor-grants-fire-resistance.patch
new file mode 100644
index 00000000..4cf6d1a0
--- /dev/null
+++ b/patches/Purpur/patches/api/0036-Full-netherite-armor-grants-fire-resistance.patch
@@ -0,0 +1,23 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: BillyGalbreath
+Date: Thu, 24 Dec 2020 11:00:04 -0600
+Subject: [PATCH] Full netherite armor grants fire resistance
+
+
+diff --git a/src/main/java/org/bukkit/event/entity/EntityPotionEffectEvent.java b/src/main/java/org/bukkit/event/entity/EntityPotionEffectEvent.java
+index 16b5fd279b0cb926900247618bcdb381a93f5a35..d592c62aadb3245396865c098c5979f2a162f868 100644
+--- a/src/main/java/org/bukkit/event/entity/EntityPotionEffectEvent.java
++++ b/src/main/java/org/bukkit/event/entity/EntityPotionEffectEvent.java
+@@ -213,6 +213,12 @@ public class EntityPotionEffectEvent extends EntityEvent implements Cancellable
+ * When all effects are removed due to a bucket of milk.
+ */
+ MILK,
++ // Purpur start
++ /**
++ * When a player wears full netherite armor
++ */
++ NETHERITE_ARMOR,
++ // Purpur end
+ /**
+ * When a player gets bad omen after killing a patrol captain.
+ */
diff --git a/patches/Purpur/patches/api/0037-Add-EntityTeleportHinderedEvent.patch b/patches/Purpur/patches/api/0037-Add-EntityTeleportHinderedEvent.patch
new file mode 100644
index 00000000..587b3f0e
--- /dev/null
+++ b/patches/Purpur/patches/api/0037-Add-EntityTeleportHinderedEvent.patch
@@ -0,0 +1,141 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Mariell Hoversholm
+Date: Sat, 9 Jan 2021 15:26:04 +0100
+Subject: [PATCH] Add EntityTeleportHinderedEvent
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+
+diff --git a/src/main/java/net/pl3x/purpur/event/entity/EntityTeleportHinderedEvent.java b/src/main/java/net/pl3x/purpur/event/entity/EntityTeleportHinderedEvent.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..e6fcc4027cb70061b804460b39e00cca273d35ad
+--- /dev/null
++++ b/src/main/java/net/pl3x/purpur/event/entity/EntityTeleportHinderedEvent.java
+@@ -0,0 +1,117 @@
++package net.pl3x.purpur.event.entity;
++
++import org.bukkit.entity.Entity;
++import org.bukkit.event.HandlerList;
++import org.bukkit.event.entity.EntityEvent;
++import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Nullable;
++
++/**
++ * Fired when an entity is hindered from teleporting.
++ */
++public class EntityTeleportHinderedEvent extends EntityEvent {
++ private static final HandlerList handlers = new HandlerList();
++
++ @NotNull
++ private final Reason reason;
++
++ @Nullable
++ private final TeleportCause teleportCause;
++
++ private boolean retry = false;
++
++ public EntityTeleportHinderedEvent(@NotNull Entity what, @NotNull Reason reason,
++ @Nullable TeleportCause teleportCause) {
++ super(what);
++ this.reason = reason;
++ this.teleportCause = teleportCause;
++ }
++
++ /**
++ * @return why the teleport was hindered.
++ */
++ @NotNull
++ public Reason getReason() {
++ return reason;
++ }
++
++ /**
++ * @return why the teleport occurred if cause was given, otherwise {@code null}.
++ */
++ @Nullable
++ public TeleportCause getTeleportCause() {
++ return teleportCause;
++ }
++
++ /**
++ * Whether the teleport should be retried.
++ *
++ * Note that this can put the server in a never-ending loop of trying to teleport someone resulting in a stack
++ * overflow. Do not retry more than necessary.
++ *
++ *
++ * @return whether the teleport should be retried.
++ */
++ public boolean shouldRetry() {
++ return retry;
++ }
++
++ /**
++ * Sets whether the teleport should be retried.
++ *
++ * Note that this can put the server in a never-ending loop of trying to teleport someone resulting in a stack
++ * overflow. Do not retry more than necessary.
++ *
++ *
++ * @param retry whether the teleport should be retried.
++ */
++ public void setShouldRetry(boolean retry) {
++ this.retry = retry;
++ }
++
++ /**
++ * Calls the event and tests if should retry.
++ *
++ * @return whether the teleport should be retried.
++ */
++ @Override
++ public boolean callEvent() {
++ super.callEvent();
++ return shouldRetry();
++ }
++
++ @Override
++ @NotNull
++ public HandlerList getHandlers() {
++ return handlers;
++ }
++
++ @NotNull
++ public static HandlerList getHandlerList() {
++ return handlers;
++ }
++
++ /**
++ * Reason for hindrance in teleports.
++ */
++ public enum Reason {
++ /**
++ * The teleported entity is a passenger of another entity.
++ */
++ IS_PASSENGER,
++
++ /**
++ * The teleported entity has passengers.
++ */
++ IS_VEHICLE,
++
++ /**
++ * The teleport event was cancelled.
++ *
++ * This is only caused by players teleporting.
++ *
++ */
++ EVENT_CANCELLED,
++ }
++}
diff --git a/patches/Purpur/patches/api/0038-Add-StructureGenerateEvent.patch b/patches/Purpur/patches/api/0038-Add-StructureGenerateEvent.patch
new file mode 100644
index 00000000..6ef11884
--- /dev/null
+++ b/patches/Purpur/patches/api/0038-Add-StructureGenerateEvent.patch
@@ -0,0 +1,112 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Nahuel
+Date: Sat, 9 Jan 2021 15:33:52 +0100
+Subject: [PATCH] Add StructureGenerateEvent
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+
+Co-authored-by: Mariell Hoversholm
+
+diff --git a/src/main/java/net/pl3x/purpur/event/world/StructureGenerateEvent.java b/src/main/java/net/pl3x/purpur/event/world/StructureGenerateEvent.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..e77f45f761368da9b230c425d975a717cf4d10fd
+--- /dev/null
++++ b/src/main/java/net/pl3x/purpur/event/world/StructureGenerateEvent.java
+@@ -0,0 +1,68 @@
++package net.pl3x.purpur.event.world;
++
++import org.bukkit.Bukkit;
++import org.bukkit.StructureType;
++import org.bukkit.World;
++import org.bukkit.event.Cancellable;
++import org.bukkit.event.HandlerList;
++import org.bukkit.event.world.WorldEvent;
++import org.jetbrains.annotations.NotNull;
++
++public class StructureGenerateEvent extends WorldEvent implements Cancellable {
++ private static final HandlerList handlers = new HandlerList();
++
++ @NotNull
++ private final StructureType structureType;
++ private final int chunkX;
++ private final int chunkZ;
++
++ private boolean cancel = false;
++
++ public StructureGenerateEvent(@NotNull World world,
++ @NotNull StructureType structureType, int chunkX, int chunkZ) {
++ super(!Bukkit.isPrimaryThread(), world); // Structure generation is not necessarily on the main thread as of 1.16.
++ this.structureType = structureType;
++ this.chunkX = chunkX;
++ this.chunkZ = chunkZ;
++ }
++
++ @NotNull
++ @Override
++ public World getWorld() {
++ return super.getWorld();
++ }
++
++ @NotNull
++ public StructureType getStructureType() {
++ return structureType;
++ }
++
++ public int getChunkX() {
++ return chunkX;
++ }
++
++ public int getChunkZ() {
++ return chunkZ;
++ }
++
++ @Override
++ public void setCancelled(boolean cancel) {
++ this.cancel = cancel;
++ }
++
++ @Override
++ public boolean isCancelled() {
++ return this.cancel;
++ }
++
++ @NotNull
++ @Override
++ public HandlerList getHandlers() {
++ return handlers;
++ }
++
++ @NotNull
++ public static HandlerList getHandlerList() {
++ return handlers;
++ }
++}
+diff --git a/src/main/java/org/bukkit/event/world/WorldEvent.java b/src/main/java/org/bukkit/event/world/WorldEvent.java
+index cffeff33f007d3b03b7c862b25be453f705da739..1fa083d53dce161ef9e9f19407f230c94b2d7d15 100644
+--- a/src/main/java/org/bukkit/event/world/WorldEvent.java
++++ b/src/main/java/org/bukkit/event/world/WorldEvent.java
+@@ -10,6 +10,13 @@ import org.jetbrains.annotations.NotNull;
+ public abstract class WorldEvent extends Event {
+ private final World world;
+
++ // Purpur start
++ public WorldEvent(boolean isAsync, @NotNull final World world) {
++ super(isAsync);
++ this.world = world;
++ }
++ // Purpur end
++
+ public WorldEvent(@NotNull final World world) {
+ this.world = world;
+ }
diff --git a/patches/Purpur/patches/server/0001-Rebrand.patch b/patches/Purpur/patches/server/0001-Rebrand.patch
new file mode 100644
index 00000000..3603fd92
--- /dev/null
+++ b/patches/Purpur/patches/server/0001-Rebrand.patch
@@ -0,0 +1,222 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sat, 4 May 2019 01:02:11 -0500
+Subject: [PATCH] Rebrand
+
+
+diff --git a/pom.xml b/pom.xml
+index e83e4241a56fe131a75fe21cc1518992c089da2c..752d62eb3b87ab24260ec2c029bae0d2b0e3b908 100644
+--- a/pom.xml
++++ b/pom.xml
+@@ -27,8 +27,10 @@
+
+
+
+- com.tuinity
+- tuinity-api
++
++ net.pl3x.purpur
++ purpur-api
++
+ ${project.version}
+ compile
+
+diff --git a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java
+index 74ed02fa9296583977bb721014b10ff8b708b43c..c1280478ee4565003883df9607d4a8a0e8fe4faa 100644
+--- a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java
++++ b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java
+@@ -17,7 +17,7 @@ public final class PaperConsole extends SimpleTerminalConsole {
+ @Override
+ protected LineReader buildReader(LineReaderBuilder builder) {
+ return super.buildReader(builder
+- .appName("Paper")
++ .appName("Purpur") // Purpur
+ .variable(LineReader.HISTORY_FILE, java.nio.file.Paths.get(".console_history"))
+ .completer(new ConsoleCommandCompleter(this.server))
+ );
+diff --git a/src/main/java/net/minecraft/server/EULA.java b/src/main/java/net/minecraft/server/EULA.java
+index 229c3b0f0c650b501f31147adaa17194af57fedd..f88cf526d272fe47b5a474c0b344b748ee4009fa 100644
+--- a/src/main/java/net/minecraft/server/EULA.java
++++ b/src/main/java/net/minecraft/server/EULA.java
+@@ -70,7 +70,7 @@ public class EULA {
+ Properties properties = new Properties();
+
+ properties.setProperty("eula", "false");
+- properties.store(outputstream, "By changing the setting below to TRUE you are indicating your agreement to our EULA (https://account.mojang.com/documents/minecraft_eula)."); // Paper - fix lag; // Tuinity - Tacos are disgusting
++ properties.store(outputstream, "By changing the setting below to TRUE you are indicating your agreement to our EULA (https://account.mojang.com/documents/minecraft_eula).\nYou also agree that tacos are tasty, and the best food in the world."); // Paper - fix lag; // Tuinity - Tacos are disgusting // Purpur - no they're not
+ } catch (Throwable throwable1) {
+ throwable = throwable1;
+ throw throwable1;
+diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
+index 45e310e249a83714d0001d85b2ead8d4f8a2d742..af5c1479d2cb8092d84e2d3d5166060d9ff2df71 100644
+--- a/src/main/java/net/minecraft/server/MinecraftServer.java
++++ b/src/main/java/net/minecraft/server/MinecraftServer.java
+@@ -1511,7 +1511,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant // Spigot - Spigot > // CraftBukkit - cb > vanilla!
++ return "Purpur"; // Purpur // Tuinity // Paper // Spigot // CraftBukkit
+ }
+
+ public CrashReport b(CrashReport crashreport) {
+diff --git a/src/main/java/net/pl3x/purpur/PurpurVersionFetcher.java b/src/main/java/net/pl3x/purpur/PurpurVersionFetcher.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..d8b408f061d96e2fa8e2e587462e2221aaee80ce
+--- /dev/null
++++ b/src/main/java/net/pl3x/purpur/PurpurVersionFetcher.java
+@@ -0,0 +1,115 @@
++package net.pl3x.purpur;
++
++import com.destroystokyo.paper.VersionHistoryManager;
++import com.destroystokyo.paper.util.VersionFetcher;
++import com.google.common.base.Charsets;
++import com.google.common.io.Resources;
++import com.google.gson.Gson;
++import com.google.gson.JsonObject;
++import com.google.gson.JsonSyntaxException;
++
++import javax.annotation.Nonnull;
++import javax.annotation.Nullable;
++import java.io.BufferedReader;
++import java.io.IOException;
++import java.io.InputStreamReader;
++import java.net.HttpURLConnection;
++import java.net.URL;
++
++public class PurpurVersionFetcher implements VersionFetcher {
++ private static final String JENKINS_URL = "https://ci.pl3x.net/job/Purpur/lastSuccessfulBuild/buildNumber";
++ private static final String GITHUB_BRANCH_NAME = "master";
++
++ @Override
++ public long getCacheTime() {
++ return 720000;
++ }
++
++ @Nonnull
++ @Override
++ public String getVersionMessage(@Nonnull String serverVersion) {
++ String[] parts = serverVersion.substring("git-Purpur-".length()).split("[-\\s]");
++ String updateMessage = getUpdateStatusMessage("pl3xgaming/Purpur", GITHUB_BRANCH_NAME, parts[0]);
++ String history = getHistory();
++
++ return history != null ? history + "\n" + updateMessage : updateMessage;
++ }
++
++ private static String getUpdateStatusMessage(@Nonnull String repo, @Nonnull String branch, @Nonnull String versionInfo) {
++ int distance;
++ try {
++ int jenkinsBuild = Integer.parseInt(versionInfo);
++ distance = fetchDistanceFromJenkins(jenkinsBuild);
++ } catch (NumberFormatException ignored) {
++ versionInfo = versionInfo.replace("\"", "");
++ distance = fetchDistanceFromGitHub(repo, branch, versionInfo);
++ }
++
++ switch (distance) {
++ case -1:
++ return "Error obtaining version information";
++ case 0:
++ return "You are running the latest version";
++ case -2:
++ return "Unknown version";
++ default:
++ return "You are " + distance + " version(s) behind";
++ }
++ }
++
++ private static int fetchDistanceFromJenkins(int jenkinsBuild) {
++ try {
++ try (BufferedReader reader = Resources.asCharSource(new URL(JENKINS_URL), Charsets.UTF_8).openBufferedStream()) {
++ return Integer.decode(reader.readLine()) - jenkinsBuild;
++ } catch (NumberFormatException ex) {
++ ex.printStackTrace();
++ return -2;
++ }
++ } catch (IOException e) {
++ e.printStackTrace();
++ return -1;
++ }
++ }
++
++ // Contributed by Techcable in GH-65
++ private static int fetchDistanceFromGitHub(@Nonnull String repo, @Nonnull String branch, @Nonnull String hash) {
++ try {
++ HttpURLConnection connection = (HttpURLConnection) new URL("https://api.github.com/repos/" + repo + "/compare/" + branch + "..." + hash).openConnection();
++ connection.connect();
++ if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) return -2; // Unknown commit
++ try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charsets.UTF_8))) {
++ JsonObject obj = new Gson().fromJson(reader, JsonObject.class);
++ String status = obj.get("status").getAsString();
++ switch (status) {
++ case "identical":
++ return 0;
++ case "behind":
++ return obj.get("behind_by").getAsInt();
++ default:
++ return -1;
++ }
++ } catch (JsonSyntaxException | NumberFormatException e) {
++ e.printStackTrace();
++ return -1;
++ }
++ } catch (IOException e) {
++ e.printStackTrace();
++ return -1;
++ }
++ }
++
++ @Nullable
++ private String getHistory() {
++ final VersionHistoryManager.VersionData data = VersionHistoryManager.INSTANCE.getVersionData();
++ if (data == null) {
++ return null;
++ }
++
++ final String oldVersion = data.getOldVersion();
++ if (oldVersion == null) {
++ return null;
++ }
++
++ return "Previous version: " + oldVersion;
++ }
++}
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+index bd7bccbea1a0a052ef7bd6ab299ae72336874911..5100460bab83cd75ac8dcdcc50ea663b1c486d00 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+@@ -232,7 +232,7 @@ import javax.annotation.Nullable; // Paper
+ import javax.annotation.Nonnull; // Paper
+
+ public final class CraftServer implements Server {
+- private final String serverName = "Tuinity"; // Paper // Tuinity
++ private final String serverName = "Purpur"; // Paper // Tuinity // Purpur
+ private final String serverVersion;
+ private final String bukkitVersion = Versioning.getBukkitVersion();
+ private final Logger logger = Logger.getLogger("Minecraft");
+diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+index ac5003dc827217bd1947c71044abcbcbd2210dcd..37c561fb775cf7dd955b185b4ea94fecc574be63 100644
+--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+@@ -370,7 +370,7 @@ public final class CraftMagicNumbers implements UnsafeValues {
+
+ @Override
+ public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() {
+- return new com.destroystokyo.paper.PaperVersionFetcher();
++ return new net.pl3x.purpur.PurpurVersionFetcher();
+ }
+
+ @Override
+diff --git a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java
+index 001b1e5197eaa51bfff9031aa6c69876c9a47960..13b98439320ac1401a920c01d7cf5a4b3a23deff 100644
+--- a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java
++++ b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java
+@@ -11,7 +11,7 @@ public final class Versioning {
+ public static String getBukkitVersion() {
+ String result = "Unknown-Version";
+
+- InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/com.tuinity/tuinity-api/pom.properties"); // Tuinity
++ InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/net.pl3x.purpur/purpur-api/pom.properties"); // Tuinity // Purpur
+ Properties properties = new Properties();
+
+ if (stream != null) {
diff --git a/patches/Purpur/patches/server/0002-Purpur-config-files.patch b/patches/Purpur/patches/server/0002-Purpur-config-files.patch
new file mode 100644
index 00000000..d5809047
--- /dev/null
+++ b/patches/Purpur/patches/server/0002-Purpur-config-files.patch
@@ -0,0 +1,440 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Thu, 9 May 2019 18:09:43 -0500
+Subject: [PATCH] Purpur config files
+
+
+diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java
+index 52c0ab1ce46e1f3233ef746d9bc699356fa9fae4..4d8740678049aa749b42618470e9cc838555528d 100644
+--- a/src/main/java/com/destroystokyo/paper/Metrics.java
++++ b/src/main/java/com/destroystokyo/paper/Metrics.java
+@@ -593,7 +593,7 @@ public class Metrics {
+ boolean logFailedRequests = config.getBoolean("logFailedRequests", false);
+ // Only start Metrics, if it's enabled in the config
+ if (config.getBoolean("enabled", true)) {
+- Metrics metrics = new Metrics("Tuinity", serverUUID, logFailedRequests, Bukkit.getLogger()); // Tuinity - we have our own bstats page
++ Metrics metrics = new Metrics("Purpur", serverUUID, logFailedRequests, Bukkit.getLogger()); // Purpur
+
+ metrics.addCustomChart(new Metrics.SimplePie("minecraft_version", () -> {
+ String minecraftVersion = Bukkit.getVersion();
+@@ -602,8 +602,8 @@ public class Metrics {
+ }));
+
+ metrics.addCustomChart(new Metrics.SingleLineChart("players", () -> Bukkit.getOnlinePlayers().size()));
+- metrics.addCustomChart(new Metrics.SimplePie("online_mode", () -> Bukkit.getOnlineMode() || PaperConfig.isProxyOnlineMode() ? "online" : "offline"));
+- metrics.addCustomChart(new Metrics.SimplePie("tuinity_version", () -> (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown")); // Tuinity - we have our own bstats page
++ metrics.addCustomChart(new Metrics.SimplePie("online_mode", () -> Bukkit.getOnlineMode() ? "online" : (PaperConfig.isProxyOnlineMode() ? "bungee" : "offline"))); // Purpur
++ metrics.addCustomChart(new Metrics.SimplePie("purpur_version", () -> (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown")); // Purpur
+
+ metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> {
+ Map> map = new HashMap<>();
+diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
+index 673c40d952bae6ae9e92aac9742e58ffb6a8b1bb..ce14283dd1a1fddbea17c2fbaf1c4ef9d7a7479f 100644
+--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
+@@ -118,6 +118,11 @@ public class PaperConfig {
+ }
+ }
+
++ // Purpur start - public save method for config migration
++ saveConfig();
++ }
++ public static void saveConfig() {
++ // Purpur end
+ try {
+ config.save(CONFIG_FILE);
+ } catch (IOException ex) {
+diff --git a/src/main/java/net/minecraft/server/DedicatedServer.java b/src/main/java/net/minecraft/server/DedicatedServer.java
+index ecff0657e5666ddc2e6a5c3111bfb2b8dd2b78d3..3ee8d31c453105eca7b96bede39a9ebbf40e1c2c 100644
+--- a/src/main/java/net/minecraft/server/DedicatedServer.java
++++ b/src/main/java/net/minecraft/server/DedicatedServer.java
+@@ -167,6 +167,15 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer
+ return false;
+ }
+ com.destroystokyo.paper.PaperConfig.registerCommands();
++ // Purpur start
++ try {
++ net.pl3x.purpur.PurpurConfig.init((java.io.File) options.valueOf("purpur-settings"));
++ } catch (Exception e) {
++ DedicatedServer.LOGGER.error("Unable to load server configuration", e);
++ return false;
++ }
++ net.pl3x.purpur.PurpurConfig.registerCommands();
++ // Purpur end
+ com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // load version history now
+ // Paper end
+ com.tuinity.tuinity.config.TuinityConfig.init((java.io.File) options.valueOf("tuinity-settings")); // Tuinity - Server Config
+diff --git a/src/main/java/net/minecraft/server/EntityVillager.java b/src/main/java/net/minecraft/server/EntityVillager.java
+index de9ea6770b8afc5e1020bef04ac6cca93b6b420c..21d0570a59240e955ff148bac0226b220a7dec36 100644
+--- a/src/main/java/net/minecraft/server/EntityVillager.java
++++ b/src/main/java/net/minecraft/server/EntityVillager.java
+@@ -166,7 +166,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation
+ protected void mobTick() { mobTick(false); }
+ protected void mobTick(boolean inactive) {
+ this.world.getMethodProfiler().enter("villagerBrain");
+- if (!inactive) this.getBehaviorController().a((WorldServer) this.world, this); // CraftBukkit - decompile error // Paper
++ if (!inactive) this.getBehaviorController().Wa((WorldServer) this.world, this); // CraftBukkit - decompile error // Paper
+ this.world.getMethodProfiler().exit();
+ if (this.bF) {
+ this.bF = false;
+diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
+index 28ee325fcc8b50397768363403823f2e3391d8c8..fb650c09dbcefa0ff021f7c508ff6811a48bee7a 100644
+--- a/src/main/java/net/minecraft/server/World.java
++++ b/src/main/java/net/minecraft/server/World.java
+@@ -95,6 +95,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
+ public final ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray
+
+ public final com.tuinity.tuinity.config.TuinityConfig.WorldConfig tuinityConfig; // Tuinity - Server Config
++ public final net.pl3x.purpur.PurpurWorldConfig purpurConfig; // Purpur
+
+ public final co.aikar.timings.WorldTimingsHandler timings; // Paper
+ public static BlockPosition lastPhysicsProblem; // Spigot
+@@ -154,8 +155,9 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
+ protected World(WorldDataMutable worlddatamutable, ResourceKey resourcekey, final DimensionManager dimensionmanager, Supplier supplier, boolean flag, boolean flag1, long i, org.bukkit.generator.ChunkGenerator gen, org.bukkit.World.Environment env, java.util.concurrent.Executor executor) { // Paper
+ this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((WorldDataServer) worlddatamutable).getName()); // Spigot
+ this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(((WorldDataServer) worlddatamutable).getName(), this.spigotConfig); // Paper
+- this.chunkPacketBlockController = this.paperConfig.antiXray ? new ChunkPacketBlockControllerAntiXray(this, executor) : ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray
+ this.tuinityConfig = new com.tuinity.tuinity.config.TuinityConfig.WorldConfig(((WorldDataServer)worlddatamutable).getName()); // Tuinity - Server Config
++ this.purpurConfig = new net.pl3x.purpur.PurpurWorldConfig((((WorldDataServer)worlddatamutable).getName())); // Purpur
++ this.chunkPacketBlockController = this.paperConfig.antiXray ? new ChunkPacketBlockControllerAntiXray(this, executor) : ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray
+ this.generator = gen;
+ this.world = new CraftWorld((WorldServer) this, gen, env);
+ this.ticksPerAnimalSpawns = this.getServer().getTicksPerAnimalSpawns(); // CraftBukkit
+diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..cd3272520eb78a9c663bac3bfdb2b63d611d48a1
+--- /dev/null
++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java
+@@ -0,0 +1,130 @@
++package net.pl3x.purpur;
++
++import com.google.common.base.Throwables;
++import net.minecraft.server.MinecraftServer;
++import net.pl3x.purpur.command.PurpurCommand;
++import org.bukkit.Bukkit;
++import org.bukkit.command.Command;
++import org.bukkit.configuration.InvalidConfigurationException;
++import org.bukkit.configuration.file.YamlConfiguration;
++
++import java.io.File;
++import java.io.IOException;
++import java.lang.reflect.InvocationTargetException;
++import java.lang.reflect.Method;
++import java.lang.reflect.Modifier;
++import java.util.HashMap;
++import java.util.List;
++import java.util.Map;
++import java.util.logging.Level;
++
++public class PurpurConfig {
++ private static final String HEADER = "This is the main configuration file for Purpur.\n"
++ + "As you can see, there's tons to configure. Some options may impact gameplay, so use\n"
++ + "with caution, and make sure you know what each option does before configuring.\n"
++ + "\n"
++ + "If you need help with the configuration or have any questions related to Purpur,\n"
++ + "join us in our Discord guild.\n"
++ + "\n"
++ + "Website: https://github.com/pl3xgaming/Purpur \n"
++ + "Wiki: https://github.com/pl3xgaming/Purpur/wiki \n";
++ private static File CONFIG_FILE;
++ public static YamlConfiguration config;
++
++ private static Map commands;
++
++ static int version;
++ static boolean verbose;
++
++ public static void init(File configFile) {
++ CONFIG_FILE = configFile;
++ config = new YamlConfiguration();
++ try {
++ config.load(CONFIG_FILE);
++ } catch (IOException ignore) {
++ } catch (InvalidConfigurationException ex) {
++ Bukkit.getLogger().log(Level.SEVERE, "Could not load purpur.yml, please correct your syntax errors", ex);
++ throw Throwables.propagate(ex);
++ }
++ config.options().header(HEADER);
++ config.options().copyDefaults(true);
++ verbose = getBoolean("verbose", false);
++
++ commands = new HashMap<>();
++ commands.put("purpur", new PurpurCommand("purpur"));
++
++ version = getInt("config-version", 10);
++ set("config-version", 10);
++
++ readConfig(PurpurConfig.class, null);
++ }
++
++ protected static void log(String s) {
++ if (verbose) {
++ log(Level.INFO, s);
++ }
++ }
++
++ protected static void log(Level level, String s) {
++ Bukkit.getLogger().log(level, s);
++ }
++
++ public static void registerCommands() {
++ for (Map.Entry entry : commands.entrySet()) {
++ MinecraftServer.getServer().server.getCommandMap().register(entry.getKey(), "Purpur", entry.getValue());
++ }
++ }
++
++ static void readConfig(Class> clazz, Object instance) {
++ for (Method method : clazz.getDeclaredMethods()) {
++ if (Modifier.isPrivate(method.getModifiers())) {
++ if (method.getParameterTypes().length == 0 && method.getReturnType() == Void.TYPE) {
++ try {
++ method.setAccessible(true);
++ method.invoke(instance);
++ } catch (InvocationTargetException ex) {
++ throw Throwables.propagate(ex.getCause());
++ } catch (Exception ex) {
++ Bukkit.getLogger().log(Level.SEVERE, "Error invoking " + method, ex);
++ }
++ }
++ }
++ }
++
++ try {
++ config.save(CONFIG_FILE);
++ } catch (IOException ex) {
++ Bukkit.getLogger().log(Level.SEVERE, "Could not save " + CONFIG_FILE, ex);
++ }
++ }
++
++ private static void set(String path, Object val) {
++ config.addDefault(path, val);
++ config.set(path, val);
++ }
++
++ private static boolean getBoolean(String path, boolean def) {
++ config.addDefault(path, def);
++ return config.getBoolean(path, config.getBoolean(path));
++ }
++
++ private static double getDouble(String path, double def) {
++ config.addDefault(path, def);
++ return config.getDouble(path, config.getDouble(path));
++ }
++
++ private static int getInt(String path, int def) {
++ config.addDefault(path, def);
++ return config.getInt(path, config.getInt(path));
++ }
++
++ private static List getList(String path, T def) {
++ config.addDefault(path, def);
++ return config.getList(path, config.getList(path));
++ }
++
++ private static String getString(String path, String def) {
++ config.addDefault(path, def);
++ return config.getString(path, config.getString(path));
++ }
++}
+diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..361f7857e461578e90cb71e15027dadaf794cb69
+--- /dev/null
++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+@@ -0,0 +1,59 @@
++package net.pl3x.purpur;
++
++import org.bukkit.configuration.ConfigurationSection;
++import java.util.List;
++import static net.pl3x.purpur.PurpurConfig.log;
++
++public class PurpurWorldConfig {
++
++ private final String worldName;
++
++ public PurpurWorldConfig(String worldName) {
++ this.worldName = worldName;
++ init();
++ }
++
++ public void init() {
++ log("-------- World Settings For [" + worldName + "] --------");
++ PurpurConfig.readConfig(PurpurWorldConfig.class, this);
++ }
++
++ private void set(String path, Object val) {
++ PurpurConfig.config.addDefault("world-settings.default." + path, val);
++ PurpurConfig.config.set("world-settings.default." + path, val);
++ if (PurpurConfig.config.get("world-settings." + worldName + "." + path) != null) {
++ PurpurConfig.config.addDefault("world-settings." + worldName + "." + path, val);
++ PurpurConfig.config.set("world-settings." + worldName + "." + path, val);
++ }
++ }
++
++ private ConfigurationSection getConfigurationSection(String path) {
++ ConfigurationSection section = PurpurConfig.config.getConfigurationSection("world-settings." + worldName + "." + path);
++ return section != null ? section : PurpurConfig.config.getConfigurationSection("world-settings.default." + path);
++ }
++
++ private boolean getBoolean(String path, boolean def) {
++ PurpurConfig.config.addDefault("world-settings.default." + path, def);
++ return PurpurConfig.config.getBoolean("world-settings." + worldName + "." + path, PurpurConfig.config.getBoolean("world-settings.default." + path));
++ }
++
++ private double getDouble(String path, double def) {
++ PurpurConfig.config.addDefault("world-settings.default." + path, def);
++ return PurpurConfig.config.getDouble("world-settings." + worldName + "." + path, PurpurConfig.config.getDouble("world-settings.default." + path));
++ }
++
++ private int getInt(String path, int def) {
++ PurpurConfig.config.addDefault("world-settings.default." + path, def);
++ return PurpurConfig.config.getInt("world-settings." + worldName + "." + path, PurpurConfig.config.getInt("world-settings.default." + path));
++ }
++
++ private List> getList(String path, T def) {
++ PurpurConfig.config.addDefault("world-settings.default." + path, def);
++ return PurpurConfig.config.getList("world-settings." + worldName + "." + path, PurpurConfig.config.getList("world-settings.default." + path));
++ }
++
++ private String getString(String path, String def) {
++ PurpurConfig.config.addDefault("world-settings.default." + path, def);
++ return PurpurConfig.config.getString("world-settings." + worldName + "." + path, PurpurConfig.config.getString("world-settings.default." + path));
++ }
++}
+diff --git a/src/main/java/net/pl3x/purpur/command/PurpurCommand.java b/src/main/java/net/pl3x/purpur/command/PurpurCommand.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..4904be939c7a4b1d1583fd7b6232c930b79caba6
+--- /dev/null
++++ b/src/main/java/net/pl3x/purpur/command/PurpurCommand.java
+@@ -0,0 +1,65 @@
++package net.pl3x.purpur.command;
++
++import net.minecraft.server.MinecraftServer;
++import net.minecraft.server.WorldServer;
++import net.pl3x.purpur.PurpurConfig;
++import org.bukkit.ChatColor;
++import org.bukkit.Location;
++import org.bukkit.command.Command;
++import org.bukkit.command.CommandSender;
++
++import java.io.File;
++import java.util.Collections;
++import java.util.List;
++import java.util.stream.Collectors;
++import java.util.stream.Stream;
++
++public class PurpurCommand extends Command {
++ public PurpurCommand(String name) {
++ super(name);
++ this.description = "Purpur related commands";
++ this.usageMessage = "/purpur [reload | version]";
++ this.setPermission("bukkit.command.purpur");
++ }
++
++ @Override
++ public List tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException {
++ if (args.length == 1) {
++ return Stream.of("reload", "version")
++ .filter(arg -> arg.startsWith(args[0].toLowerCase()))
++ .collect(Collectors.toList());
++ }
++ return Collections.emptyList();
++ }
++
++ @Override
++ public boolean execute(CommandSender sender, String commandLabel, String[] args) {
++ if (!testPermission(sender)) return true;
++
++ if (args.length != 1) {
++ sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage);
++ return false;
++ }
++
++ if (args[0].equalsIgnoreCase("reload")) {
++ Command.broadcastCommandMessage(sender, ChatColor.RED + "Please note that this command is not supported and may cause issues.");
++ Command.broadcastCommandMessage(sender, ChatColor.RED + "If you encounter any issues please use the /stop command to restart your server.");
++
++ MinecraftServer console = MinecraftServer.getServer();
++ PurpurConfig.init((File) console.options.valueOf("purpur-settings"));
++ for (WorldServer world : console.getWorlds()) {
++ world.purpurConfig.init();
++ }
++ console.server.reloadCount++;
++
++ Command.broadcastCommandMessage(sender, ChatColor.GREEN + "Purpur config reload complete.");
++ } else if (args[0].equalsIgnoreCase("version")) {
++ Command verCmd = org.bukkit.Bukkit.getServer().getCommandMap().getCommand("version");
++ if (verCmd != null) {
++ return verCmd.execute(sender, commandLabel, new String[0]);
++ }
++ }
++
++ return true;
++ }
++}
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+index 5100460bab83cd75ac8dcdcc50ea663b1c486d00..b5d274c1fe214ea274057084bc40d6eeb618b21d 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+@@ -862,6 +862,7 @@ public final class CraftServer implements Server {
+ org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings")); // Spigot
+ com.destroystokyo.paper.PaperConfig.init((File) console.options.valueOf("paper-settings")); // Paper
+ com.tuinity.tuinity.config.TuinityConfig.init((File) console.options.valueOf("tuinity-settings")); // Tuinity - Server Config
++ net.pl3x.purpur.PurpurConfig.init((File) console.options.valueOf("purpur-settings")); // Purpur
+ for (WorldServer world : console.getWorlds()) {
+ world.worldDataServer.setDifficulty(config.difficulty);
+ world.setSpawnFlags(config.spawnMonsters, config.spawnAnimals);
+@@ -897,6 +898,7 @@ public final class CraftServer implements Server {
+ world.spigotConfig.init(); // Spigot
+ world.paperConfig.init(); // Paper
+ world.tuinityConfig.init(); // Tuinity - Server Config
++ world.purpurConfig.init(); // Purpur
+ }
+
+ Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper
+@@ -915,6 +917,7 @@ public final class CraftServer implements Server {
+ reloadData();
+ org.spigotmc.SpigotConfig.registerCommands(); // Spigot
+ com.destroystokyo.paper.PaperConfig.registerCommands(); // Paper
++ net.pl3x.purpur.PurpurConfig.registerCommands(); // Purpur
+ overrideAllCommandBlockCommands = commandsConfiguration.getStringList("command-block-overrides").contains("*");
+ ignoreVanillaPermissions = commandsConfiguration.getBoolean("ignore-vanilla-permissions");
+
+@@ -2298,6 +2301,18 @@ public final class CraftServer implements Server {
+ }
+ // Tuinity end - add config to timings report
+
++ // Purpur start
++ @Override
++ public YamlConfiguration getPurpurConfig() {
++ return net.pl3x.purpur.PurpurConfig.config;
++ }
++
++ @Override
++ public java.util.Properties getServerProperties() {
++ return getProperties().properties;
++ }
++ // Purpur end
++
+ @Override
+ public void restart() {
+ org.spigotmc.RestartCommand.restart();
+diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java
+index 0f6cb508a170360b6479f9c34048412453fbb89d..a92721dff5c2a9a2a167b36c23d1ef22d2bbd3e1 100644
+--- a/src/main/java/org/bukkit/craftbukkit/Main.java
++++ b/src/main/java/org/bukkit/craftbukkit/Main.java
+@@ -146,6 +146,14 @@ public class Main {
+ .describedAs("Yml file");
+ /* Conctete End - Server Config */
+
++ // Purpur Start
++ acceptsAll(asList("purpur", "purpur-settings"), "File for purpur settings")
++ .withRequiredArg()
++ .ofType(File.class)
++ .defaultsTo(new File("purpur.yml"))
++ .describedAs("Yml file");
++ // Purpur end
++
+ // Paper start
+ acceptsAll(asList("server-name"), "Name of the server")
+ .withRequiredArg()
diff --git a/patches/Purpur/patches/server/0003-Timings-stuff.patch b/patches/Purpur/patches/server/0003-Timings-stuff.patch
new file mode 100644
index 00000000..151e6d7d
--- /dev/null
+++ b/patches/Purpur/patches/server/0003-Timings-stuff.patch
@@ -0,0 +1,73 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Fri, 5 Jun 2020 21:30:19 -0500
+Subject: [PATCH] Timings stuff
+
+
+diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java
+index 5dfa0658838c4801cdf260eae8b98163f729e5af..dae2e5d70756c5b61163d57099b65f7e415b288c 100644
+--- a/src/main/java/co/aikar/timings/TimingsExport.java
++++ b/src/main/java/co/aikar/timings/TimingsExport.java
+@@ -227,10 +227,14 @@ public class TimingsExport extends Thread {
+ // Information on the users Config
+
+ parent.put("config", createObject(
+- pair("spigot", mapAsJSON(Bukkit.spigot().getSpigotConfig(), null)),
+- pair("bukkit", mapAsJSON(Bukkit.spigot().getBukkitConfig(), null)),
+- pair("paper", mapAsJSON(Bukkit.spigot().getPaperConfig(), null)), // Tuinity - add config to timings report
+- pair("tuinity", mapAsJSON(Bukkit.spigot().getTuinityConfig(), null)) // Tuinity - add config to timings report
++ // Purpur start
++ pair("server.properties", mapAsJSON(Bukkit.spigot().getServerProperties())),
++ pair("bukkit", mapAsJSON(Bukkit.spigot().getBukkitConfig(), null)),
++ pair("spigot", mapAsJSON(Bukkit.spigot().getSpigotConfig(), null)),
++ pair("paper", mapAsJSON(Bukkit.spigot().getPaperConfig(), null)),
++ pair("tuinity", mapAsJSON(Bukkit.spigot().getTuinityConfig(), null)), // Tuinity - add config to timings report
++ pair("purpur", mapAsJSON(Bukkit.spigot().getPurpurConfig(), null))
++ // Purpur end
+ ));
+
+ new TimingsExport(listeners, parent, history).start();
+@@ -271,6 +275,19 @@ public class TimingsExport extends Thread {
+ return timingsCost;
+ }
+
++ // Purpur start
++ private static JSONObject mapAsJSON(java.util.Properties properties) {
++ JSONObject object = new JSONObject();
++ for (String key : properties.stringPropertyNames()) {
++ if (key.startsWith("rcon") || key.startsWith("query") || key.equals("level-seed") || TimingsManager.hiddenConfigs.contains(key)) {
++ continue;
++ }
++ object.put(key, valAsJSON(properties.get(key), key));
++ }
++ return object;
++ }
++ // Purpur end
++
+ private static JSONObject mapAsJSON(ConfigurationSection config, String parentKey) {
+
+ JSONObject object = new JSONObject();
+@@ -307,7 +324,7 @@ public class TimingsExport extends Thread {
+ String response = null;
+ String timingsURL = null;
+ try {
+- HttpURLConnection con = (HttpURLConnection) new URL("http://timings.aikar.co/post").openConnection();
++ HttpURLConnection con = (HttpURLConnection) new URL(net.pl3x.purpur.PurpurConfig.timingsUrl + "/post").openConnection(); // Purpur
+ con.setDoOutput(true);
+ String hostName = "BrokenHost";
+ try {
+diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java
+index cd3272520eb78a9c663bac3bfdb2b63d611d48a1..00eb196f8caa2e4f2478972c14f4596071adbd2a 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java
+@@ -127,4 +127,10 @@ public class PurpurConfig {
+ config.addDefault(path, def);
+ return config.getString(path, config.getString(path));
+ }
++
++ public static String timingsUrl = "https://timings.pl3x.net";
++ private static void timingsSettings() {
++ timingsUrl = getString("settings.timings.url", timingsUrl);
++ if (!TimingsManager.hiddenConfigs.contains("server-ip")) TimingsManager.hiddenConfigs.add("server-ip");
++ }
+ }
diff --git a/patches/Purpur/patches/server/0004-Add-component-util.patch b/patches/Purpur/patches/server/0004-Add-component-util.patch
new file mode 100644
index 00000000..f63b095a
--- /dev/null
+++ b/patches/Purpur/patches/server/0004-Add-component-util.patch
@@ -0,0 +1,44 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sat, 15 Aug 2020 03:49:33 -0500
+Subject: [PATCH] Add component util
+
+
+diff --git a/src/main/java/net/pl3x/purpur/ComponentUtil.java b/src/main/java/net/pl3x/purpur/ComponentUtil.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..3f7bc68d1a6fb00758b178bb46113e38b8bc24bc
+--- /dev/null
++++ b/src/main/java/net/pl3x/purpur/ComponentUtil.java
+@@ -0,0 +1,32 @@
++package net.pl3x.purpur;
++
++import net.md_5.bungee.api.chat.BaseComponent;
++import net.md_5.bungee.api.chat.TextComponent;
++import net.md_5.bungee.chat.ComponentSerializer;
++import net.minecraft.server.IChatBaseComponent;
++import net.minecraft.server.MinecraftServer;
++
++import java.util.List;
++
++public class ComponentUtil {
++ public static String fromComponent(IChatBaseComponent component) {
++ String json = "";
++ try {
++ int chop;
++ List siblings = component.getSiblings();
++ if (siblings.size() > 0) chop = siblings.get(0).getChatModifier().getColor() == null ? 4 : 2;
++ else chop = component.getChatModifier().getColor() == null ? 2 : 0;
++ json = IChatBaseComponent.ChatSerializer.componentToJson(component);
++ BaseComponent[] parsed = ComponentSerializer.parse(json);
++ return TextComponent.toLegacyText(parsed).substring(chop);
++ } catch (Exception e) {
++ MinecraftServer.LOGGER.warn("There was a problem processing a chat component!");
++ MinecraftServer.LOGGER.warn("We have fallen back to legacy colorless string to prevent real errors");
++ MinecraftServer.LOGGER.warn("Please report this to Purpur!");
++ MinecraftServer.LOGGER.warn("JSON: " + json);
++ MinecraftServer.LOGGER.warn("The following error describes what went wrong:");
++ e.printStackTrace();
++ return component.getString();
++ }
++ }
++}
diff --git a/patches/Purpur/patches/server/0005-Barrels-and-enderchests-6-rows.patch b/patches/Purpur/patches/server/0005-Barrels-and-enderchests-6-rows.patch
new file mode 100644
index 00000000..c8bd7f2b
--- /dev/null
+++ b/patches/Purpur/patches/server/0005-Barrels-and-enderchests-6-rows.patch
@@ -0,0 +1,173 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Thu, 23 May 2019 21:50:37 -0500
+Subject: [PATCH] Barrels and enderchests 6 rows
+
+
+diff --git a/src/main/java/net/minecraft/server/BlockEnderChest.java b/src/main/java/net/minecraft/server/BlockEnderChest.java
+index 896d99d404419fef5bdf6f9083e07dfc978f4e67..9ab8336df4f1702e9cabefb63f279034fdd57486 100644
+--- a/src/main/java/net/minecraft/server/BlockEnderChest.java
++++ b/src/main/java/net/minecraft/server/BlockEnderChest.java
+@@ -48,6 +48,27 @@ public class BlockEnderChest extends BlockChestAbstract im
+
+ inventoryenderchest.a(tileentityenderchest);
+ entityhuman.openContainer(new TileInventory((i, playerinventory, entityhuman1) -> {
++ // Purpur start
++ if (net.pl3x.purpur.PurpurConfig.enderChestSixRows) {
++ if (net.pl3x.purpur.PurpurConfig.enderChestPermissionRows) {
++ org.bukkit.craftbukkit.entity.CraftHumanEntity player = entityhuman.getBukkitEntity();
++ if (player.hasPermission("purpur.enderchest.rows.six")) {
++ return new ContainerChest(Containers.GENERIC_9X6, i, playerinventory, inventoryenderchest, 6);
++ } else if (player.hasPermission("purpur.enderchest.rows.five")) {
++ return new ContainerChest(Containers.GENERIC_9X5, i, playerinventory, inventoryenderchest, 5);
++ } else if (player.hasPermission("purpur.enderchest.rows.four")) {
++ return new ContainerChest(Containers.GENERIC_9X4, i, playerinventory, inventoryenderchest, 4);
++ } else if (player.hasPermission("purpur.enderchest.rows.three")) {
++ return new ContainerChest(Containers.GENERIC_9X3, i, playerinventory, inventoryenderchest, 3);
++ } else if (player.hasPermission("purpur.enderchest.rows.two")) {
++ return new ContainerChest(Containers.GENERIC_9X2, i, playerinventory, inventoryenderchest, 2);
++ } else if (player.hasPermission("purpur.enderchest.rows.one")) {
++ return new ContainerChest(Containers.GENERIC_9X1, i, playerinventory, inventoryenderchest, 1);
++ }
++ }
++ return new ContainerChest(Containers.GENERIC_9X6, i, playerinventory, inventoryenderchest, 6);
++ }
++ // Purpur end
+ return ContainerChest.a(i, playerinventory, inventoryenderchest);
+ }, BlockEnderChest.e));
+ entityhuman.a(StatisticList.OPEN_ENDERCHEST);
+diff --git a/src/main/java/net/minecraft/server/InventoryEnderChest.java b/src/main/java/net/minecraft/server/InventoryEnderChest.java
+index fa56d6e3a9f0bfad3961697a3bae98205f32ae9c..3fc6298a5ef81c4203a79f1d9e87b0a9913255b6 100644
+--- a/src/main/java/net/minecraft/server/InventoryEnderChest.java
++++ b/src/main/java/net/minecraft/server/InventoryEnderChest.java
+@@ -19,11 +19,34 @@ public class InventoryEnderChest extends InventorySubcontainer {
+ }
+
+ public InventoryEnderChest(EntityHuman owner) {
+- super(27);
++ super(net.pl3x.purpur.PurpurConfig.enderChestSixRows ? 54 : 27); // Purpur
+ this.owner = owner;
+ // CraftBukkit end
+ }
+
++ // Purpur start
++ @Override
++ public int getSize() {
++ if (net.pl3x.purpur.PurpurConfig.enderChestSixRows && net.pl3x.purpur.PurpurConfig.enderChestPermissionRows && owner != null && owner.getProfile() != null) {
++ org.bukkit.craftbukkit.entity.CraftHumanEntity bukkit = owner.getBukkitEntity();
++ if (bukkit.hasPermission("purpur.enderchest.rows.six")) {
++ return 54;
++ } else if (bukkit.hasPermission("purpur.enderchest.rows.five")) {
++ return 45;
++ } else if (bukkit.hasPermission("purpur.enderchest.rows.four")) {
++ return 36;
++ } else if (bukkit.hasPermission("purpur.enderchest.rows.three")) {
++ return 27;
++ } else if (bukkit.hasPermission("purpur.enderchest.rows.two")) {
++ return 18;
++ } else if (bukkit.hasPermission("purpur.enderchest.rows.one")) {
++ return 9;
++ }
++ }
++ return super.getSize();
++ }
++ // Purpur end
++
+ public void a(TileEntityEnderChest tileentityenderchest) {
+ this.a = tileentityenderchest;
+ }
+diff --git a/src/main/java/net/minecraft/server/TileEntityBarrel.java b/src/main/java/net/minecraft/server/TileEntityBarrel.java
+index a1c3942cbf9a6c0adc4943b05a1c3859c5f0aed6..953741c1cfd9b5c1e0eac80d1e4c7890f94e3bb1 100644
+--- a/src/main/java/net/minecraft/server/TileEntityBarrel.java
++++ b/src/main/java/net/minecraft/server/TileEntityBarrel.java
+@@ -55,7 +55,7 @@ public class TileEntityBarrel extends TileEntityLootable {
+
+ private TileEntityBarrel(TileEntityTypes> tileentitytypes) {
+ super(tileentitytypes);
+- this.items = NonNullList.a(27, ItemStack.b);
++ this.items = NonNullList.a(net.pl3x.purpur.PurpurConfig.barrelSixRows ? 54 : 27, ItemStack.b); // Purpur
+ }
+
+ public TileEntityBarrel() {
+@@ -84,7 +84,7 @@ public class TileEntityBarrel extends TileEntityLootable {
+
+ @Override
+ public int getSize() {
+- return 27;
++ return net.pl3x.purpur.PurpurConfig.barrelSixRows ? 54 : 27; // Purpur
+ }
+
+ @Override
+@@ -104,6 +104,7 @@ public class TileEntityBarrel extends TileEntityLootable {
+
+ @Override
+ protected Container createContainer(int i, PlayerInventory playerinventory) {
++ if (net.pl3x.purpur.PurpurConfig.barrelSixRows) return new ContainerChest(Containers.GENERIC_9X6, i, playerinventory, this, 6); // Purpur
+ return ContainerChest.a(i, playerinventory, this);
+ }
+
+diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java
+index 00eb196f8caa2e4f2478972c14f4596071adbd2a..cb7e34924cb5dbff25d1ffe05cfe5bc22e4a90ed 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java
+@@ -7,6 +7,7 @@ import org.bukkit.Bukkit;
+ import org.bukkit.command.Command;
+ import org.bukkit.configuration.InvalidConfigurationException;
+ import org.bukkit.configuration.file.YamlConfiguration;
++import org.bukkit.event.inventory.InventoryType;
+
+ import java.io.File;
+ import java.io.IOException;
+@@ -133,4 +134,23 @@ public class PurpurConfig {
+ timingsUrl = getString("settings.timings.url", timingsUrl);
+ if (!TimingsManager.hiddenConfigs.contains("server-ip")) TimingsManager.hiddenConfigs.add("server-ip");
+ }
++
++ public static boolean barrelSixRows = false;
++ public static boolean enderChestSixRows = false;
++ public static boolean enderChestPermissionRows = false;
++ private static void blockSettings() {
++ if (version < 3) {
++ boolean oldValue = getBoolean("settings.barrel.packed-barrels", true);
++ set("settings.blocks.barrel.six-rows", oldValue);
++ set("settings.packed-barrels", null);
++ oldValue = getBoolean("settings.large-ender-chests", true);
++ set("settings.blocks.ender_chest.six-rows", oldValue);
++ set("settings.large-ender-chests", null);
++ }
++ barrelSixRows = getBoolean("settings.blocks.barrel.six-rows", barrelSixRows);
++ InventoryType.BARREL.setDefaultSize(barrelSixRows ? 54 : 27);
++ enderChestSixRows = getBoolean("settings.blocks.ender_chest.six-rows", enderChestSixRows);
++ InventoryType.ENDER_CHEST.setDefaultSize(enderChestSixRows ? 54 : 27);
++ enderChestPermissionRows = getBoolean("settings.blocks.ender_chest.use-permissions-for-rows", enderChestPermissionRows);
++ }
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java
+index 8c714c7430c0a6b8fd7f4a158d9a271e1642bd7a..cae362bae9e1e253c34bc81813d251fece839de3 100644
+--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java
++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java
+@@ -198,8 +198,10 @@ public class CraftContainer extends Container {
+ case PLAYER:
+ case CHEST:
+ case ENDER_CHEST:
++ delegate = new ContainerChest(net.pl3x.purpur.PurpurConfig.enderChestSixRows ? Containers.GENERIC_9X6 : Containers.GENERIC_9X3, windowId, bottom, top, top.getSize() / 9); // Purpur
++ break; // Purpur
+ case BARREL:
+- delegate = new ContainerChest(Containers.GENERIC_9X3, windowId, bottom, top, top.getSize() / 9);
++ delegate = new ContainerChest(net.pl3x.purpur.PurpurConfig.barrelSixRows ? Containers.GENERIC_9X6 : Containers.GENERIC_9X3, windowId, bottom, top, top.getSize() / 9); // Purpur
+ break;
+ case DISPENSER:
+ case DROPPER:
+diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java
+index bba9bddc1c0aacade9b7ad56afb1e630caa078fc..c2802c5bfb5ec82daad32d3a3375f4428ae76dfd 100644
+--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java
++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java
+@@ -81,7 +81,7 @@ public class CraftInventory implements Inventory {
+
+ @Override
+ public void setContents(ItemStack[] items) {
+- if (getSize() < items.length) {
++ if (false && getSize() < items.length) { // Purpur
+ throw new IllegalArgumentException("Invalid inventory size; expected " + getSize() + " or less");
+ }
+
diff --git a/patches/Purpur/patches/server/0006-Advancement-API.patch b/patches/Purpur/patches/server/0006-Advancement-API.patch
new file mode 100644
index 00000000..1ce8a56b
--- /dev/null
+++ b/patches/Purpur/patches/server/0006-Advancement-API.patch
@@ -0,0 +1,181 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Fri, 31 May 2019 21:24:33 -0500
+Subject: [PATCH] Advancement API
+
+
+diff --git a/src/main/java/net/minecraft/server/Advancement.java b/src/main/java/net/minecraft/server/Advancement.java
+index c405047c00d354bbc1449fd2f917b73f980ef1a5..384d4090f8ff1ea718de16affa5c146a2f58d28a 100644
+--- a/src/main/java/net/minecraft/server/Advancement.java
++++ b/src/main/java/net/minecraft/server/Advancement.java
+@@ -64,7 +64,7 @@ public class Advancement {
+ }
+
+ @Nullable
+- public AdvancementDisplay c() {
++ public AdvancementDisplay c() { return getDisplay(); } public AdvancementDisplay getDisplay() { // Purpur
+ return this.display;
+ }
+
+diff --git a/src/main/java/net/minecraft/server/AdvancementDisplay.java b/src/main/java/net/minecraft/server/AdvancementDisplay.java
+index b0d4b7a67679a35fa8f88c241193c0f3814f1e7b..ac4fac89837f4e77dcaec6f9ca90c5aa8a78c4be 100644
+--- a/src/main/java/net/minecraft/server/AdvancementDisplay.java
++++ b/src/main/java/net/minecraft/server/AdvancementDisplay.java
+@@ -15,10 +15,11 @@ public class AdvancementDisplay {
+ private final MinecraftKey d;
+ private final AdvancementFrameType e;
+ private final boolean f;
+- private final boolean g;
+- private final boolean h;
++ private boolean g; // Purpur - un-finalize
++ private boolean h; // Purpur - un-finalize
+ private float i;
+ private float j;
++ public final org.bukkit.advancement.AdvancementDisplay bukkit = new org.bukkit.craftbukkit.advancement.CraftAdvancementDisplay(this); // Purpur
+
+ public AdvancementDisplay(ItemStack itemstack, IChatBaseComponent ichatbasecomponent, IChatBaseComponent ichatbasecomponent1, @Nullable MinecraftKey minecraftkey, AdvancementFrameType advancementframetype, boolean flag, boolean flag1, boolean flag2) {
+ this.a = ichatbasecomponent;
+@@ -36,22 +37,29 @@ public class AdvancementDisplay {
+ this.j = f1;
+ }
+
++ public IChatBaseComponent getTitle() { return a(); } // Purpur - OBFHELPER
+ public IChatBaseComponent a() {
+ return this.a;
+ }
+
++ public IChatBaseComponent getDescription() { return b(); } // Purpur - OBFHELPER
+ public IChatBaseComponent b() {
+ return this.b;
+ }
+
++ public AdvancementFrameType getFrameType() { return e(); } // Purpur - OBFHELPER
+ public AdvancementFrameType e() {
+ return this.e;
+ }
+
++ public void setShouldAnnounceToChat(boolean announce) { this.g = announce; } // Purpur - OBFHELPER
++ public boolean shouldAnnounceToChat() { return i(); } // Purpur - OBFHELPER
+ public boolean i() {
+ return this.g;
+ }
+
++ public void setHidden(boolean hidden) { this.h = hidden; } // Purpur - OBFHELPER
++ public boolean isHidden() { return j(); } // Purpur - OBFHELPER
+ public boolean j() {
+ return this.h;
+ }
+diff --git a/src/main/java/net/minecraft/server/AdvancementFrameType.java b/src/main/java/net/minecraft/server/AdvancementFrameType.java
+index 90b78e49c0688dc2fb02df0b6784cd82fad4bc07..9a3a53cf3576c299629a84ba76cb5b9b86a14491 100644
+--- a/src/main/java/net/minecraft/server/AdvancementFrameType.java
++++ b/src/main/java/net/minecraft/server/AdvancementFrameType.java
+@@ -1,15 +1,26 @@
+ package net.minecraft.server;
+
++import org.bukkit.advancement.FrameType; // Purpur
++
+ public enum AdvancementFrameType {
+
+- TASK("task", 0, EnumChatFormat.GREEN), CHALLENGE("challenge", 26, EnumChatFormat.DARK_PURPLE), GOAL("goal", 52, EnumChatFormat.GREEN);
++ // Purpur start
++ TASK("task", 0, EnumChatFormat.GREEN, FrameType.TASK),
++ CHALLENGE("challenge", 26, EnumChatFormat.DARK_PURPLE, FrameType.CHALLENGE),
++ GOAL("goal", 52, EnumChatFormat.GREEN, FrameType.GOAL);
++ // Purpur end
+
+ private final String d;
+ private final int e;
+ private final EnumChatFormat f;
+ private final IChatBaseComponent g;
+
+- private AdvancementFrameType(String s, int i, EnumChatFormat enumchatformat) {
++ // Purpur start
++ public final FrameType bukkit;
++
++ AdvancementFrameType(String s, int i, EnumChatFormat enumchatformat, FrameType bukkit) {
++ this.bukkit = bukkit;
++ // Purpur end
+ this.d = s;
+ this.e = i;
+ this.f = enumchatformat;
+diff --git a/src/main/java/net/minecraft/server/CriterionTrigger.java b/src/main/java/net/minecraft/server/CriterionTrigger.java
+index cfb420a9c7e64ec240fff81d2e3fd32f607847b3..6fd3671c37a4fc42aa438a93d5a749b52f618b1e 100644
+--- a/src/main/java/net/minecraft/server/CriterionTrigger.java
++++ b/src/main/java/net/minecraft/server/CriterionTrigger.java
+@@ -26,6 +26,7 @@ public interface CriterionTrigger {
+ this.c = s;
+ }
+
++ public T getInstance() { return a(); } // Purpur - OBFHELPER
+ public T a() {
+ return this.a;
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancement.java b/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancement.java
+index a5aadf2850f273e258f84b6c7bc9ca3649fb884d..b0a7092d623adccd61fd3e094f1ec5e8d95c3691 100644
+--- a/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancement.java
++++ b/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancement.java
+@@ -27,4 +27,11 @@ public class CraftAdvancement implements org.bukkit.advancement.Advancement {
+ public Collection getCriteria() {
+ return Collections.unmodifiableCollection(handle.getCriteria().keySet());
+ }
++
++ // Purpur start
++ @Override
++ public org.bukkit.advancement.AdvancementDisplay getDisplay() {
++ return getHandle().getDisplay() == null ? null : getHandle().getDisplay().bukkit;
++ }
++ // Purpur end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancementDisplay.java b/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancementDisplay.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..1cbb1e67b64a7e830cfabcd1fc07e998434476c3
+--- /dev/null
++++ b/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancementDisplay.java
+@@ -0,0 +1,47 @@
++package org.bukkit.craftbukkit.advancement;
++
++import net.minecraft.server.AdvancementDisplay;
++import org.bukkit.advancement.FrameType;
++import org.bukkit.craftbukkit.util.CraftChatMessage;
++
++public class CraftAdvancementDisplay implements org.bukkit.advancement.AdvancementDisplay {
++ private final AdvancementDisplay handle;
++
++ public CraftAdvancementDisplay(AdvancementDisplay handle) {
++ this.handle = handle;
++ }
++
++ public AdvancementDisplay getHandle() {
++ return handle;
++ }
++
++ @Override
++ public String getTitle() {
++ return CraftChatMessage.fromComponent(handle.getTitle());
++ }
++
++ @Override
++ public String getDescription() {
++ return CraftChatMessage.fromComponent(handle.getDescription());
++ }
++
++ @Override
++ public FrameType getFrameType() {
++ return handle.getFrameType().bukkit;
++ }
++
++ @Override
++ public boolean shouldAnnounceToChat() {
++ return handle.shouldAnnounceToChat();
++ }
++
++ @Override
++ public void setShouldAnnounceToChat(boolean announce) {
++ handle.setShouldAnnounceToChat(announce);
++ }
++
++ @Override
++ public boolean isHidden() {
++ return handle.isHidden();
++ }
++}
diff --git a/patches/Purpur/patches/server/0007-Llama-API.patch b/patches/Purpur/patches/server/0007-Llama-API.patch
new file mode 100644
index 00000000..4986a46a
--- /dev/null
+++ b/patches/Purpur/patches/server/0007-Llama-API.patch
@@ -0,0 +1,156 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Fri, 18 Oct 2019 22:50:12 -0500
+Subject: [PATCH] Llama API
+
+
+diff --git a/src/main/java/net/minecraft/server/EntityLlama.java b/src/main/java/net/minecraft/server/EntityLlama.java
+index d9e1b43283bee15c659dd3a99e45d9412aedd0bc..e61f53816cbf09e775762403d97e9c591fb405a6 100644
+--- a/src/main/java/net/minecraft/server/EntityLlama.java
++++ b/src/main/java/net/minecraft/server/EntityLlama.java
+@@ -13,7 +13,8 @@ public class EntityLlama extends EntityHorseChestedAbstract implements IRangedEn
+ @Nullable
+ private EntityLlama bB;
+ @Nullable
+- private EntityLlama bC;
++ private EntityLlama bC; public EntityLlama getCaravanTail() { return bC; } // Purpur - OBFHELPER
++ public boolean shouldJoinCaravan = true; // Purpur
+
+ public EntityLlama(EntityTypes extends EntityLlama> entitytypes, World world) {
+ super(entitytypes, world);
+@@ -42,6 +43,7 @@ public class EntityLlama extends EntityHorseChestedAbstract implements IRangedEn
+ nbttagcompound.set("DecorItem", this.inventoryChest.getItem(1).save(new NBTTagCompound()));
+ }
+
++ nbttagcompound.setBoolean("Purpur.ShouldJoinCaravan", shouldJoinCaravan); // Purpur
+ }
+
+ @Override
+@@ -53,6 +55,11 @@ public class EntityLlama extends EntityHorseChestedAbstract implements IRangedEn
+ this.inventoryChest.setItem(1, ItemStack.a(nbttagcompound.getCompound("DecorItem")));
+ }
+
++ // Purpur start
++ if (nbttagcompound.hasKey("Purpur.ShouldJoinCaravan")) {
++ nbttagcompound.setBoolean("Purpur.ShouldJoinCaravan", shouldJoinCaravan);
++ }
++ // Purpur end
+ this.fe();
+ }
+
+@@ -387,19 +394,24 @@ public class EntityLlama extends EntityHorseChestedAbstract implements IRangedEn
+ }
+ }
+
++ public void leaveCaravan() { fA(); } // Purpur - OBFHELPER
+ public void fA() {
+ if (this.bB != null) {
++ new net.pl3x.purpur.event.entity.LlamaLeaveCaravanEvent((org.bukkit.entity.Llama) getBukkitEntity()).callEvent(); // Purpur
+ this.bB.bC = null;
+ }
+
+ this.bB = null;
+ }
+
++ public void joinCaravan(EntityLlama entitiyllama) { a(entitiyllama); } // Purpur - OBFHELPER
+ public void a(EntityLlama entityllama) {
++ if (!shouldJoinCaravan || !new net.pl3x.purpur.event.entity.LlamaJoinCaravanEvent((org.bukkit.entity.Llama) getBukkitEntity(), (org.bukkit.entity.Llama) entityllama.getBukkitEntity()).callEvent()) return; // Purpur
+ this.bB = entityllama;
+ this.bB.bC = this;
+ }
+
++ public boolean hasCaravanTail() { return fB(); } // Purpur - OBFHELPER
+ public boolean fB() {
+ return this.bC != null;
+ }
+@@ -410,7 +422,7 @@ public class EntityLlama extends EntityHorseChestedAbstract implements IRangedEn
+ }
+
+ @Nullable
+- public EntityLlama fD() {
++ public EntityLlama fD() { return getCaravanHead(); } public EntityLlama getCaravanHead() { // Purpur - OBFHELPER
+ return this.bB;
+ }
+
+diff --git a/src/main/java/net/minecraft/server/PathfinderGoalLlamaFollow.java b/src/main/java/net/minecraft/server/PathfinderGoalLlamaFollow.java
+index 1b29ca2ca0bc5d17673de43bdc854d5b4c96b8b6..47ffa669681da7512ee594ecb643f28576dee444 100644
+--- a/src/main/java/net/minecraft/server/PathfinderGoalLlamaFollow.java
++++ b/src/main/java/net/minecraft/server/PathfinderGoalLlamaFollow.java
+@@ -6,7 +6,7 @@ import java.util.List;
+
+ public class PathfinderGoalLlamaFollow extends PathfinderGoal {
+
+- public final EntityLlama a;
++ public final EntityLlama a; public EntityLlama getLlama() { return a; } // Purpur
+ private double b;
+ private int c;
+
+@@ -18,6 +18,7 @@ public class PathfinderGoalLlamaFollow extends PathfinderGoal {
+
+ @Override
+ public boolean a() {
++ if (!getLlama().shouldJoinCaravan) return false; // Purpur
+ if (!this.a.isLeashed() && !this.a.fC()) {
+ List list = this.a.world.getEntities(this.a, this.a.getBoundingBox().grow(9.0D, 4.0D, 9.0D), (entity) -> {
+ EntityTypes> entitytypes = entity.getEntityType();
+@@ -77,6 +78,7 @@ public class PathfinderGoalLlamaFollow extends PathfinderGoal {
+
+ @Override
+ public boolean b() {
++ if (!getLlama().shouldJoinCaravan) return false; // Purpur
+ if (this.a.fC() && this.a.fD().isAlive() && this.a(this.a, 0)) {
+ double d0 = this.a.h((Entity) this.a.fD());
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java
+index 3f94c5a9206e2da9c852d282e267ab4d9f7324c4..a02763480149dc7fb0f07f17ef8530a2e76d99bc 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java
+@@ -65,4 +65,48 @@ public class CraftLlama extends CraftChestedHorse implements Llama, CraftRangedE
+ public EntityType getType() {
+ return EntityType.LLAMA;
+ }
++
++ // Purpur start
++ @Override
++ public boolean shouldJoinCaravan() {
++ return getHandle().shouldJoinCaravan;
++ }
++
++ @Override
++ public void setShouldJoinCaravan(boolean shouldJoinCaravan) {
++ getHandle().shouldJoinCaravan = shouldJoinCaravan;
++ }
++
++ @Override
++ public boolean inCaravan() {
++ return getHandle().inCaravan();
++ }
++
++ @Override
++ public void joinCaravan(Llama llama) {
++ if (llama != null) {
++ getHandle().joinCaravan(((CraftLlama) llama).getHandle());
++ }
++ }
++
++ @Override
++ public void leaveCaravan() {
++ getHandle().leaveCaravan();
++ }
++
++ @Override
++ public boolean hasCaravanTail() {
++ return getHandle().hasCaravanTail();
++ }
++
++ @Override
++ public Llama getCaravanHead() {
++ return getHandle().getCaravanHead() == null ? null : (Llama) getHandle().getCaravanHead().getBukkitEntity();
++ }
++
++ @Override
++ public Llama getCaravanTail() {
++ return getHandle().getCaravanTail() == null ? null : (Llama) getHandle().getCaravanTail().getBukkitEntity();
++ }
++ // Purpur end
+ }
diff --git a/patches/Purpur/patches/server/0008-AFK-API.patch b/patches/Purpur/patches/server/0008-AFK-API.patch
new file mode 100644
index 00000000..86e3c73d
--- /dev/null
+++ b/patches/Purpur/patches/server/0008-AFK-API.patch
@@ -0,0 +1,302 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Thu, 8 Aug 2019 15:29:15 -0500
+Subject: [PATCH] AFK API
+
+
+diff --git a/src/main/java/net/minecraft/server/EntityHuman.java b/src/main/java/net/minecraft/server/EntityHuman.java
+index 9796b4e57d6680c9f0dc76decdd985572daafb7e..f15ec5c45d95c6828ed628451917ac3426a76f1f 100644
+--- a/src/main/java/net/minecraft/server/EntityHuman.java
++++ b/src/main/java/net/minecraft/server/EntityHuman.java
+@@ -84,6 +84,15 @@ public abstract class EntityHuman extends EntityLiving {
+ }
+ // CraftBukkit end
+
++ // Purpur start
++ public void setAfk(boolean setAfk){
++ }
++
++ public boolean isAfk() {
++ return false;
++ }
++ // Purpur end
++
+ public EntityHuman(World world, BlockPosition blockposition, float f, GameProfile gameprofile) {
+ super(EntityTypes.PLAYER, world);
+ this.bL = ItemStack.b;
+diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java
+index 9f5b7243ccbe0729a061345c25033d9145b91b3f..6bab47ab2583735c36d74d849ab0923494a265db 100644
+--- a/src/main/java/net/minecraft/server/EntityPlayer.java
++++ b/src/main/java/net/minecraft/server/EntityPlayer.java
+@@ -1904,8 +1904,54 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
+
+ public void resetIdleTimer() {
+ this.ca = SystemUtils.getMonotonicMillis();
++ setAfk(false); // Purpur
+ }
+
++ // Purpur start
++ private boolean isAfk = false;
++
++ @Override
++ public void setAfk(boolean setAfk) {
++ if (this.isAfk == setAfk) {
++ return;
++ }
++
++ String msg = setAfk ? net.pl3x.purpur.PurpurConfig.afkBroadcastAway : net.pl3x.purpur.PurpurConfig.afkBroadcastBack;
++
++ net.pl3x.purpur.event.PlayerAFKEvent event = new net.pl3x.purpur.event.PlayerAFKEvent(getBukkitEntity(), setAfk, world.purpurConfig.idleTimeoutKick, msg, !Bukkit.isPrimaryThread());
++ if (!event.callEvent() || event.shouldKick()) {
++ return;
++ }
++
++ this.isAfk = setAfk;
++
++ if (!setAfk) {
++ resetIdleTimer();
++ }
++
++ msg = event.getBroadcastMsg();
++ if (msg != null && !msg.isEmpty()) {
++ server.getPlayerList().sendMessage(org.bukkit.craftbukkit.util.CraftChatMessage.fromStringOrNull(String.format(msg, getProfile().getName())));
++ }
++
++ if (world.purpurConfig.idleTimeoutUpdateTabList) {
++ getBukkitEntity().setPlayerListName((setAfk ? net.pl3x.purpur.PurpurConfig.afkTabListPrefix : "") + getName());
++ }
++
++ ((WorldServer) world).everyoneSleeping();
++ }
++
++ @Override
++ public boolean isAfk() {
++ return isAfk;
++ }
++
++ @Override
++ public boolean isCollidable() {
++ return !isAfk() && super.isCollidable();
++ }
++ // Purpur end
++
+ public ServerStatisticManager getStatisticManager() {
+ return this.serverStatisticManager;
+ }
+diff --git a/src/main/java/net/minecraft/server/IEntityAccess.java b/src/main/java/net/minecraft/server/IEntityAccess.java
+index cbaf18af1066e8bde10293bba5eb3060bae1e66f..0c98a436021cbdedba5352073b1f8bf9852298eb 100644
+--- a/src/main/java/net/minecraft/server/IEntityAccess.java
++++ b/src/main/java/net/minecraft/server/IEntityAccess.java
+@@ -174,28 +174,18 @@ public interface IEntityAccess {
+ }
+ // Paper end
+
+- default boolean isPlayerNearby(double d0, double d1, double d2, double d3) {
+- Iterator iterator = this.getPlayers().iterator();
+-
+- double d4;
+-
+- do {
+- EntityHuman entityhuman;
+-
+- do {
+- do {
+- if (!iterator.hasNext()) {
+- return false;
+- }
+-
+- entityhuman = (EntityHuman) iterator.next();
+- } while (!IEntitySelector.g.test(entityhuman));
+- } while (!IEntitySelector.b.test(entityhuman));
+-
+- d4 = entityhuman.h(d0, d1, d2);
+- } while (d3 >= 0.0D && d4 >= d3 * d3);
+-
+- return true;
++ // Purpur start
++ default boolean isPlayerNearby(double x, double y, double z, double distance) {
++ double distanceSq = distance * distance;
++ for (EntityHuman player : getPlayers()) {
++ if (IEntitySelector.notSpectator().test(player) && IEntitySelector.isLivingAlive().test(player) && IEntitySelector.notAfk.test(player)) {
++ if (distance < 0.0D || player.getDistanceSquared(x, y, z) < distanceSq) {
++ return true;
++ }
++ }
++ }
++ return false;
++ // Purpur end
+ }
+
+ @Nullable
+diff --git a/src/main/java/net/minecraft/server/IEntitySelector.java b/src/main/java/net/minecraft/server/IEntitySelector.java
+index b5e1a860a2569d7668330827614d221b60f3fc78..5f85a1d513f4fdc21b64e1a2b6882e3325b98ddd 100644
+--- a/src/main/java/net/minecraft/server/IEntitySelector.java
++++ b/src/main/java/net/minecraft/server/IEntitySelector.java
+@@ -7,6 +7,7 @@ import javax.annotation.Nullable;
+ public final class IEntitySelector {
+
+ public static final Predicate a = Entity::isAlive;
++ public static Predicate isLivingAlive() { return b; } // Purpur - OBFHELPER
+ public static final Predicate b = EntityLiving::isAlive;
+ public static final Predicate c = (entity) -> {
+ return entity.isAlive() && !entity.isVehicle() && !entity.isPassenger();
+@@ -27,6 +28,7 @@ public final class IEntitySelector {
+ return !entity.isSpectator();
+ };
+ public static Predicate isInsomniac = (player) -> MathHelper.clamp(((EntityPlayer) player).getStatisticManager().getStatisticValue(StatisticList.CUSTOM.get(StatisticList.TIME_SINCE_REST)), 1, Integer.MAX_VALUE) >= 72000; // Paper
++ public static Predicate notAfk = (player) -> !player.isAfk(); // Purpur
+
+ // Paper start
+ public static final Predicate affectsSpawning = (entity) -> {
+diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java
+index 4058c1f7ada7d0c9e4ba73a0073b4f94bf410a8f..caf9ce94a7cb6154981d42953c81b588b19e3814 100644
+--- a/src/main/java/net/minecraft/server/PlayerConnection.java
++++ b/src/main/java/net/minecraft/server/PlayerConnection.java
+@@ -250,6 +250,12 @@ public class PlayerConnection implements PacketListenerPlayIn {
+ }
+
+ if (this.player.F() > 0L && this.minecraftServer.getIdleTimeout() > 0 && SystemUtils.getMonotonicMillis() - this.player.F() > (long) (this.minecraftServer.getIdleTimeout() * 1000 * 60)) {
++ // Purpur start
++ this.player.setAfk(true);
++ if (!this.player.world.purpurConfig.idleTimeoutKick) {
++ return;
++ }
++ // Purpur end
+ this.player.resetIdleTimer(); // CraftBukkit - SPIGOT-854
+ this.disconnect(new ChatMessage("multiplayer.disconnect.idling"));
+ }
+@@ -517,6 +523,8 @@ public class PlayerConnection implements PacketListenerPlayIn {
+ this.lastYaw = to.getYaw();
+ this.lastPitch = to.getPitch();
+
++ if (!to.getWorld().getUID().equals(from.getWorld().getUID()) || to.getBlockX() != from.getBlockX() || to.getBlockY() != from.getBlockY() || to.getBlockZ() != from.getBlockZ() || to.getYaw() != from.getYaw() || to.getPitch() != from.getPitch()) this.player.resetIdleTimer(); // Purpur
++
+ // Skip the first time we do this
+ if (true) { // Spigot - don't skip any move events
+ Location oldTo = to.clone();
+@@ -1228,7 +1236,7 @@ public class PlayerConnection implements PacketListenerPlayIn {
+
+ if (!this.player.H() && d11 > org.spigotmc.SpigotConfig.movedWronglyThreshold && !this.player.isSleeping() && !this.player.playerInteractManager.isCreative() && this.player.playerInteractManager.getGameMode() != EnumGamemode.SPECTATOR) { // Spigot
+ flag1 = true; // Tuinity - diff on change, this should be moved wrongly
+- PlayerConnection.LOGGER.warn("{} moved wrongly!", this.player.getDisplayName().getString());
++ PlayerConnection.LOGGER.warn("{} moved wrongly! ({})", this.player.getDisplayName().getString(), d11); // Purpur
+ }
+
+ this.player.setLocation(d4, d5, d6, f, f1);
+@@ -1278,6 +1286,8 @@ public class PlayerConnection implements PacketListenerPlayIn {
+ this.lastYaw = to.getYaw();
+ this.lastPitch = to.getPitch();
+
++ if (!to.getWorld().getUID().equals(from.getWorld().getUID()) || to.getBlockX() != from.getBlockX() || to.getBlockY() != from.getBlockY() || to.getBlockZ() != from.getBlockZ() || to.getYaw() != from.getYaw() || to.getPitch() != from.getPitch()) this.player.resetIdleTimer(); // Purpur
++
+ // Skip the first time we do this
+ if (from.getX() != Double.MAX_VALUE) {
+ Location oldTo = to.clone();
+diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
+index 6ff5ef6b710652f1c4fe6461ff230ee78988f623..efe2a9ad2ece23bd71f4ad63b2c6f54f42345b55 100644
+--- a/src/main/java/net/minecraft/server/WorldServer.java
++++ b/src/main/java/net/minecraft/server/WorldServer.java
+@@ -781,7 +781,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
+ // CraftBukkit end
+
+ if (this.everyoneSleeping && this.players.stream().noneMatch((entityplayer) -> {
+- return !entityplayer.isSpectator() && !entityplayer.isDeeplySleeping() && !entityplayer.fauxSleeping; // CraftBukkit
++ return !entityplayer.isSpectator() && !entityplayer.isDeeplySleeping() && !entityplayer.fauxSleeping && !(purpurConfig.idleTimeoutCountAsSleeping && entityplayer.isAfk()); // CraftBukkit // Purpur
+ })) {
+ // CraftBukkit start
+ long l = this.worldData.getDayTime() + 24000L;
+@@ -1118,7 +1118,7 @@ public class WorldServer extends World implements GeneratorAccessSeed {
+ while (iterator.hasNext()) {
+ EntityPlayer entityplayer = (EntityPlayer) iterator.next();
+
+- if (entityplayer.isSpectator() || (entityplayer.fauxSleeping && !entityplayer.isSleeping())) { // CraftBukkit
++ if (entityplayer.isSpectator() || (entityplayer.fauxSleeping && !entityplayer.isSleeping()) || (purpurConfig.idleTimeoutCountAsSleeping && entityplayer.isAfk())) { // CraftBukkit // Purpur
+ ++i;
+ } else if (entityplayer.isSleeping()) {
+ ++j;
+diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java
+index cb7e34924cb5dbff25d1ffe05cfe5bc22e4a90ed..406e840499e09638e8b325d0e52b764e80acc777 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java
+@@ -1,6 +1,7 @@
+ package net.pl3x.purpur;
+
+ import com.google.common.base.Throwables;
++import net.minecraft.server.LocaleLanguage;
+ import net.minecraft.server.MinecraftServer;
+ import net.pl3x.purpur.command.PurpurCommand;
+ import org.bukkit.Bukkit;
+@@ -129,6 +130,15 @@ public class PurpurConfig {
+ return config.getString(path, config.getString(path));
+ }
+
++ public static String afkBroadcastAway = "§e§o%s is now AFK";
++ public static String afkBroadcastBack = "§e§o%s is no longer AFK";
++ public static String afkTabListPrefix = "[AFK] ";
++ private static void messages() {
++ afkBroadcastAway = getString("settings.messages.afk-broadcast-away", afkBroadcastAway);
++ afkBroadcastBack = getString("settings.messages.afk-broadcast-back", afkBroadcastBack);
++ afkTabListPrefix = getString("settings.messages.afk-tab-list-prefix", afkTabListPrefix);
++ }
++
+ public static String timingsUrl = "https://timings.pl3x.net";
+ private static void timingsSettings() {
+ timingsUrl = getString("settings.timings.url", timingsUrl);
+diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+index 361f7857e461578e90cb71e15027dadaf794cb69..2578a4677d1ee060f687be531e696b7c7be89e84 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+@@ -56,4 +56,15 @@ public class PurpurWorldConfig {
+ PurpurConfig.config.addDefault("world-settings.default." + path, def);
+ return PurpurConfig.config.getString("world-settings." + worldName + "." + path, PurpurConfig.config.getString("world-settings.default." + path));
+ }
++
++ public boolean idleTimeoutKick = true;
++ public boolean idleTimeoutTickNearbyEntities = true;
++ public boolean idleTimeoutCountAsSleeping = false;
++ public boolean idleTimeoutUpdateTabList = false;
++ private void playerIdleTimeoutSettings() {
++ idleTimeoutKick = getBoolean("gameplay-mechanics.player.idle-timeout.kick-if-idle", idleTimeoutKick);
++ idleTimeoutTickNearbyEntities = getBoolean("gameplay-mechanics.player.idle-timeout.tick-nearby-entities", idleTimeoutTickNearbyEntities);
++ idleTimeoutCountAsSleeping = getBoolean("gameplay-mechanics.player.idle-timeout.count-as-sleeping", idleTimeoutCountAsSleeping);
++ idleTimeoutUpdateTabList = getBoolean("gameplay-mechanics.player.idle-timeout.update-tab-list", idleTimeoutUpdateTabList);
++ }
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+index 7c18b22c7b93b6ca1189e481dde17476797b8fd5..debf252a23d0178f06fdadb9c27c3c66b9bbc2d0 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+@@ -2223,4 +2223,21 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
+ return spigot;
+ }
+ // Spigot end
++
++ // Purpur start
++ @Override
++ public boolean isAfk() {
++ return getHandle().isAfk();
++ }
++
++ @Override
++ public void setAfk(boolean setAfk) {
++ getHandle().setAfk(setAfk);
++ }
++
++ @Override
++ public void resetIdleTimer() {
++ getHandle().resetIdleTimer();
++ }
++ // Purpur end
+ }
+diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java
+index 0b93635ba59df4eb4456a97c5e9b51ab5aeda53f..b47d6fa2de3368d1afe329573bc18c3541bb7377 100644
+--- a/src/main/java/org/spigotmc/ActivationRange.java
++++ b/src/main/java/org/spigotmc/ActivationRange.java
+@@ -207,6 +207,7 @@ public class ActivationRange
+ {
+
+ player.activatedTick = MinecraftServer.currentTick;
++ if (!player.world.purpurConfig.idleTimeoutTickNearbyEntities && player.isAfk()) continue; // Purpur
+ maxBB = player.getBoundingBox().grow( maxRange, 256, maxRange );
+ ActivationType.MISC.boundingBox = player.getBoundingBox().grow( miscActivationRange, 256, miscActivationRange );
+ ActivationType.RAIDER.boundingBox = player.getBoundingBox().grow( raiderActivationRange, 256, raiderActivationRange );
diff --git a/patches/Purpur/patches/server/0009-Bring-back-server-name.patch b/patches/Purpur/patches/server/0009-Bring-back-server-name.patch
new file mode 100644
index 00000000..4fc877ce
--- /dev/null
+++ b/patches/Purpur/patches/server/0009-Bring-back-server-name.patch
@@ -0,0 +1,34 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sun, 26 May 2019 15:19:14 -0500
+Subject: [PATCH] Bring back server name
+
+
+diff --git a/src/main/java/net/minecraft/server/DedicatedServerProperties.java b/src/main/java/net/minecraft/server/DedicatedServerProperties.java
+index 65961a03728852bd75367083a0de6fd0082b17cb..780474397acb4d0e7ecb4540e1a2db5721e59d3d 100644
+--- a/src/main/java/net/minecraft/server/DedicatedServerProperties.java
++++ b/src/main/java/net/minecraft/server/DedicatedServerProperties.java
+@@ -10,6 +10,7 @@ public class DedicatedServerProperties extends PropertyManager
+Date: Sat, 21 Mar 2020 11:47:39 -0500
+Subject: [PATCH] Configurable server mod name
+
+
+diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
+index af5c1479d2cb8092d84e2d3d5166060d9ff2df71..90556fca9315e71c4f3fbd231ac6d765677ca271 100644
+--- a/src/main/java/net/minecraft/server/MinecraftServer.java
++++ b/src/main/java/net/minecraft/server/MinecraftServer.java
+@@ -1511,7 +1511,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant
+Date: Sun, 5 May 2019 12:58:45 -0500
+Subject: [PATCH] LivingEntity safeFallDistance
+
+
+diff --git a/src/main/java/net/minecraft/server/EntityGiantZombie.java b/src/main/java/net/minecraft/server/EntityGiantZombie.java
+index 702242653a47051c9ed32304c427c27652af6157..9f4f56c47ecd4b35ebf33ca0bf9a040074ababf2 100644
+--- a/src/main/java/net/minecraft/server/EntityGiantZombie.java
++++ b/src/main/java/net/minecraft/server/EntityGiantZombie.java
+@@ -4,6 +4,7 @@ public class EntityGiantZombie extends EntityMonster {
+
+ public EntityGiantZombie(EntityTypes extends EntityGiantZombie> entitytypes, World world) {
+ super(entitytypes, world);
++ this.safeFallDistance = 10.0F; // Purpur
+ }
+
+ @Override
+diff --git a/src/main/java/net/minecraft/server/EntityHorseAbstract.java b/src/main/java/net/minecraft/server/EntityHorseAbstract.java
+index 2a91f07ca9c4dc0cb3b5aef5c9c1db7f69773530..7604fd83de9cfe93d427a9a1f6bbbee76aa861e8 100644
+--- a/src/main/java/net/minecraft/server/EntityHorseAbstract.java
++++ b/src/main/java/net/minecraft/server/EntityHorseAbstract.java
+@@ -210,7 +210,7 @@ public abstract class EntityHorseAbstract extends EntityAnimal implements IInven
+
+ @Override
+ protected int e(float f, float f1) {
+- return MathHelper.f((f * 0.5F - 3.0F) * f1);
++ return MathHelper.f((f * 0.5F - this.safeFallDistance) * f1); // Purpur
+ }
+
+ protected int getChestSlots() {
+diff --git a/src/main/java/net/minecraft/server/EntityLiving.java b/src/main/java/net/minecraft/server/EntityLiving.java
+index 4fe5a8d0201d662c68dd58eeb8cf1d304787edb4..279662570c0bbd6c00b5732881cd35dfe694b25d 100644
+--- a/src/main/java/net/minecraft/server/EntityLiving.java
++++ b/src/main/java/net/minecraft/server/EntityLiving.java
+@@ -132,6 +132,7 @@ public abstract class EntityLiving extends Entity {
+ // CraftBukkit start
+ public int expToDrop;
+ public int maxAirTicks = 300;
++ public float safeFallDistance = 3.0F; // Purpur
+ boolean forceDrops;
+ ArrayList drops = new ArrayList();
+ public final org.bukkit.craftbukkit.attribute.CraftAttributeMap craftAttributes;
+@@ -226,8 +227,8 @@ public abstract class EntityLiving extends Entity {
+ this.cR();
+ }
+
+- if (!this.world.isClientSide && this.fallDistance > 3.0F && flag) {
+- float f = (float) MathHelper.f(this.fallDistance - 3.0F);
++ if (!this.world.isClientSide && this.fallDistance > this.safeFallDistance && flag) { // Purpur
++ float f = (float) MathHelper.f(this.fallDistance - this.safeFallDistance); // Purpur
+
+ if (!iblockdata.isAir()) {
+ double d1 = Math.min((double) (0.2F + f / 15.0F), 2.5D);
+@@ -1685,7 +1686,7 @@ public abstract class EntityLiving extends Entity {
+ MobEffect mobeffect = this.getEffect(MobEffects.JUMP);
+ float f2 = mobeffect == null ? 0.0F : (float) (mobeffect.getAmplifier() + 1);
+
+- return MathHelper.f((f - 3.0F - f2) * f1);
++ return MathHelper.f((f - this.safeFallDistance - f2) * f1); // Purpur
+ }
+
+ protected void playBlockStepSound() {
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
+index 16e69cfd4994fd6850ee3635ea819379412351c9..0292cae6225ae2ee156f436c8827a018e8ffa723 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
+@@ -826,4 +826,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
+ getHandle().setHurtDirection(hurtDirection);
+ }
+ // Paper end
++
++ // Purpur start
++ @Override
++ public float getSafeFallDistance() {
++ return getHandle().safeFallDistance;
++ }
++
++ @Override
++ public void setSafeFallDistance(float safeFallDistance) {
++ getHandle().safeFallDistance = safeFallDistance;
++ }
++ // Purpur end
+ }
diff --git a/patches/Purpur/patches/server/0012-Lagging-threshold.patch b/patches/Purpur/patches/server/0012-Lagging-threshold.patch
new file mode 100644
index 00000000..1eccab1d
--- /dev/null
+++ b/patches/Purpur/patches/server/0012-Lagging-threshold.patch
@@ -0,0 +1,57 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Tue, 23 Jul 2019 10:07:16 -0500
+Subject: [PATCH] Lagging threshold
+
+
+diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
+index 90556fca9315e71c4f3fbd231ac6d765677ca271..e12fecf47798f638b85e7c6d54055a526097811c 100644
+--- a/src/main/java/net/minecraft/server/MinecraftServer.java
++++ b/src/main/java/net/minecraft/server/MinecraftServer.java
+@@ -163,6 +163,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant