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..bc175666
--- /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 27848ad0eee667e515685a5f1cef3e9bfc7a3f53..09729abc27b6cb3458e19af24137bbbc6e5cb63e 100644
+--- a/src/main/java/org/bukkit/entity/Player.java
++++ b/src/main/java/org/bukkit/entity/Player.java
+@@ -2139,4 +2139,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..e49a4594
--- /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 e8414592b3afeb1e5db2b817b8fb7c13e073b9aa..281c5a72cf59dd5cb3dee47541641483d434aeb0 100644
+--- a/src/main/java/org/bukkit/Bukkit.java
++++ b/src/main/java/org/bukkit/Bukkit.java
+@@ -1963,4 +1963,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 0888165f2327bad2125d9cbe9b72e3282d44e072..eebaee3f3e01f39c0378cc89381eb97d95f31152 100644
+--- a/src/main/java/org/bukkit/Server.java
++++ b/src/main/java/org/bukkit/Server.java
+@@ -1736,4 +1736,13 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
+ @NotNull
+ io.papermc.paper.datapack.DatapackManager getDatapackManager();
+ // 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..7383c9a6
--- /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 ccb81ceee74fff50ec3ed88ae0a41f790c40ae87..b96edbf7e5a2bf66a9ebf9300368e8f6a8d561f9 100644
+--- a/src/main/java/org/bukkit/entity/LivingEntity.java
++++ b/src/main/java/org/bukkit/entity/LivingEntity.java
+@@ -873,4 +873,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..b5636be5
--- /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 281c5a72cf59dd5cb3dee47541641483d434aeb0..a60c8bd89d14e47d3243307241458e907249a99b 100644
+--- a/src/main/java/org/bukkit/Bukkit.java
++++ b/src/main/java/org/bukkit/Bukkit.java
+@@ -1973,5 +1973,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 eebaee3f3e01f39c0378cc89381eb97d95f31152..f24d00951cd6951023f1ff34312ae0438107fc22 100644
+--- a/src/main/java/org/bukkit/Server.java
++++ b/src/main/java/org/bukkit/Server.java
+@@ -1744,5 +1744,12 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
+ */
+ @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..f426c80e
--- /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 ec7c07564608386c3f7d4322d0af4cbb7d92bc74..39323b3151c733392333858a2dbf1f3f8637341e 100644
+--- a/src/main/java/org/bukkit/inventory/ItemFactory.java
++++ b/src/main/java/org/bukkit/inventory/ItemFactory.java
+@@ -242,4 +242,15 @@ public interface ItemFactory {
+ @Deprecated
+ 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..74209f30
--- /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..8f1c9c421aeeb0ddf331f076a9b646c510ea4337
+--- /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.Entity;
++import org.bukkit.entity.HumanEntity;
++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 Entity entity;
++ private final ItemStack item;
++
++ public MonsterEggSpawnEvent(@Nullable HumanEntity player, @NotNull Entity entity, @NotNull ItemStack item) {
++ this.player = (Player) player;
++ this.entity = entity;
++ this.item = item;
++ }
++
++ @Nullable
++ public Player getPlayer() {
++ return player;
++ }
++
++ @NotNull
++ public Entity getEntity() {
++ return entity;
++ }
++
++ public void setEntity(@Nullable Entity 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-Player-invulnerabilities.patch b/patches/Purpur/patches/api/0015-Player-invulnerabilities.patch
new file mode 100644
index 00000000..d7c53b52
--- /dev/null
+++ b/patches/Purpur/patches/api/0015-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 09729abc27b6cb3458e19af24137bbbc6e5cb63e..d6b6508fd7ab245f657be262c54ae6dfa20415e7 100644
+--- a/src/main/java/org/bukkit/entity/Player.java
++++ b/src/main/java/org/bukkit/entity/Player.java
+@@ -2159,5 +2159,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/0016-Anvil-API.patch b/patches/Purpur/patches/api/0016-Anvil-API.patch
new file mode 100644
index 00000000..f660b4e7
--- /dev/null
+++ b/patches/Purpur/patches/api/0016-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/0017-ItemStack-convenience-methods.patch b/patches/Purpur/patches/api/0017-ItemStack-convenience-methods.patch
new file mode 100644
index 00000000..f7418988
--- /dev/null
+++ b/patches/Purpur/patches/api/0017-ItemStack-convenience-methods.patch
@@ -0,0 +1,701 @@
+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 e2b3470e3c9a97671723f5a67f722fb86fb07fbf..560b441ef35e507236e683b04f6a774c5949a078 100644
+--- a/src/main/java/org/bukkit/Material.java
++++ b/src/main/java/org/bukkit/Material.java
+@@ -8731,4 +8731,40 @@ 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 NETHERITE_BOOTS:
++ case NETHERITE_CHESTPLATE:
++ case NETHERITE_HELMET:
++ case NETHERITE_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 a7909406e9d54c1ab4789b984ed6b1da50837fce..ac2967eac165d74c8ee7e0e9ac63124a10851a0e 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.
+@@ -921,4 +933,626 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, net.kyor
+ return Bukkit.getUnsafe().isValidRepairItemStack(toBeRepaired, this);
+ }
+ // 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 hasItemMeta() && 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 hasItemMeta() && getItemMeta().hasLocalizedName();
++ }
++
++ /**
++ * Checks for existence of lore.
++ *
++ * @return true if this has lore
++ */
++ public boolean hasLore() {
++ return hasItemMeta() && 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 hasItemMeta() && 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 hasItemMeta() && 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 hasItemMeta() && 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 hasItemMeta() && getItemMeta().hasCustomModelData();
++ }
++
++ /**
++ * Returns whether the item has block data currently attached to it.
++ *
++ * @return whether block data is already attached
++ */
++ public boolean hasBlockData() {
++ return hasItemMeta() && ((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 hasItemMeta() && ((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 hasItemMeta() && 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 hasItemMeta() && 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 hasItemMeta() && ((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 hasItemMeta() && 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 hasItemMeta() && 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/0018-Phantoms-attracted-to-crystals-and-crystals-shoot-ph.patch b/patches/Purpur/patches/api/0018-Phantoms-attracted-to-crystals-and-crystals-shoot-ph.patch
new file mode 100644
index 00000000..bdaed9c0
--- /dev/null
+++ b/patches/Purpur/patches/api/0018-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/0019-ChatColor-conveniences.patch b/patches/Purpur/patches/api/0019-ChatColor-conveniences.patch
new file mode 100644
index 00000000..9c0f19ba
--- /dev/null
+++ b/patches/Purpur/patches/api/0019-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/0020-Ridables.patch b/patches/Purpur/patches/api/0020-Ridables.patch
new file mode 100644
index 00000000..0031ab00
--- /dev/null
+++ b/patches/Purpur/patches/api/0020-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 46985eaea3d3b00d1dd88c2dd5a2bc53d518c64f..38c6ecba4a6090ee42180ff52db42bac8e7f95d7 100644
+--- a/src/main/java/org/bukkit/entity/Entity.java
++++ b/src/main/java/org/bukkit/entity/Entity.java
+@@ -704,4 +704,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/0021-Configurable-permission-message-upgrades.patch b/patches/Purpur/patches/api/0021-Configurable-permission-message-upgrades.patch
new file mode 100644
index 00000000..bda8c7b7
--- /dev/null
+++ b/patches/Purpur/patches/api/0021-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/0022-LivingEntity-broadcastItemBreak.patch b/patches/Purpur/patches/api/0022-LivingEntity-broadcastItemBreak.patch
new file mode 100644
index 00000000..ff7ac930
--- /dev/null
+++ b/patches/Purpur/patches/api/0022-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 b96edbf7e5a2bf66a9ebf9300368e8f6a8d561f9..f64cd3a467ccb4f773138542a6beb61ab2e1ae40 100644
+--- a/src/main/java/org/bukkit/entity/LivingEntity.java
++++ b/src/main/java/org/bukkit/entity/LivingEntity.java
+@@ -888,5 +888,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/0023-Item-entity-immunities.patch b/patches/Purpur/patches/api/0023-Item-entity-immunities.patch
new file mode 100644
index 00000000..0d320bc5
--- /dev/null
+++ b/patches/Purpur/patches/api/0023-Item-entity-immunities.patch
@@ -0,0 +1,73 @@
+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..5b9a20e0695218f1239d2bf1d0368291e2a10c06 100644
+--- a/src/main/java/org/bukkit/entity/Item.java
++++ b/src/main/java/org/bukkit/entity/Item.java
+@@ -120,4 +120,62 @@ 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();
++
++ /**
++ * Set whether or not this item is immune to lightning
++ *
++ * @param immuneToLightning True to make immune to lightning
++ */
++ void setImmuneToLightning(boolean immuneToLightning);
++
++ /**
++ * Check if item is immune to lightning
++ *
++ * @return True if immune to lightning
++ */
++ boolean isImmuneToLightning();
++ // Purpur end
+ }
diff --git a/patches/Purpur/patches/api/0024-Spigot-Improve-output-of-plugins-command.patch b/patches/Purpur/patches/api/0024-Spigot-Improve-output-of-plugins-command.patch
new file mode 100644
index 00000000..bb371528
--- /dev/null
+++ b/patches/Purpur/patches/api/0024-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/0025-Add-option-to-disable-zombie-aggressiveness-towards-.patch b/patches/Purpur/patches/api/0025-Add-option-to-disable-zombie-aggressiveness-towards-.patch
new file mode 100644
index 00000000..45b72c6b
--- /dev/null
+++ b/patches/Purpur/patches/api/0025-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/0026-Add-predicate-to-recipe-s-ExactChoice-ingredient.patch b/patches/Purpur/patches/api/0026-Add-predicate-to-recipe-s-ExactChoice-ingredient.patch
new file mode 100644
index 00000000..db153e66
--- /dev/null
+++ b/patches/Purpur/patches/api/0026-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 90208bc96085f05a3b657b9467b1670d00b03104..c59d5e4ef9641fd73463b177239226866272745b 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
+@@ -152,6 +153,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));
+@@ -196,6 +198,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;
+@@ -205,6 +208,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/0027-Add-critical-hit-check-to-EntityDamagedByEntityEvent.patch b/patches/Purpur/patches/api/0027-Add-critical-hit-check-to-EntityDamagedByEntityEvent.patch
new file mode 100644
index 00000000..ebfe2021
--- /dev/null
+++ b/patches/Purpur/patches/api/0027-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/0028-Left-handed-API.patch b/patches/Purpur/patches/api/0028-Left-handed-API.patch
new file mode 100644
index 00000000..5e8b1e02
--- /dev/null
+++ b/patches/Purpur/patches/api/0028-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 7d4ce660adb21e579e564796568945ee20f0ca59..4205dc5746dafd966f95103cdd2a1a55e79642f8 100644
+--- a/src/main/java/org/bukkit/entity/Mob.java
++++ b/src/main/java/org/bukkit/entity/Mob.java
+@@ -146,4 +146,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/0029-Alphabetize-in-game-plugins-list.patch b/patches/Purpur/patches/api/0029-Alphabetize-in-game-plugins-list.patch
new file mode 100644
index 00000000..0e1b879b
--- /dev/null
+++ b/patches/Purpur/patches/api/0029-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/0030-Rabid-Wolf-API.patch b/patches/Purpur/patches/api/0030-Rabid-Wolf-API.patch
new file mode 100644
index 00000000..a3c3a541
--- /dev/null
+++ b/patches/Purpur/patches/api/0030-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/0031-Fix-javadoc-warnings-missing-param-and-return.patch b/patches/Purpur/patches/api/0031-Fix-javadoc-warnings-missing-param-and-return.patch
new file mode 100644
index 00000000..b712f808
--- /dev/null
+++ b/patches/Purpur/patches/api/0031-Fix-javadoc-warnings-missing-param-and-return.patch
@@ -0,0 +1,1660 @@
+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 a736d7bcdc5861a01b66ba36158db1c716339346..4825c9ca2191d3bf1440b986827fc32e230a3280 100644
+--- a/src/main/java/com/destroystokyo/paper/util/VersionFetcher.java
++++ b/src/main/java/com/destroystokyo/paper/util/VersionFetcher.java
+@@ -5,6 +5,9 @@ import net.kyori.adventure.text.format.NamedTextColor;
+ import org.bukkit.Bukkit;
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * Version fetcher
++ */
+ public interface VersionFetcher {
+ /**
+ * Amount of time to cache results for in milliseconds
+@@ -26,6 +29,9 @@ public interface VersionFetcher {
+ @NotNull
+ Component 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/event/player/AsyncChatEvent.java b/src/main/java/io/papermc/paper/event/player/AsyncChatEvent.java
+index ee7ec316a2f814ec759e0a3e5dfe5efbee782b22..1bf28ec988aad12ede93fa508075811601c82d60 100644
+--- a/src/main/java/io/papermc/paper/event/player/AsyncChatEvent.java
++++ b/src/main/java/io/papermc/paper/event/player/AsyncChatEvent.java
+@@ -53,6 +53,11 @@ public final class AsyncChatEvent extends AbstractChatEvent {
+ }
+
+ /**
++ * @param async Async
++ * @param player Player
++ * @param recipients Recipients
++ * @param formatter Formatter
++ * @param message Message
+ * @deprecated for removal with 1.17, use {@link #AsyncChatEvent(boolean, Player, Set, ChatRenderer, Component, Component)}
+ */
+ @Deprecated
+diff --git a/src/main/java/io/papermc/paper/event/player/ChatEvent.java b/src/main/java/io/papermc/paper/event/player/ChatEvent.java
+index c6bcf0dc3f77c631aa7eeb9b1e88b5bbfe445fc6..c1c6d45b10cac170e2a9b6ec7dc273d12f21163a 100644
+--- a/src/main/java/io/papermc/paper/event/player/ChatEvent.java
++++ b/src/main/java/io/papermc/paper/event/player/ChatEvent.java
+@@ -50,6 +50,10 @@ public final class ChatEvent extends AbstractChatEvent {
+ }
+
+ /**
++ * @param player Player
++ * @param recipients Recipients
++ * @param formatter Formatter
++ * @param message Message
+ * @deprecated for removal with 1.17, use {@link #ChatEvent(Player, Set, ChatRenderer, Component, Component)}
+ */
+ @Deprecated
+diff --git a/src/main/java/io/papermc/paper/inventory/ItemRarity.java b/src/main/java/io/papermc/paper/inventory/ItemRarity.java
+index 74ef8395cc040ce488c2acaa416db20272cc2734..b974627a415cd6897b245275e953cc907a5929d8 100644
+--- a/src/main/java/io/papermc/paper/inventory/ItemRarity.java
++++ b/src/main/java/io/papermc/paper/inventory/ItemRarity.java
+@@ -19,7 +19,7 @@ public enum ItemRarity {
+
+ /**
+ * Gets the color formatting associated with the rarity.
+- * @return
++ * @return TextColor
+ */
+ @NotNull
+ public TextColor getColor() {
+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 2acdf2a6d3955923c721222b9da784f3278f6418..737c3b053060e38f6776a5a508948cc300bbe6f4 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 {
+
+ // Paper start
+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 f24d00951cd6951023f1ff34312ae0438107fc22..3ee71225eb67ec728b414a8cdc0570d48d5603e3 100644
+--- a/src/main/java/org/bukkit/Server.java
++++ b/src/main/java/org/bukkit/Server.java
+@@ -1579,6 +1579,9 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
+ 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 379acee1b5f2d06e6a96f3444783f4a29ca24095..ef3011d74ce9acf02d0ee857033816854134ec0e 100644
+--- a/src/main/java/org/bukkit/UnsafeValues.java
++++ b/src/main/java/org/bukkit/UnsafeValues.java
+@@ -87,6 +87,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();
+@@ -105,6 +107,8 @@ public interface UnsafeValues {
+ /**
+ * Return the translation key for the Material, so the client can translate it into the active
+ * locale when using a {@link net.kyori.adventure.text.TranslatableComponent}.
++ *
++ * @param mat Material to check
+ * @return the translation key
+ */
+ String getTranslationKey(Material mat);
+@@ -112,6 +116,8 @@ public interface UnsafeValues {
+ /**
+ * Return the translation key for the Block, so the client can translate it into the active
+ * locale when using a {@link net.kyori.adventure.text.TranslatableComponent}.
++ *
++ * @param block Block to check
+ * @return the translation key
+ */
+ String getTranslationKey(org.bukkit.block.Block block);
+@@ -120,6 +126,8 @@ public interface UnsafeValues {
+ * Return the translation key for the EntityType, so the client can translate it into the active
+ * locale when using a {@link net.kyori.adventure.text.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);
+@@ -135,6 +143,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 e6a83252f42da31ad38f8dc1beccc7aa2c3f54b8..f3b107210473f1707b051c15771ce3bf2a62f171 100644
+--- a/src/main/java/org/bukkit/WorldCreator.java
++++ b/src/main/java/org/bukkit/WorldCreator.java
+@@ -71,6 +71,8 @@ public class WorldCreator {
+ *
+ * @param levelName LevelName of the world that will be created
+ * @param worldKey NamespacedKey of the world that will be created
++ *
++ * @return WorldCreator
+ */
+ @NotNull
+ public static WorldCreator ofNameAndKey(@NotNull String levelName, @NotNull NamespacedKey worldKey) {
+@@ -82,6 +84,8 @@ public class WorldCreator {
+ * LevelName will be the Key part of the NamespacedKey.
+ *
+ * @param worldKey NamespacedKey of the world that will be created
++ *
++ * @return WorldCreator
+ */
+ @NotNull
+ public static WorldCreator ofKey(@NotNull NamespacedKey worldKey) {
+@@ -293,11 +297,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 08e6f1741685f54506c8a4ff29bbd30f62cf8e45..8efd2669bd5e3dfa47ff8fcb858333210eb5c201 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 30c7df0021df44a411e50636d906d4a1d30fd927..73930312accf6d8c5d71777caa8190a15c2f036d 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 fb0e608fa92dae99b9eee8fc1cbdf4b91a33e620..ca6d3afd6fa51c0822e289356025b51bc50f55a7 100644
+--- a/src/main/java/org/bukkit/command/CommandSender.java
++++ b/src/main/java/org/bukkit/command/CommandSender.java
+@@ -62,6 +62,9 @@ public interface CommandSender extends net.kyori.adventure.audience.Audience, Pe
+ 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 2f0c6af7fa6688a98d6aa0bd3f0e6556af8330d0..b38c69482e3112e0cd626bcb17f4523c541b748f 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 38c6ecba4a6090ee42180ff52db42bac8e7f95d7..b47e31d2b9b41b39b46892fe10bf36d82c5d8e1b 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 {
+
+ }
+@@ -671,36 +674,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 d6b6508fd7ab245f657be262c54ae6dfa20415e7..25252bad38ca35b81b225b57c4b6ce77ad6de166 100644
+--- a/src/main/java/org/bukkit/entity/Player.java
++++ b/src/main/java/org/bukkit/entity/Player.java
+@@ -1961,6 +1961,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
+@@ -2000,6 +2002,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 010e1f9c3567a2fe8297fe04fcf7b75df0279eca..bd40fdfbcd9a34c7cde5f4dc34cba53aec53c485 100644
+--- a/src/main/java/org/bukkit/entity/Shulker.java
++++ b/src/main/java/org/bukkit/entity/Shulker.java
+@@ -4,6 +4,9 @@ import org.bukkit.block.BlockFace;
+ import org.bukkit.material.Colorable;
+ import org.jetbrains.annotations.NotNull;
+
++/**
++ * 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 684477b894e52ff33f9fce2edf76e58c292dd75e..581abc69290ca14b8e64f50fdf5a49c14be13940 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 91cab8b13d5bba34007f124838b32a1df58c5ac7..078228106b299a8e38495f7f881d38de4f87bc95 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 76ad715961c1b373ba276c61ced728affd4dbec1..27ce18199100b181a0502bc6de12cc7dfc430f86 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 e0e068799a1868c8e561869015f41f553ef4fbdb..9fa0ba2f81a6724491c22446c87135841d099fb0 100644
+--- a/src/main/java/org/bukkit/event/entity/EntityUnleashEvent.java
++++ b/src/main/java/org/bukkit/event/entity/EntityUnleashEvent.java
+@@ -66,6 +66,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 ca660dde2010098e8c77141d05c2d4d5470adf81..129eac25da4f27489038fb15ab1aeecb172b60cc 100644
+--- a/src/main/java/org/bukkit/event/inventory/InventoryType.java
++++ b/src/main/java/org/bukkit/event/inventory/InventoryType.java
+@@ -185,6 +185,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 b6016aa1e91863efc252eecab69ade6f54c89f27..e43acfb570036adb73d195136573620378cc6a61 100644
+--- a/src/main/java/org/bukkit/event/player/PlayerQuitEvent.java
++++ b/src/main/java/org/bukkit/event/player/PlayerQuitEvent.java
+@@ -94,6 +94,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 39323b3151c733392333858a2dbf1f3f8637341e..b2af5217a74266917365c3bf216a780b95c833bd 100644
+--- a/src/main/java/org/bukkit/inventory/ItemFactory.java
++++ b/src/main/java/org/bukkit/inventory/ItemFactory.java
+@@ -148,6 +148,7 @@ public interface ItemFactory {
+ * Creates a hover event for the given item.
+ *
+ * @param item The item
++ * @param op Unary operator
+ * @return A hover event
+ */
+ @NotNull
+@@ -188,7 +189,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 4947251f347d83fa326a67735293401c10d87ba4..248cd6faa72a91ceaf6f74d06c05d05bd26f5fbc 100644
+--- a/src/main/java/org/bukkit/inventory/meta/BookMeta.java
++++ b/src/main/java/org/bukkit/inventory/meta/BookMeta.java
+@@ -315,6 +315,9 @@ public interface BookMeta extends ItemMeta, net.kyori.adventure.inventory.Book {
+ 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/0032-PlayerBookTooLargeEvent.patch b/patches/Purpur/patches/api/0032-PlayerBookTooLargeEvent.patch
new file mode 100644
index 00000000..dc89fde7
--- /dev/null
+++ b/patches/Purpur/patches/api/0032-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/0033-Full-netherite-armor-grants-fire-resistance.patch b/patches/Purpur/patches/api/0033-Full-netherite-armor-grants-fire-resistance.patch
new file mode 100644
index 00000000..4cf6d1a0
--- /dev/null
+++ b/patches/Purpur/patches/api/0033-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/0034-Add-EntityTeleportHinderedEvent.patch b/patches/Purpur/patches/api/0034-Add-EntityTeleportHinderedEvent.patch
new file mode 100644
index 00000000..587b3f0e
--- /dev/null
+++ b/patches/Purpur/patches/api/0034-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/0035-Add-StructureGenerateEvent.patch b/patches/Purpur/patches/api/0035-Add-StructureGenerateEvent.patch
new file mode 100644
index 00000000..6ef11884
--- /dev/null
+++ b/patches/Purpur/patches/api/0035-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/api/0036-Add-unsafe-Entity-serialization-API.patch b/patches/Purpur/patches/api/0036-Add-unsafe-Entity-serialization-API.patch
new file mode 100644
index 00000000..db374eca
--- /dev/null
+++ b/patches/Purpur/patches/api/0036-Add-unsafe-Entity-serialization-API.patch
@@ -0,0 +1,80 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Mariell Hoversholm
+Date: Sat, 9 Jan 2021 21:21:27 +0100
+Subject: [PATCH] Add unsafe Entity serialization API
+
+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/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java
+index ef3011d74ce9acf02d0ee857033816854134ec0e..b2502aaab690b1414a1adffdf64e5a5456feb99c 100644
+--- a/src/main/java/org/bukkit/UnsafeValues.java
++++ b/src/main/java/org/bukkit/UnsafeValues.java
+@@ -194,4 +194,28 @@ public interface UnsafeValues {
+ */
+ int getProtocolVersion();
+ // Paper end
++
++ // Purpur start
++
++ /**
++ * Serialize entity to byte array
++ *
++ * @param entity entity to serialize
++ * @return serialized entity
++ */
++ byte[] serializeEntity(org.bukkit.entity.Entity entity);
++
++ /**
++ * Deserialize an entity from byte array
++ *
++ * The entity is not automatically spawned in the world. You will have to spawn
++ * the entity yourself with {@link org.bukkit.entity.Entity#spawnAt(Location)} or
++ * {@link org.bukkit.entity.Entity#spawnAt(Location, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason)}
++ *
++ * @param data serialized entity
++ * @param world world entity belongs in
++ * @return deserialized entity
++ */
++ org.bukkit.entity.Entity deserializeEntity(byte[] data, org.bukkit.World world);
++ // Purpur end
+ }
+diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java
+index b47e31d2b9b41b39b46892fe10bf36d82c5d8e1b..7fa5242bd44c9b19648d79fa8fecbb7ee125288e 100644
+--- a/src/main/java/org/bukkit/entity/Entity.java
++++ b/src/main/java/org/bukkit/entity/Entity.java
+@@ -751,5 +751,24 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent
+ * @return True if ridable in water
+ */
+ boolean isRidableInWater();
++
++ /**
++ * Spawn this entity in the world at the given {@link Location} with the default spawn reason.
++ *
++ * @param location The location at which to spawn the entity.
++ * @return Whether the entity was successfully spawned.
++ */
++ default boolean spawnAt(@NotNull Location location) {
++ return spawnAt(location, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT);
++ }
++
++ /**
++ * Spawn this entity in the world at the given {@link Location} with the reason given.
++ *
++ * @param location The location at which to spawn the entity.
++ * @param spawnReason The reason for which the entity was spawned.
++ * @return Whether the entity was successfully spawned.
++ */
++ boolean spawnAt(@NotNull Location location, @NotNull org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason);
+ // Purpur end
+ }
diff --git a/patches/Purpur/patches/api/0037-Conflict-on-change-for-adventure-deprecations.patch b/patches/Purpur/patches/api/0037-Conflict-on-change-for-adventure-deprecations.patch
new file mode 100644
index 00000000..0cbe2e1b
--- /dev/null
+++ b/patches/Purpur/patches/api/0037-Conflict-on-change-for-adventure-deprecations.patch
@@ -0,0 +1,867 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: BillyGalbreath
+Date: Wed, 17 Mar 2021 15:56:47 -0500
+Subject: [PATCH] Conflict on change for adventure deprecations
+
+
+diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java
+index a60c8bd89d14e47d3243307241458e907249a99b..194261bf20bb727d207a2429fa59abf0acf61b19 100644
+--- a/src/main/java/org/bukkit/Bukkit.java
++++ b/src/main/java/org/bukkit/Bukkit.java
+@@ -303,7 +303,7 @@ public final class Bukkit {
+ * @return the number of players
+ * @deprecated in favour of {@link Server#broadcast(net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public static int broadcastMessage(@NotNull String message) {
+ return server.broadcastMessage(message);
+ }
+@@ -947,7 +947,7 @@ public final class Bukkit {
+ * @return number of message recipients
+ * @deprecated in favour of {@link #broadcast(net.kyori.adventure.text.Component, String)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public static int broadcast(@NotNull String message, @NotNull String permission) {
+ return server.broadcast(message, permission);
+ }
+@@ -1224,7 +1224,7 @@ public final class Bukkit {
+ *
+ * @see InventoryType#isCreatable()
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ @NotNull
+ public static Inventory createInventory(@Nullable InventoryHolder owner, @NotNull InventoryType type, @NotNull String title) {
+ return server.createInventory(owner, type, title);
+@@ -1274,7 +1274,7 @@ public final class Bukkit {
+ * @throws IllegalArgumentException if the size is not a multiple of 9
+ * @deprecated in favour of {@link #createInventory(InventoryHolder, InventoryType, net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ @NotNull
+ public static Inventory createInventory(@Nullable InventoryHolder owner, int size, @NotNull String title) throws IllegalArgumentException {
+ return server.createInventory(owner, size, title);
+@@ -1301,7 +1301,7 @@ public final class Bukkit {
+ * @deprecated in favour of {@link #createMerchant(net.kyori.adventure.text.Component)}
+ */
+ @NotNull
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public static Merchant createMerchant(@Nullable String title) {
+ return server.createMerchant(title);
+ }
+@@ -1390,7 +1390,7 @@ public final class Bukkit {
+ * @deprecated in favour of {@link #motd()}
+ */
+ @NotNull
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public static String getMotd() {
+ return server.getMotd();
+ }
+@@ -1412,7 +1412,7 @@ public final class Bukkit {
+ * @deprecated in favour of {@link #shutdownMessage()}
+ */
+ @Nullable
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public static String getShutdownMessage() {
+ return server.getShutdownMessage();
+ }
+diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java
+index 3ee71225eb67ec728b414a8cdc0570d48d5603e3..a7fcd00ae37f9a2026759642562b1059c9dd9526 100644
+--- a/src/main/java/org/bukkit/Server.java
++++ b/src/main/java/org/bukkit/Server.java
+@@ -250,7 +250,7 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
+ * @return the number of players
+ * @deprecated use {@link #broadcast(net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public int broadcastMessage(@NotNull String message);
+
+ // Paper start
+@@ -773,7 +773,7 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
+ * @return number of message recipients
+ * @deprecated in favour of {@link #broadcast(net.kyori.adventure.text.Component, String)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public int broadcast(@NotNull String message, @NotNull String permission);
+ // Paper start
+ /**
+@@ -1035,7 +1035,7 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
+ *
+ * @see InventoryType#isCreatable()
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ @NotNull
+ Inventory createInventory(@Nullable InventoryHolder owner, @NotNull InventoryType type, @NotNull String title);
+
+@@ -1079,7 +1079,7 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
+ * @throws IllegalArgumentException if the size is not a multiple of 9
+ * @deprecated in favour of {@link #createInventory(InventoryHolder, int, net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ @NotNull
+ Inventory createInventory(@Nullable InventoryHolder owner, int size, @NotNull String title) throws IllegalArgumentException;
+
+@@ -1102,7 +1102,7 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
+ * @deprecated in favour of {@link #createMerchant(net.kyori.adventure.text.Component)}
+ */
+ @NotNull
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ Merchant createMerchant(@Nullable String title);
+
+ /**
+@@ -1175,7 +1175,7 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
+ * @deprecated in favour of {@link #motd()}
+ */
+ @NotNull
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ String getMotd();
+
+ // Paper start
+@@ -1193,7 +1193,7 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
+ * @deprecated in favour of {@link #shutdownMessage()}
+ */
+ @Nullable
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ String getShutdownMessage();
+
+ /**
+diff --git a/src/main/java/org/bukkit/block/Sign.java b/src/main/java/org/bukkit/block/Sign.java
+index 6ea9b54d95d80070c01a612c0ce2ab37f0b4ad41..fe9ec9cb7875df4a40d1c4155e13cca9b3628b30 100644
+--- a/src/main/java/org/bukkit/block/Sign.java
++++ b/src/main/java/org/bukkit/block/Sign.java
+@@ -48,7 +48,7 @@ public interface Sign extends TileState, Colorable {
+ * @deprecated in favour of {@link #lines()}
+ */
+ @NotNull
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public String[] getLines();
+
+ /**
+@@ -62,7 +62,7 @@ public interface Sign extends TileState, Colorable {
+ * @deprecated in favour of {@link #line(int)}
+ */
+ @NotNull
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public String getLine(int index) throws IndexOutOfBoundsException;
+
+ /**
+@@ -76,7 +76,7 @@ public interface Sign extends TileState, Colorable {
+ * @throws IndexOutOfBoundsException If the index is out of the range 0..3
+ * @deprecated in favour of {@link #line(int, net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public void setLine(int index, @NotNull String line) throws IndexOutOfBoundsException;
+
+ /**
+diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
+index 25252bad38ca35b81b225b57c4b6ce77ad6de166..9f289576d97c3406d506d3f4fa7287bc74e5b425 100644
+--- a/src/main/java/org/bukkit/entity/Player.java
++++ b/src/main/java/org/bukkit/entity/Player.java
+@@ -72,7 +72,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+ * @return the friendly name
+ * @deprecated in favour of {@link #displayName()}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ @NotNull
+ public String getDisplayName();
+
+@@ -86,7 +86,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+ * @param name The new display name.
+ * @deprecated in favour of {@link #displayName(net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public void setDisplayName(@Nullable String name);
+
+ // Paper start
+@@ -127,7 +127,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+ * @deprecated in favour of {@link #playerListName()}
+ */
+ @NotNull
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public String getPlayerListName();
+
+ /**
+@@ -138,7 +138,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+ * @param name new player list name
+ * @deprecated in favour of {@link #playerListName(net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public void setPlayerListName(@Nullable String name);
+
+ /**
+@@ -147,7 +147,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+ * @return player list header or null
+ * @deprecated in favour of {@link #playerListHeader()}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ @Nullable
+ public String getPlayerListHeader();
+
+@@ -157,7 +157,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+ * @return player list header or null
+ * @deprecated in favour of {@link #playerListFooter()}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ @Nullable
+ public String getPlayerListFooter();
+
+@@ -167,7 +167,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+ * @param header player list header, null for empty
+ * @deprecated in favour of {@link #sendPlayerListHeader(net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public void setPlayerListHeader(@Nullable String header);
+
+ /**
+@@ -176,7 +176,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+ * @param footer player list footer, null for empty
+ * @deprecated in favour of {@link #sendPlayerListFooter(net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public void setPlayerListFooter(@Nullable String footer);
+
+ /**
+@@ -187,7 +187,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+ * @param footer player list footer, null for empty
+ * @deprecated in favour of {@link #sendPlayerListHeaderAndFooter(net.kyori.adventure.text.Component, net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public void setPlayerListHeaderFooter(@Nullable String header, @Nullable String footer);
+
+ /**
+@@ -227,7 +227,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+ * @param message kick message
+ * @deprecated in favour of {@link #kick(net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public void kickPlayer(@Nullable String message);
+
+ // Paper start
+@@ -598,7 +598,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+ * @throws IllegalArgumentException if lines is non-null and has a length less than 4
+ * @deprecated in favour of {@link #sendSignChange(org.bukkit.Location, java.util.List)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public void sendSignChange(@NotNull Location loc, @Nullable String[] lines) throws IllegalArgumentException;
+
+
+@@ -620,7 +620,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+ * @throws IllegalArgumentException if lines is non-null and has a length less than 4
+ * @deprecated in favour of {@link #sendSignChange(org.bukkit.Location, java.util.List, org.bukkit.DyeColor)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public void sendSignChange(@NotNull Location loc, @Nullable String[] lines, @NotNull DyeColor dyeColor) throws IllegalArgumentException;
+
+ /**
+@@ -1769,7 +1769,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+ * @deprecated in favour of {@link #locale()}
+ */
+ @NotNull
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public String getLocale();
+
+ // Paper start
+diff --git a/src/main/java/org/bukkit/entity/minecart/CommandMinecart.java b/src/main/java/org/bukkit/entity/minecart/CommandMinecart.java
+index 078228106b299a8e38495f7f881d38de4f87bc95..bd5b3142a88c31a676c08fa3e8175f81f4259835 100644
+--- a/src/main/java/org/bukkit/entity/minecart/CommandMinecart.java
++++ b/src/main/java/org/bukkit/entity/minecart/CommandMinecart.java
+@@ -37,7 +37,7 @@ public interface CommandMinecart extends Minecart {
+ * @param name New name for this CommandMinecart.
+ * @deprecated in favour of {@link #customName(net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public void setName(@Nullable String name);
+
+ }
+diff --git a/src/main/java/org/bukkit/event/block/SignChangeEvent.java b/src/main/java/org/bukkit/event/block/SignChangeEvent.java
+index 1f79f704abf339150df08900b8ea7da4cefef258..004106913655446774f875015cb79863d21263c8 100644
+--- a/src/main/java/org/bukkit/event/block/SignChangeEvent.java
++++ b/src/main/java/org/bukkit/event/block/SignChangeEvent.java
+@@ -90,7 +90,7 @@ public class SignChangeEvent extends BlockEvent implements Cancellable {
+ * @deprecated in favour of {@link #lines()}
+ */
+ @NotNull
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public String[] getLines() {
+ return adventure$lines.stream().map(org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer()::serialize).toArray(String[]::new); // Paper
+ }
+@@ -106,7 +106,7 @@ public class SignChangeEvent extends BlockEvent implements Cancellable {
+ * @deprecated in favour of {@link #line(int)}
+ */
+ @Nullable
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public String getLine(int index) throws IndexOutOfBoundsException {
+ return org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().serialize(this.adventure$lines.get(index)); // Paper
+ }
+@@ -120,7 +120,7 @@ public class SignChangeEvent extends BlockEvent implements Cancellable {
+ * or < 0}
+ * @deprecated in favour of {@link #line(int, net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public void setLine(int index, @Nullable String line) throws IndexOutOfBoundsException {
+ adventure$lines.set(index, line != null ? org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().deserialize(line) : null); // Paper
+ }
+diff --git a/src/main/java/org/bukkit/event/entity/PlayerDeathEvent.java b/src/main/java/org/bukkit/event/entity/PlayerDeathEvent.java
+index 3d45d2e41aad6992b40a22030f2a63baeec78757..3cecfe9f7c253ab474829c612cca2dc05fd5d111 100644
+--- a/src/main/java/org/bukkit/event/entity/PlayerDeathEvent.java
++++ b/src/main/java/org/bukkit/event/entity/PlayerDeathEvent.java
+@@ -151,7 +151,7 @@ public class PlayerDeathEvent extends EntityDeathEvent {
+ * @param deathMessage Message to appear to other players on the server.
+ * @deprecated in favour of {@link #deathMessage(net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public void setDeathMessage(@Nullable String deathMessage) {
+ this.deathMessage = deathMessage;
+ this.adventure$deathMessage = deathMessage != null ? org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().deserialize(deathMessage) : net.kyori.adventure.text.Component.empty(); // Paper
+@@ -164,7 +164,7 @@ public class PlayerDeathEvent extends EntityDeathEvent {
+ * @deprecated in favour of {@link #deathMessage()}
+ */
+ @Nullable
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public String getDeathMessage() {
+ return this.deathMessage != null ? this.deathMessage : (this.adventure$deathMessage != null ? getDeathMessageString(this.adventure$deathMessage) : null); // Paper
+ }
+diff --git a/src/main/java/org/bukkit/event/inventory/InventoryType.java b/src/main/java/org/bukkit/event/inventory/InventoryType.java
+index 129eac25da4f27489038fb15ab1aeecb172b60cc..670aca11bbaa2d155cd9d2105ac94c9df71d7d8d 100644
+--- a/src/main/java/org/bukkit/event/inventory/InventoryType.java
++++ b/src/main/java/org/bukkit/event/inventory/InventoryType.java
+@@ -170,7 +170,7 @@ public enum InventoryType {
+ }
+
+ @NotNull
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public String getDefaultTitle() {
+ return title;
+ }
+diff --git a/src/main/java/org/bukkit/event/player/AsyncPlayerPreLoginEvent.java b/src/main/java/org/bukkit/event/player/AsyncPlayerPreLoginEvent.java
+index 77aefda5aac4602bf5bf71c29600e7450defdd4e..240552d61ae12fbec826f771f0f366500e72d941 100644
+--- a/src/main/java/org/bukkit/event/player/AsyncPlayerPreLoginEvent.java
++++ b/src/main/java/org/bukkit/event/player/AsyncPlayerPreLoginEvent.java
+@@ -179,7 +179,7 @@ public class AsyncPlayerPreLoginEvent extends Event {
+ * @deprecated in favour of {@link #kickMessage()}
+ */
+ @NotNull
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public String getKickMessage() {
+ return org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().serialize(this.message); // Paper
+ }
+@@ -190,7 +190,7 @@ public class AsyncPlayerPreLoginEvent extends Event {
+ * @param message New kick message
+ * @deprecated in favour of {@link #kickMessage(net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public void setKickMessage(@NotNull final String message) {
+ this.message = org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().deserialize(message); // Paper
+ }
+@@ -210,7 +210,7 @@ public class AsyncPlayerPreLoginEvent extends Event {
+ * @param message Kick message to display to the user
+ * @deprecated in favour of {@link #disallow(org.bukkit.event.player.AsyncPlayerPreLoginEvent.Result, net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public void disallow(@NotNull final Result result, @NotNull final String message) {
+ this.result = result;
+ this.message = org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().deserialize(message); // Paper
+diff --git a/src/main/java/org/bukkit/event/player/PlayerJoinEvent.java b/src/main/java/org/bukkit/event/player/PlayerJoinEvent.java
+index 851a189d42e271679abc78f95049d8badf7a2b64..7057c2e95267ad32190c5666f20a0566f7fe32fa 100644
+--- a/src/main/java/org/bukkit/event/player/PlayerJoinEvent.java
++++ b/src/main/java/org/bukkit/event/player/PlayerJoinEvent.java
+@@ -17,7 +17,7 @@ public class PlayerJoinEvent extends PlayerEvent {
+ this.joinMessage = joinMessage;
+ }
+
+- @Deprecated // Paper end
++ @Deprecated // Paper end // Purpur - conflict on change
+ public PlayerJoinEvent(@NotNull final Player playerJoined, @Nullable final String joinMessage) {
+ super(playerJoined);
+ this.joinMessage = joinMessage != null ? org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().deserialize(joinMessage) : null; // Paper end
+@@ -50,7 +50,7 @@ public class PlayerJoinEvent extends PlayerEvent {
+ * @deprecated in favour of {@link #joinMessage()}
+ */
+ @Nullable
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public String getJoinMessage() {
+ return this.joinMessage == null ? null : org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().serialize(this.joinMessage); // Paper
+ }
+@@ -61,7 +61,7 @@ public class PlayerJoinEvent extends PlayerEvent {
+ * @param joinMessage join message. If null, no message will be sent
+ * @deprecated in favour of {@link #joinMessage(net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public void setJoinMessage(@Nullable String joinMessage) {
+ this.joinMessage = joinMessage != null ? org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().deserialize(joinMessage) : null; // Paper
+ }
+diff --git a/src/main/java/org/bukkit/event/player/PlayerKickEvent.java b/src/main/java/org/bukkit/event/player/PlayerKickEvent.java
+index 02914c0743852e9e4fd2c085fd4b735e74d8875b..94688093671949551992a8c80904cd6042deb83b 100644
+--- a/src/main/java/org/bukkit/event/player/PlayerKickEvent.java
++++ b/src/main/java/org/bukkit/event/player/PlayerKickEvent.java
+@@ -85,7 +85,7 @@ public class PlayerKickEvent extends PlayerEvent implements Cancellable {
+ * @deprecated in favour of {@link #reason()}
+ */
+ @NotNull
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public String getReason() {
+ return org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().serialize(this.kickReason); // Paper
+ }
+@@ -97,7 +97,7 @@ public class PlayerKickEvent extends PlayerEvent implements Cancellable {
+ * @deprecated in favour of {@link #leaveMessage()}
+ */
+ @NotNull
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public String getLeaveMessage() {
+ return org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().serialize(this.leaveMessage); // Paper
+ }
+@@ -118,7 +118,7 @@ public class PlayerKickEvent extends PlayerEvent implements Cancellable {
+ * @param kickReason kick reason
+ * @deprecated in favour of {@link #reason(net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public void setReason(@NotNull String kickReason) {
+ this.kickReason = org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().deserialize(kickReason); // Paper
+ }
+@@ -129,7 +129,7 @@ public class PlayerKickEvent extends PlayerEvent implements Cancellable {
+ * @param leaveMessage leave message
+ * @deprecated in favour of {@link #leaveMessage(net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public void setLeaveMessage(@NotNull String leaveMessage) {
+ this.leaveMessage = org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().deserialize(leaveMessage); // Paper
+ }
+diff --git a/src/main/java/org/bukkit/event/player/PlayerLocaleChangeEvent.java b/src/main/java/org/bukkit/event/player/PlayerLocaleChangeEvent.java
+index 84521186404b8e43c81a2f9513dce2be40d27840..8c65e9e1476d27fc55419290fb53e46dee9b304d 100644
+--- a/src/main/java/org/bukkit/event/player/PlayerLocaleChangeEvent.java
++++ b/src/main/java/org/bukkit/event/player/PlayerLocaleChangeEvent.java
+@@ -37,7 +37,7 @@ public class PlayerLocaleChangeEvent extends PlayerEvent {
+ * @deprecated in favour of {@link #locale()}
+ */
+ @NotNull
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public String getLocale() {
+ return locale;
+ }
+diff --git a/src/main/java/org/bukkit/event/player/PlayerLoginEvent.java b/src/main/java/org/bukkit/event/player/PlayerLoginEvent.java
+index 75cc54739ef841cd90568d74927d6002d4cfa7e0..712900c9afc3b79806d2b564c0734facf969a6fe 100644
+--- a/src/main/java/org/bukkit/event/player/PlayerLoginEvent.java
++++ b/src/main/java/org/bukkit/event/player/PlayerLoginEvent.java
+@@ -139,7 +139,7 @@ public class PlayerLoginEvent extends PlayerEvent {
+ * @deprecated in favour of {@link #kickMessage()}
+ */
+ @NotNull
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public String getKickMessage() {
+ return org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().serialize(this.message); // Paper
+ }
+@@ -150,7 +150,7 @@ public class PlayerLoginEvent extends PlayerEvent {
+ * @param message New kick message
+ * @deprecated in favour of {@link #kickMessage(net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public void setKickMessage(@NotNull final String message) {
+ this.message = org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().deserialize(message); // Paper
+ }
+@@ -181,7 +181,7 @@ public class PlayerLoginEvent extends PlayerEvent {
+ * @param message Kick message to display to the user
+ * @deprecated in favour of {@link #disallow(Result, net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper start
++ @Deprecated // Paper start // Purpur - conflict on change
+ public void disallow(@NotNull final Result result, @NotNull final String message) {
+ this.result = result;
+ this.message = org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().deserialize(message);
+diff --git a/src/main/java/org/bukkit/event/player/PlayerPreLoginEvent.java b/src/main/java/org/bukkit/event/player/PlayerPreLoginEvent.java
+index 123979ed64939d615b061f91c19c630e1e1db8c7..5b85579964dc6a6150f0c0be650a4bf731414838 100644
+--- a/src/main/java/org/bukkit/event/player/PlayerPreLoginEvent.java
++++ b/src/main/java/org/bukkit/event/player/PlayerPreLoginEvent.java
+@@ -95,7 +95,7 @@ public class PlayerPreLoginEvent extends Event {
+ * @return Current kick message
+ * @deprecated in favour of {@link #kickMessage()}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ @NotNull
+ public String getKickMessage() {
+ return org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().serialize(this.message); // Paper
+@@ -107,7 +107,7 @@ public class PlayerPreLoginEvent extends Event {
+ * @param message New kick message
+ * @deprecated in favour of {@link #kickMessage(net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public void setKickMessage(@NotNull final String message) {
+ this.message = org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().deserialize(message); // Paper
+ }
+@@ -127,7 +127,7 @@ public class PlayerPreLoginEvent extends Event {
+ * @param message Kick message to display to the user
+ * @deprecated in favour of {@link #disallow(org.bukkit.event.player.PlayerPreLoginEvent.Result, net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public void disallow(@NotNull final Result result, @NotNull final String message) {
+ this.result = result;
+ this.message = org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().deserialize(message); // Paper
+diff --git a/src/main/java/org/bukkit/event/player/PlayerQuitEvent.java b/src/main/java/org/bukkit/event/player/PlayerQuitEvent.java
+index e43acfb570036adb73d195136573620378cc6a61..1826d11d8b18702ee12c669b50df7f538c324582 100644
+--- a/src/main/java/org/bukkit/event/player/PlayerQuitEvent.java
++++ b/src/main/java/org/bukkit/event/player/PlayerQuitEvent.java
+@@ -61,7 +61,7 @@ public class PlayerQuitEvent extends PlayerEvent {
+ * @deprecated in favour of {@link #quitMessage()}
+ */
+ @Nullable
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public String getQuitMessage() {
+ return this.quitMessage == null ? null : org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().serialize(this.quitMessage); // Paper
+ }
+@@ -72,7 +72,7 @@ public class PlayerQuitEvent extends PlayerEvent {
+ * @param quitMessage quit message
+ * @deprecated in favour of {@link #quitMessage(net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public void setQuitMessage(@Nullable String quitMessage) {
+ this.quitMessage = quitMessage != null ? org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().deserialize(quitMessage) : null; // Paper
+ }
+diff --git a/src/main/java/org/bukkit/event/server/BroadcastMessageEvent.java b/src/main/java/org/bukkit/event/server/BroadcastMessageEvent.java
+index 4f8c85222c7bd33217c7db0ff5f47bf397f8f3e5..c18d6d979bd22814ebdc52b995d2cc7ed46dd87f 100644
+--- a/src/main/java/org/bukkit/event/server/BroadcastMessageEvent.java
++++ b/src/main/java/org/bukkit/event/server/BroadcastMessageEvent.java
+@@ -73,7 +73,7 @@ public class BroadcastMessageEvent extends ServerEvent implements Cancellable {
+ * @deprecated in favour of {@link #message()}
+ */
+ @NotNull
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public String getMessage() {
+ return org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().serialize(this.message); // Paper
+ }
+@@ -84,7 +84,7 @@ public class BroadcastMessageEvent extends ServerEvent implements Cancellable {
+ * @param message New message to broadcast
+ * @deprecated in favour of {@link #message(net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public void setMessage(@NotNull String message) {
+ this.message = org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().deserialize(message); // Paper
+ }
+diff --git a/src/main/java/org/bukkit/event/server/ServerListPingEvent.java b/src/main/java/org/bukkit/event/server/ServerListPingEvent.java
+index ede5a41bc071a9c9cea369b227b37a50222f295d..6c6501d73041a6c69e78f34d3bf2a96a7de5f690 100644
+--- a/src/main/java/org/bukkit/event/server/ServerListPingEvent.java
++++ b/src/main/java/org/bukkit/event/server/ServerListPingEvent.java
+@@ -109,7 +109,7 @@ public class ServerListPingEvent extends ServerEvent implements Iterable
+ * @deprecated in favour of {@link #motd()}
+ */
+ @NotNull
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public String getMotd() {
+ return org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().serialize(this.motd); // Paper
+ }
+@@ -120,7 +120,7 @@ public class ServerListPingEvent extends ServerEvent implements Iterable
+ * @param motd the message of the day
+ * @deprecated in favour of {@link #motd(net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public void setMotd(@NotNull String motd) {
+ this.motd = org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().deserialize(motd); // Paper
+ }
+diff --git a/src/main/java/org/bukkit/inventory/InventoryView.java b/src/main/java/org/bukkit/inventory/InventoryView.java
+index b06995aa57aa9cba0bb59f1d26d81015619a08e6..5e33fe46ab9bb034acc6a38a3c00c33c8f029ca6 100644
+--- a/src/main/java/org/bukkit/inventory/InventoryView.java
++++ b/src/main/java/org/bukkit/inventory/InventoryView.java
+@@ -464,7 +464,7 @@ public abstract class InventoryView {
+ * @return The title.
+ * @deprecated in favour of {@link #title()}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ @NotNull
+ public abstract String getTitle();
+ }
+diff --git a/src/main/java/org/bukkit/inventory/meta/BookMeta.java b/src/main/java/org/bukkit/inventory/meta/BookMeta.java
+index 248cd6faa72a91ceaf6f74d06c05d05bd26f5fbc..6ec728854333cd473528162b6381e7a5a83532d8 100644
+--- a/src/main/java/org/bukkit/inventory/meta/BookMeta.java
++++ b/src/main/java/org/bukkit/inventory/meta/BookMeta.java
+@@ -244,7 +244,7 @@ public interface BookMeta extends ItemMeta, net.kyori.adventure.inventory.Book {
+ * @deprecated in favour of {@link #page(int)}
+ */
+ @NotNull
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ String getPage(int page);
+
+ /**
+@@ -260,7 +260,7 @@ public interface BookMeta extends ItemMeta, net.kyori.adventure.inventory.Book {
+ * @param data the data to set for that page
+ * @deprecated in favour of {@link #page(int, net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ void setPage(int page, @NotNull String data);
+
+ /**
+@@ -270,7 +270,7 @@ public interface BookMeta extends ItemMeta, net.kyori.adventure.inventory.Book {
+ * @deprecated in favour of {@link #pages()}
+ */
+ @NotNull
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ List getPages();
+
+ /**
+@@ -280,7 +280,7 @@ public interface BookMeta extends ItemMeta, net.kyori.adventure.inventory.Book {
+ * @param pages A list of pages to set the book to use
+ * @deprecated in favour of {@link #pages(List)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ void setPages(@NotNull List pages);
+
+ /**
+@@ -290,7 +290,7 @@ public interface BookMeta extends ItemMeta, net.kyori.adventure.inventory.Book {
+ * @param pages A list of strings, each being a page
+ * @deprecated in favour of {@link #pages(net.kyori.adventure.text.Component...)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ void setPages(@NotNull String... pages);
+
+ /**
+@@ -300,7 +300,7 @@ public interface BookMeta extends ItemMeta, net.kyori.adventure.inventory.Book {
+ * @param pages A list of strings, each being a page
+ * @deprecated in favour of {@link #addPages(net.kyori.adventure.text.Component...)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ void addPage(@NotNull String... pages);
+
+ /**
+diff --git a/src/main/java/org/bukkit/inventory/meta/ItemMeta.java b/src/main/java/org/bukkit/inventory/meta/ItemMeta.java
+index f093f991f1fedd20fcef041b093398250b7fb286..49d8b1bdad79f452c863f83557ffde7e8f4749c6 100644
+--- a/src/main/java/org/bukkit/inventory/meta/ItemMeta.java
++++ b/src/main/java/org/bukkit/inventory/meta/ItemMeta.java
+@@ -59,7 +59,7 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste
+ * @return the display name that is set
+ * @deprecated in favour of {@link #displayName()}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ @NotNull
+ String getDisplayName();
+
+@@ -83,7 +83,7 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste
+ * @param name the name to set
+ * @deprecated in favour of {@link #displayName(net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ void setDisplayName(@Nullable String name);
+
+ // Paper start
+@@ -155,7 +155,7 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste
+ * @return a list of lore that is set
+ * @deprecated in favour of {@link #lore()}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ @Nullable
+ List getLore();
+
+@@ -179,7 +179,7 @@ public interface ItemMeta extends Cloneable, ConfigurationSerializable, Persiste
+ * @param lore the lore that will be set
+ * @deprecated in favour of {@link #lore(List)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ void setLore(@Nullable List lore);
+
+ /**
+diff --git a/src/main/java/org/bukkit/map/MapCursor.java b/src/main/java/org/bukkit/map/MapCursor.java
+index ed0bc2024a0bb85837e25f75ae89d1fe257b2e60..f6e831f844e1fe99a2617bd64c2290d1f2e96d81 100644
+--- a/src/main/java/org/bukkit/map/MapCursor.java
++++ b/src/main/java/org/bukkit/map/MapCursor.java
+@@ -259,7 +259,7 @@ public final class MapCursor {
+ * @deprecated in favour of {@link #caption()}
+ */
+ @Nullable
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public String getCaption() {
+ return this.caption == null ? null : org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().serialize(this.caption); // Paper
+ }
+@@ -270,7 +270,7 @@ public final class MapCursor {
+ * @param caption new caption
+ * @deprecated in favour of {@link #caption(net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ public void setCaption(@Nullable String caption) {
+ this.caption = caption == null ? null : org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().deserialize(caption); // Paper
+ }
+diff --git a/src/main/java/org/bukkit/scoreboard/Objective.java b/src/main/java/org/bukkit/scoreboard/Objective.java
+index 58bddb11fd534e7c33a4ffd7b72b055ba92c767a..a1b6b1123808378d58c855cacac391ce97df6f19 100644
+--- a/src/main/java/org/bukkit/scoreboard/Objective.java
++++ b/src/main/java/org/bukkit/scoreboard/Objective.java
+@@ -47,7 +47,7 @@ public interface Objective {
+ * @deprecated in favour of {@link #displayName()}
+ */
+ @NotNull
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ String getDisplayName() throws IllegalStateException;
+
+ /**
+@@ -60,7 +60,7 @@ public interface Objective {
+ * characters.
+ * @deprecated in favour of {@link #displayName(net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ void setDisplayName(@NotNull String displayName) throws IllegalStateException, IllegalArgumentException;
+
+ /**
+diff --git a/src/main/java/org/bukkit/scoreboard/Scoreboard.java b/src/main/java/org/bukkit/scoreboard/Scoreboard.java
+index f09ff32cc3ffc16af379a378b1948991435393e8..e9db79d10522895e6f119c0cc87eec1cbc45ba6e 100644
+--- a/src/main/java/org/bukkit/scoreboard/Scoreboard.java
++++ b/src/main/java/org/bukkit/scoreboard/Scoreboard.java
+@@ -89,7 +89,7 @@ public interface Scoreboard {
+ * @deprecated in favour of {@link #registerNewObjective(String, String, net.kyori.adventure.text.Component)}
+ */
+ @NotNull
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ Objective registerNewObjective(@NotNull String name, @NotNull String criteria, @NotNull String displayName) throws IllegalArgumentException;
+
+ /**
+@@ -113,7 +113,7 @@ public interface Scoreboard {
+ * @deprecated in favour of {@link #registerNewObjective(String, String, net.kyori.adventure.text.Component, RenderType)}
+ */
+ @NotNull
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ Objective registerNewObjective(@NotNull String name, @NotNull String criteria, @NotNull String displayName, @NotNull RenderType renderType) throws IllegalArgumentException;
+
+ /**
+diff --git a/src/main/java/org/bukkit/scoreboard/Team.java b/src/main/java/org/bukkit/scoreboard/Team.java
+index f0af10a5b9ad048be197ed5ec6c8ed2672eb3dd5..705b2268b1c227b34852c14601381230dc626a08 100644
+--- a/src/main/java/org/bukkit/scoreboard/Team.java
++++ b/src/main/java/org/bukkit/scoreboard/Team.java
+@@ -110,7 +110,7 @@ public interface Team {
+ * @deprecated in favour of {@link #displayName()}
+ */
+ @NotNull
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ String getDisplayName() throws IllegalStateException;
+
+ /**
+@@ -122,7 +122,7 @@ public interface Team {
+ * @throws IllegalStateException if this team has been unregistered
+ * @deprecated in favour of {@link #displayName(net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ void setDisplayName(@NotNull String displayName) throws IllegalStateException, IllegalArgumentException;
+
+ /**
+@@ -133,7 +133,7 @@ public interface Team {
+ * @deprecated in favour of {@link #prefix()}
+ */
+ @NotNull
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ String getPrefix() throws IllegalStateException;
+
+ /**
+@@ -146,7 +146,7 @@ public interface Team {
+ * @throws IllegalStateException if this team has been unregistered
+ * @deprecated in favour of {@link #prefix(net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ void setPrefix(@NotNull String prefix) throws IllegalStateException, IllegalArgumentException;
+
+ /**
+@@ -157,7 +157,7 @@ public interface Team {
+ * @deprecated in favour of {@link #suffix()}
+ */
+ @NotNull
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ String getSuffix() throws IllegalStateException;
+
+ /**
+@@ -170,7 +170,7 @@ public interface Team {
+ * @throws IllegalStateException if this team has been unregistered
+ * @deprecated in favour of {@link #suffix(net.kyori.adventure.text.Component)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ void setSuffix(@NotNull String suffix) throws IllegalStateException, IllegalArgumentException;
+
+ /**
+@@ -184,7 +184,7 @@ public interface Team {
+ * @deprecated in favour of {@link #color()}
+ */
+ @NotNull
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ ChatColor getColor() throws IllegalStateException;
+
+ /**
+@@ -197,7 +197,7 @@ public interface Team {
+ * no color
+ * @deprecated in favour of {@link #color(net.kyori.adventure.text.format.NamedTextColor)}
+ */
+- @Deprecated // Paper
++ @Deprecated // Paper // Purpur - conflict on change
+ void setColor(@NotNull ChatColor color);
+
+ /**
diff --git a/patches/Purpur/patches/api/0038-Add-enchantment-target-for-bows-and-crossbows.patch b/patches/Purpur/patches/api/0038-Add-enchantment-target-for-bows-and-crossbows.patch
new file mode 100644
index 00000000..d30cd004
--- /dev/null
+++ b/patches/Purpur/patches/api/0038-Add-enchantment-target-for-bows-and-crossbows.patch
@@ -0,0 +1,29 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: BillyGalbreath
+Date: Tue, 23 Mar 2021 15:01:03 -0500
+Subject: [PATCH] Add enchantment target for bows and crossbows
+
+
+diff --git a/src/main/java/org/bukkit/enchantments/EnchantmentTarget.java b/src/main/java/org/bukkit/enchantments/EnchantmentTarget.java
+index 635e07a6b0e255c4fdad58ba9d281c807af4e229..93d5fad641c5afa679b59dc712f0d0faaddcfe2e 100644
+--- a/src/main/java/org/bukkit/enchantments/EnchantmentTarget.java
++++ b/src/main/java/org/bukkit/enchantments/EnchantmentTarget.java
+@@ -226,6 +226,18 @@ public enum EnchantmentTarget {
+ public boolean includes(@NotNull Material item) {
+ return BREAKABLE.includes(item) || (WEARABLE.includes(item) && !item.equals(Material.ELYTRA)) || item.equals(Material.COMPASS);
+ }
++ // Purpur start
++ },
++
++ /**
++ * Allow the Enchantment to be placed on bows and crossbows.
++ */
++ BOW_AND_CROSSBOW {
++ @Override
++ public boolean includes(@NotNull Material item) {
++ return item.equals(Material.BOW) || item.equals(Material.CROSSBOW);
++ }
++ // Purpur end
+ };
+
+ /**
diff --git a/patches/Purpur/patches/api/0039-Iron-golem-poppy-calms-anger.patch b/patches/Purpur/patches/api/0039-Iron-golem-poppy-calms-anger.patch
new file mode 100644
index 00000000..0287a30a
--- /dev/null
+++ b/patches/Purpur/patches/api/0039-Iron-golem-poppy-calms-anger.patch
@@ -0,0 +1,17 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: BillyGalbreath
+Date: Thu, 13 May 2021 21:38:01 -0500
+Subject: [PATCH] Iron golem poppy calms anger
+
+
+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 39f77041133228c4bd4cec2427ad0bae8e739d4a..29144c0e325a3efbef05670a6fb2e849bbed6bba 100644
+--- a/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java
++++ b/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java
+@@ -214,5 +214,6 @@ public interface VanillaGoal extends Goal {
+ 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"));
++ GoalKey RECEIVE_FLOWER = GoalKey.of(IronGolem.class, NamespacedKey.minecraft("receive_flower"));
+ // Purpur end
+ }
diff --git a/patches/Purpur/patches/api/0040-API-for-any-mob-to-burn-daylight.patch b/patches/Purpur/patches/api/0040-API-for-any-mob-to-burn-daylight.patch
new file mode 100644
index 00000000..edec35d7
--- /dev/null
+++ b/patches/Purpur/patches/api/0040-API-for-any-mob-to-burn-daylight.patch
@@ -0,0 +1,48 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Ben Kerllenevich
+Date: Tue, 25 May 2021 16:30:30 -0400
+Subject: [PATCH] API for any mob to burn daylight
+
+Co-authored by: Encode42
+
+diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java
+index 7fa5242bd44c9b19648d79fa8fecbb7ee125288e..62d8d7dbd4d602ca8cb00ff0cf1331583b398323 100644
+--- a/src/main/java/org/bukkit/entity/Entity.java
++++ b/src/main/java/org/bukkit/entity/Entity.java
+@@ -770,5 +770,12 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent
+ * @return Whether the entity was successfully spawned.
+ */
+ boolean spawnAt(@NotNull Location location, @NotNull org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason);
++
++ /**
++ * Checks if the entity is in daylight
++ *
++ * @return True if in daylight
++ */
++ boolean isInDaylight();
+ // Purpur end
+ }
+diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java
+index f64cd3a467ccb4f773138542a6beb61ab2e1ae40..41f91ab59e34b2b15432d3941e4ee5658b24f10e 100644
+--- a/src/main/java/org/bukkit/entity/LivingEntity.java
++++ b/src/main/java/org/bukkit/entity/LivingEntity.java
+@@ -895,5 +895,19 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource
+ * @param slot Equipment slot to play break animation for
+ */
+ void broadcastItemBreak(@NotNull org.bukkit.inventory.EquipmentSlot slot);
++
++ /**
++ * If this mob will burn in the sunlight
++ *
++ * @return True if mob will burn in sunlight
++ */
++ boolean shouldBurnInDay();
++
++ /**
++ * Set if this mob should burn in the sunlight
++ *
++ * @param shouldBurnInDay True to burn in sunlight
++ */
++ void setShouldBurnInDay(boolean shouldBurnInDay);
+ // Purpur end
+ }
diff --git a/patches/Purpur/patches/api/0041-Flying-Fall-Damage-API.patch b/patches/Purpur/patches/api/0041-Flying-Fall-Damage-API.patch
new file mode 100644
index 00000000..890534c3
--- /dev/null
+++ b/patches/Purpur/patches/api/0041-Flying-Fall-Damage-API.patch
@@ -0,0 +1,30 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: TreyRuffy
+Date: Wed, 9 Jun 2021 16:31:15 -0600
+Subject: [PATCH] Flying Fall Damage API
+
+
+diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
+index 9f289576d97c3406d506d3f4fa7287bc74e5b425..df1da8a7dec072ddc33a884973bcad67152576fe 100644
+--- a/src/main/java/org/bukkit/entity/Player.java
++++ b/src/main/java/org/bukkit/entity/Player.java
+@@ -2185,5 +2185,19 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+ * @param invulnerableTicks Invulnerable ticks remaining
+ */
+ void setSpawnInvulnerableTicks(int invulnerableTicks);
++
++ /**
++ * Allows you to enable fall damage while {@link #getAllowFlight()} is {@code true}
++ *
++ * @param flyingFallDamage Enables fall damage when {@link #getAllowFlight()} is true
++ */
++ public void setFlyingFallDamage(boolean flyingFallDamage);
++
++ /**
++ * Allows you get if fall damage is enabled while {@link #getAllowFlight()} is {@code true}
++ *
++ * @return True if fall damage is enabled when {@link #getAllowFlight()} is true
++ */
++ public boolean hasFlyingFallDamage();
+ // Purpur end
+ }
diff --git a/patches/Purpur/patches/server/0001-Purpur-config-files.patch b/patches/Purpur/patches/server/0001-Purpur-config-files.patch
new file mode 100644
index 00000000..f3650811
--- /dev/null
+++ b/patches/Purpur/patches/server/0001-Purpur-config-files.patch
@@ -0,0 +1,424 @@
+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 7acf077bc131af718c7548cc29deef558c04e463..10126cb1c3efa2e6c84f20c0da701a131a5dd4cb 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/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+index 3713a110a64fa686e785b9789c33dd09cacc2f48..8db809fdaf1e18a54a21812ff5688095f6ffd6f4 100644
+--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+@@ -212,6 +212,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
+ io.papermc.paper.brigadier.PaperBrigadierProviderImpl.INSTANCE.getClass(); // init PaperBrigadierProvider
+ // Paper end
+diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java
+index cb8064df7e9f1b8b4d4292486e2193680d83663c..22d91a970c7376aa6349bb6cd9cb174e89bc8a09 100644
+--- a/src/main/java/net/minecraft/world/level/World.java
++++ b/src/main/java/net/minecraft/world/level/World.java
+@@ -155,6 +155,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
+@@ -254,6 +255,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
+ this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(((net.minecraft.world.level.storage.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(((net.minecraft.world.level.storage.WorldDataServer)worlddatamutable).getName()); // Tuinity - Server Config
++ this.purpurConfig = new net.pl3x.purpur.PurpurWorldConfig((((net.minecraft.world.level.storage.WorldDataServer)worlddatamutable).getName())); // Purpur
+ 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..74fc4fc2216cf82e1546ef3d567f2750b1240df1
+--- /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", 13);
++ set("config-version", 13);
++
++ 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..536955124afaec5c8a070249c7432cb99bf43d67
+--- /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.level.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 507c5255542ba1b958470b4db2c35b1b0b779f17..04f81eafed1af281222e961f9fa8a4b2736cc2e3 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+@@ -881,6 +881,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);
+@@ -916,6 +917,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
+@@ -934,6 +936,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");
+
+@@ -2384,6 +2387,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 fbeca329f62325aa479f968e7f0f04cde341a009..424d55f51a36d9033db3dfbf2f7a86749b0e3b91 100644
+--- a/src/main/java/org/bukkit/craftbukkit/Main.java
++++ b/src/main/java/org/bukkit/craftbukkit/Main.java
+@@ -154,6 +154,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/0002-Timings-stuff.patch b/patches/Purpur/patches/server/0002-Timings-stuff.patch
new file mode 100644
index 00000000..7919aa80
--- /dev/null
+++ b/patches/Purpur/patches/server/0002-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 35810f42d7a0cd50a4cbe90e8d698fe57914c889..5e672a0660d0aceffcdb26d185590ca18aa4f023 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 74fc4fc2216cf82e1546ef3d567f2750b1240df1..108be36fc37c04eece6ccb93d19e58a31326ceb0 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/0003-Barrels-and-enderchests-6-rows.patch b/patches/Purpur/patches/server/0003-Barrels-and-enderchests-6-rows.patch
new file mode 100644
index 00000000..aceec8b1
--- /dev/null
+++ b/patches/Purpur/patches/server/0003-Barrels-and-enderchests-6-rows.patch
@@ -0,0 +1,189 @@
+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/world/inventory/InventoryEnderChest.java b/src/main/java/net/minecraft/world/inventory/InventoryEnderChest.java
+index 85b9eba1dba3de69ab65b0e1c5ebb8740ce6e9e5..97f6ba97a4b2a35c0b8a003e1e27ad38831d859d 100644
+--- a/src/main/java/net/minecraft/world/inventory/InventoryEnderChest.java
++++ b/src/main/java/net/minecraft/world/inventory/InventoryEnderChest.java
+@@ -28,11 +28,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/world/level/block/BlockEnderChest.java b/src/main/java/net/minecraft/world/level/block/BlockEnderChest.java
+index 70d10c492b6ba893d56a463c0e71ac6aa8707f81..34ea9d2aeb9d606d487be796283c9d5ed614a6af 100644
+--- a/src/main/java/net/minecraft/world/level/block/BlockEnderChest.java
++++ b/src/main/java/net/minecraft/world/level/block/BlockEnderChest.java
+@@ -11,6 +11,7 @@ import net.minecraft.world.TileInventory;
+ import net.minecraft.world.entity.monster.piglin.PiglinAI;
+ import net.minecraft.world.entity.player.EntityHuman;
+ import net.minecraft.world.inventory.ContainerChest;
++import net.minecraft.world.inventory.Containers;
+ import net.minecraft.world.inventory.InventoryEnderChest;
+ import net.minecraft.world.item.context.BlockActionContext;
+ import net.minecraft.world.level.GeneratorAccess;
+@@ -81,6 +82,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/world/level/block/entity/TileEntityBarrel.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntityBarrel.java
+index 7a6f150490bc3ef8a5ed43c401fd70bcc67f40f0..449d2c38abdd35b782a6732006eebb381815bcba 100644
+--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntityBarrel.java
++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntityBarrel.java
+@@ -14,6 +14,7 @@ import net.minecraft.world.entity.player.EntityHuman;
+ import net.minecraft.world.entity.player.PlayerInventory;
+ import net.minecraft.world.inventory.Container;
+ import net.minecraft.world.inventory.ContainerChest;
++import net.minecraft.world.inventory.Containers;
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.level.block.BlockBarrel;
+ import net.minecraft.world.level.block.Blocks;
+@@ -68,7 +69,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() {
+@@ -97,7 +98,7 @@ public class TileEntityBarrel extends TileEntityLootable {
+
+ @Override
+ public int getSize() {
+- return 27;
++ return net.pl3x.purpur.PurpurConfig.barrelSixRows ? 54 : 27; // Purpur
+ }
+
+ @Override
+@@ -117,6 +118,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 108be36fc37c04eece6ccb93d19e58a31326ceb0..2f329bae9f09d0ed21a4538fba6b95919ec35887 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 614ab2d73db2293116f2272f6cd5c16da446132d..2885dc250f171917393c0356a005b476b23f9c5f 100644
+--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java
++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java
+@@ -212,8 +212,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 f6001047ada8308cfa1d9b26677a7a5d7774de51..922a15097bdfe64be657fdf157145d1e882b6a40 100644
+--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java
++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java
+@@ -82,7 +82,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/0004-Advancement-API.patch b/patches/Purpur/patches/server/0004-Advancement-API.patch
new file mode 100644
index 00000000..40477e4e
--- /dev/null
+++ b/patches/Purpur/patches/server/0004-Advancement-API.patch
@@ -0,0 +1,180 @@
+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/advancements/Advancement.java b/src/main/java/net/minecraft/advancements/Advancement.java
+index 2f3d9e5b849093027d3c2ef434494cd600f52a31..23567430901284ab9d4d4873e53a1c8a06da6862 100644
+--- a/src/main/java/net/minecraft/advancements/Advancement.java
++++ b/src/main/java/net/minecraft/advancements/Advancement.java
+@@ -78,7 +78,7 @@ public class Advancement {
+
+ public final @Nullable AdvancementDisplay getAdvancementDisplay() { return this.c(); } // Paper - OBFHELPER
+ @Nullable
+- public AdvancementDisplay c() {
++ public AdvancementDisplay c() { return getDisplay(); } public AdvancementDisplay getDisplay() { // Purpur
+ return this.display;
+ }
+
+diff --git a/src/main/java/net/minecraft/advancements/AdvancementDisplay.java b/src/main/java/net/minecraft/advancements/AdvancementDisplay.java
+index adc6779e53e7b2ee04a80e2ea714e3378b8e6f39..3335c96ec15eb8d8f0b67f51846038f728f6f9fc 100644
+--- a/src/main/java/net/minecraft/advancements/AdvancementDisplay.java
++++ b/src/main/java/net/minecraft/advancements/AdvancementDisplay.java
+@@ -25,10 +25,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;
+@@ -46,10 +47,12 @@ 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;
+ }
+@@ -59,11 +62,14 @@ public class AdvancementDisplay {
+ return this.e;
+ }
+
++ public final void setShouldAnnounceToChat(boolean announce) { this.g = announce; } // Purpur - OBFHELPER
+ public final boolean shouldAnnounceToChat() { return this.i(); } // Paper - 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/advancements/AdvancementFrameType.java b/src/main/java/net/minecraft/advancements/AdvancementFrameType.java
+index 32380346555e194227423999a79f1ebcbbe38d3b..173266c81be66f85db06dd28d9c9d720e21f8bc4 100644
+--- a/src/main/java/net/minecraft/advancements/AdvancementFrameType.java
++++ b/src/main/java/net/minecraft/advancements/AdvancementFrameType.java
+@@ -4,16 +4,27 @@ import net.minecraft.EnumChatFormat;
+ import net.minecraft.network.chat.ChatMessage;
+ import net.minecraft.network.chat.IChatBaseComponent;
+
++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/advancements/CriterionTrigger.java b/src/main/java/net/minecraft/advancements/CriterionTrigger.java
+index f2d74473caf96ca6e871311ef87afa128cd4d0bf..851e69a2f5155d9fa2e5652abdea9aee59e4b20a 100644
+--- a/src/main/java/net/minecraft/advancements/CriterionTrigger.java
++++ b/src/main/java/net/minecraft/advancements/CriterionTrigger.java
+@@ -29,6 +29,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 77abcd6de43302985cdbb2085abece4f621068d4..c859fc16c263e0c50cb01fc722b6f6723d682481 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..0b4ff544e04ec314e78a7a48b5bf90ee699b2ad6
+--- /dev/null
++++ b/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancementDisplay.java
+@@ -0,0 +1,47 @@
++package org.bukkit.craftbukkit.advancement;
++
++import net.minecraft.advancements.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/0005-Llama-API.patch b/patches/Purpur/patches/server/0005-Llama-API.patch
new file mode 100644
index 00000000..dd9d6492
--- /dev/null
+++ b/patches/Purpur/patches/server/0005-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/world/entity/ai/goal/PathfinderGoalLlamaFollow.java b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalLlamaFollow.java
+index 4fd1744f13b87c79ae3f46b28a56daeaba343aa6..34a854131dd939693a6df4d52103714ebe373dc3 100644
+--- a/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalLlamaFollow.java
++++ b/src/main/java/net/minecraft/world/entity/ai/goal/PathfinderGoalLlamaFollow.java
+@@ -11,7 +11,7 @@ import net.minecraft.world.phys.Vec3D;
+
+ 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;
+
+@@ -23,6 +23,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();
+@@ -82,6 +83,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/net/minecraft/world/entity/animal/horse/EntityLlama.java b/src/main/java/net/minecraft/world/entity/animal/horse/EntityLlama.java
+index 2005cb484ba6b5929ad81d3d120521f247f3d4cf..1c6435bf2cd870b795f87368057d8dfc1e1c938a 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/horse/EntityLlama.java
++++ b/src/main/java/net/minecraft/world/entity/animal/horse/EntityLlama.java
+@@ -63,7 +63,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);
+@@ -92,6 +93,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
+@@ -103,6 +105,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();
+ }
+
+@@ -437,19 +444,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;
+ }
+@@ -460,7 +472,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/org/bukkit/craftbukkit/entity/CraftLlama.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java
+index 818034c62893a71808e3af0aa33393605611acdd..71536b6ae6a423e33667efcf584a0020f36fb189 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java
+@@ -66,4 +66,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/0006-AFK-API.patch b/patches/Purpur/patches/server/0006-AFK-API.patch
new file mode 100644
index 00000000..ee37cbcd
--- /dev/null
+++ b/patches/Purpur/patches/server/0006-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/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java
+index c813077455a463dd558076d1d7474829f76b905a..b302d1f7c86a8df2ede46871397189f6f2e6b912 100644
+--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java
++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java
+@@ -2103,8 +2103,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/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java
+index 6fc215df5ed3aa6ef0c23a57e8444602ff9309e8..a97b8d8116920237964cc3a887525f8c3804c640 100644
+--- a/src/main/java/net/minecraft/server/level/WorldServer.java
++++ b/src/main/java/net/minecraft/server/level/WorldServer.java
+@@ -1002,7 +1002,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;
+@@ -1345,7 +1345,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/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java
+index 8513795943497ca80232e7d47066a6944c2f45ec..de72ebb94052efe8c63bf28f6741a4645b3ee721 100644
+--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java
++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java
+@@ -399,6 +399,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"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause
+ }
+@@ -682,6 +688,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();
+@@ -1429,7 +1437,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);
+@@ -1479,6 +1487,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/world/entity/IEntitySelector.java b/src/main/java/net/minecraft/world/entity/IEntitySelector.java
+index f5e32faeb6d937cf90b1f3ea251b5cfc91f2338d..f9908fb7cc27a8947030c2100dccf1dc1a4e24f7 100644
+--- a/src/main/java/net/minecraft/world/entity/IEntitySelector.java
++++ b/src/main/java/net/minecraft/world/entity/IEntitySelector.java
+@@ -15,6 +15,7 @@ import net.minecraft.world.scores.ScoreboardTeamBase;
+ 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();
+@@ -35,6 +36,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/world/entity/player/EntityHuman.java b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java
+index 5bce47fa8f191bc1d33c04c9865cb0efd492a9a2..afa14eddb6a21d4747689af6d70551f10d304be7 100644
+--- a/src/main/java/net/minecraft/world/entity/player/EntityHuman.java
++++ b/src/main/java/net/minecraft/world/entity/player/EntityHuman.java
+@@ -181,6 +181,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/world/level/IEntityAccess.java b/src/main/java/net/minecraft/world/level/IEntityAccess.java
+index a7f2304acf8ee0a15d6eae8c42060e003be13ae7..fd56b2f15e570f266a79c25823a3b3530a693510 100644
+--- a/src/main/java/net/minecraft/world/level/IEntityAccess.java
++++ b/src/main/java/net/minecraft/world/level/IEntityAccess.java
+@@ -183,28 +183,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/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java
+index 2f329bae9f09d0ed21a4538fba6b95919ec35887..95b55fb93049c6686e13aab78ba1ae2b2fd5785b 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.locale.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 ed1bb89ae7b85bf4017315d6189d6cbf595aefe5..f3fb405c92a35796baa30cafcd96df2d8bf162e6 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+@@ -2499,4 +2499,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 5c2eaca0bc63c7880ee928aba6a24761737aa649..2bbbd4a7ae87c2ead3dc2fd5520adfaefe2776b8 100644
+--- a/src/main/java/org/spigotmc/ActivationRange.java
++++ b/src/main/java/org/spigotmc/ActivationRange.java
+@@ -204,6 +204,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/0007-Bring-back-server-name.patch b/patches/Purpur/patches/server/0007-Bring-back-server-name.patch
new file mode 100644
index 00000000..8327ce0d
--- /dev/null
+++ b/patches/Purpur/patches/server/0007-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/dedicated/DedicatedServerProperties.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java
+index 1fab9b9c7d41a0d2a551096c2c15f741a887fa2d..f33309f4c1ad92960d0634f3f5b8105c284f26a2 100644
+--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java
++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java
+@@ -20,6 +20,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 6f90a4182e008b49a4b3328e569311382e4bec0d..2f3d6acf497b6fa6b497a1b94ba96977ce2e6008 100644
+--- a/src/main/java/net/minecraft/server/MinecraftServer.java
++++ b/src/main/java/net/minecraft/server/MinecraftServer.java
+@@ -1650,7 +1650,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant // Spigot - Spigot > // CraftBukkit - cb > vanilla!
++ return net.pl3x.purpur.PurpurConfig.serverModName; // Purpur // Tuinity // Paper // Spigot // CraftBukkit
+ }
+
+ public CrashReport b(CrashReport crashreport) {
+diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java
+index 95b55fb93049c6686e13aab78ba1ae2b2fd5785b..2442309843bb62e08ae13c46d335c65f7d072510 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java
+@@ -139,6 +139,11 @@ public class PurpurConfig {
+ afkTabListPrefix = getString("settings.messages.afk-tab-list-prefix", afkTabListPrefix);
+ }
+
++ public static String serverModName = "Purpur";
++ private static void serverModName() {
++ serverModName = getString("settings.server-mod-name", serverModName);
++ }
++
+ public static String timingsUrl = "https://timings.pl3x.net";
+ private static void timingsSettings() {
+ timingsUrl = getString("settings.timings.url", timingsUrl);
diff --git a/patches/Purpur/patches/server/0009-LivingEntity-safeFallDistance.patch b/patches/Purpur/patches/server/0009-LivingEntity-safeFallDistance.patch
new file mode 100644
index 00000000..8b82ecae
--- /dev/null
+++ b/patches/Purpur/patches/server/0009-LivingEntity-safeFallDistance.patch
@@ -0,0 +1,84 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sun, 5 May 2019 12:58:45 -0500
+Subject: [PATCH] LivingEntity safeFallDistance
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java
+index 2ff3297fb8c0e4f8c969ba2727eecb7fe06525c4..7d0bb706fb106709432c3fae8758d2cea1d14db8 100644
+--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java
++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java
+@@ -227,6 +227,7 @@ public abstract class EntityLiving extends Entity {
+ // CraftBukkit start
+ public int expToDrop;
+ public int maxAirTicks = 300;
++ public float safeFallDistance = 3.0F; // Purpur
+ public boolean forceDrops;
+ public ArrayList drops = new ArrayList();
+ public final org.bukkit.craftbukkit.attribute.CraftAttributeMap craftAttributes;
+@@ -321,8 +322,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);
+@@ -1787,7 +1788,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/net/minecraft/world/entity/animal/horse/EntityHorseAbstract.java b/src/main/java/net/minecraft/world/entity/animal/horse/EntityHorseAbstract.java
+index 1e41c45af6dbcf097d7d6104e63db637f199301a..cb6e2053d1315b65812e7bff8a17988b5b8ab0e4 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/horse/EntityHorseAbstract.java
++++ b/src/main/java/net/minecraft/world/entity/animal/horse/EntityHorseAbstract.java
+@@ -272,7 +272,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/world/entity/monster/EntityGiantZombie.java b/src/main/java/net/minecraft/world/entity/monster/EntityGiantZombie.java
+index 3b004160da9a2aed440a3ccda538d78f91c61e87..5e6a92dcdbca686d5a8cfc4aaff72b70b81b111f 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/EntityGiantZombie.java
++++ b/src/main/java/net/minecraft/world/entity/monster/EntityGiantZombie.java
+@@ -13,6 +13,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/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
+index 592dacf8cc924caac339a8810ba5b0d85448ed90..75af4d5385d4366e562a53716e020ba20ccbea94 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
+@@ -875,4 +875,16 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
+ throw new IllegalArgumentException(entityCategory + " is an unrecognized entity category");
+ }
+ // 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/0010-Lagging-threshold.patch b/patches/Purpur/patches/server/0010-Lagging-threshold.patch
new file mode 100644
index 00000000..934b245c
--- /dev/null
+++ b/patches/Purpur/patches/server/0010-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 2f3d6acf497b6fa6b497a1b94ba96977ce2e6008..991ac5baf3f10631edb0e60d17c714af1f406a29 100644
+--- a/src/main/java/net/minecraft/server/MinecraftServer.java
++++ b/src/main/java/net/minecraft/server/MinecraftServer.java
+@@ -280,6 +280,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant
+Date: Fri, 5 Jul 2019 16:36:55 -0500
+Subject: [PATCH] ItemFactory#getMonsterEgg
+
+
+diff --git a/src/main/java/net/minecraft/world/item/ItemMonsterEgg.java b/src/main/java/net/minecraft/world/item/ItemMonsterEgg.java
+index 4d965e504a40eb52777575df839856c825a0900a..addddb64956c63563fc072b35cc511d31c9afd45 100644
+--- a/src/main/java/net/minecraft/world/item/ItemMonsterEgg.java
++++ b/src/main/java/net/minecraft/world/item/ItemMonsterEgg.java
+@@ -35,7 +35,7 @@ import net.minecraft.world.phys.Vec3D;
+
+ public class ItemMonsterEgg extends Item {
+
+- private static final Map, ItemMonsterEgg> a = Maps.newIdentityHashMap();
++ public static final Map, ItemMonsterEgg> a = Maps.newIdentityHashMap(); // Purpur - private -> public
+ private final int b;
+ private final int c;
+ private final EntityTypes> d;
+diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java
+index 347c23d4b7d47198f214c3f95354e8abb660b191..4ec0e93d93936080d876ffa017ebe181d2896b22 100644
+--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java
++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java
+@@ -401,4 +401,18 @@ public final class CraftItemFactory implements ItemFactory {
+ new net.md_5.bungee.api.chat.TextComponent(customName));
+ }
+ // Paper end
++
++ // Purpur start
++ @Override
++ public ItemStack getMonsterEgg(org.bukkit.entity.EntityType type) {
++ if (type == null) {
++ return null;
++ }
++ String name = type.getKey().toString();
++ net.minecraft.resources.MinecraftKey key = new net.minecraft.resources.MinecraftKey(name);
++ net.minecraft.world.entity.EntityTypes types = net.minecraft.world.entity.EntityTypes.getFromKey(key);
++ net.minecraft.world.item.ItemMonsterEgg egg = net.minecraft.world.item.ItemMonsterEgg.a.get(types);
++ return new net.minecraft.world.item.ItemStack(egg).asBukkitMirror();
++ }
++ // Purpur end
+ }
diff --git a/patches/Purpur/patches/server/0012-PlayerSetSpawnerTypeWithEggEvent.patch b/patches/Purpur/patches/server/0012-PlayerSetSpawnerTypeWithEggEvent.patch
new file mode 100644
index 00000000..c40126d0
--- /dev/null
+++ b/patches/Purpur/patches/server/0012-PlayerSetSpawnerTypeWithEggEvent.patch
@@ -0,0 +1,86 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Fri, 5 Jul 2019 18:21:00 -0500
+Subject: [PATCH] PlayerSetSpawnerTypeWithEggEvent
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/EntityTypes.java b/src/main/java/net/minecraft/world/entity/EntityTypes.java
+index 80c229c1852199fda85c03453d64cae33e413e89..6335e9046a6288f7bcf945ad8c381e080744653d 100644
+--- a/src/main/java/net/minecraft/world/entity/EntityTypes.java
++++ b/src/main/java/net/minecraft/world/entity/EntityTypes.java
+@@ -274,6 +274,16 @@ public class EntityTypes {
+ return (EntityTypes) IRegistry.a((IRegistry) IRegistry.ENTITY_TYPE, s, (Object) entitytypes_builder.a(s));
+ }
+
++ // Purpur start
++ public static EntityTypes getFromBukkitType(org.bukkit.entity.EntityType bukkitType) {
++ return getFromKey(new MinecraftKey(bukkitType.getKey().toString()));
++ }
++
++ public static EntityTypes getFromKey(MinecraftKey key) {
++ return IRegistry.ENTITY_TYPE.get(key);
++ }
++ // Purpur end
++
+ public static MinecraftKey getName(EntityTypes> entitytypes) {
+ return IRegistry.ENTITY_TYPE.getKey(entitytypes);
+ }
+@@ -439,6 +449,16 @@ public class EntityTypes {
+ return this.bg;
+ }
+
++ // Purpur start
++ public String getName() {
++ return IRegistry.ENTITY_TYPE.getKey(this).getKey();
++ }
++
++ public String getTranslatedName() {
++ return getNameComponent().getString();
++ }
++ // Purpur end
++
+ public String getDescriptionId() { return f(); } // Paper - OBFHELPER
+ public String f() {
+ if (this.bo == null) {
+@@ -448,6 +468,7 @@ public class EntityTypes {
+ return this.bo;
+ }
+
++ public IChatBaseComponent getNameComponent() { return g(); } // Purpur - OBFHELPER
+ public IChatBaseComponent g() {
+ if (this.bp == null) {
+ this.bp = new ChatMessage(this.f());
+diff --git a/src/main/java/net/minecraft/world/item/ItemMonsterEgg.java b/src/main/java/net/minecraft/world/item/ItemMonsterEgg.java
+index addddb64956c63563fc072b35cc511d31c9afd45..5e2d0246146af8bf1de1038f6a1953451b99f0f5 100644
+--- a/src/main/java/net/minecraft/world/item/ItemMonsterEgg.java
++++ b/src/main/java/net/minecraft/world/item/ItemMonsterEgg.java
+@@ -33,6 +33,13 @@ import net.minecraft.world.phys.MovingObjectPosition;
+ import net.minecraft.world.phys.MovingObjectPositionBlock;
+ import net.minecraft.world.phys.Vec3D;
+
++// Purpur start
++import net.pl3x.purpur.event.PlayerSetSpawnerTypeWithEggEvent;
++import org.bukkit.block.CreatureSpawner;
++import org.bukkit.entity.EntityType;
++import org.bukkit.entity.Player;
++// Purpur end
++
+ public class ItemMonsterEgg extends Item {
+
+ public static final Map, ItemMonsterEgg> a = Maps.newIdentityHashMap(); // Purpur - private -> public
+@@ -67,6 +74,15 @@ public class ItemMonsterEgg extends Item {
+ MobSpawnerAbstract mobspawnerabstract = ((TileEntityMobSpawner) tileentity).getSpawner();
+ EntityTypes> entitytypes = this.a(itemstack.getTag());
+
++ // Purpur start
++ org.bukkit.block.Block bukkitBlock = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ PlayerSetSpawnerTypeWithEggEvent event = new PlayerSetSpawnerTypeWithEggEvent((Player) itemactioncontext.getEntity().getBukkitEntity(), bukkitBlock, (CreatureSpawner) bukkitBlock.getState(), EntityType.fromName(entitytypes.getName()));
++ if (!event.callEvent()) {
++ return EnumInteractionResult.FAIL;
++ }
++ entitytypes = EntityTypes.getFromBukkitType(event.getEntityType());
++ // Purpur end
++
+ mobspawnerabstract.setMobName(entitytypes);
+ tileentity.update();
+ world.notify(blockposition, iblockdata, iblockdata, 3);
diff --git a/patches/Purpur/patches/server/0013-EMC-MonsterEggSpawnEvent.patch b/patches/Purpur/patches/server/0013-EMC-MonsterEggSpawnEvent.patch
new file mode 100644
index 00000000..86429a61
--- /dev/null
+++ b/patches/Purpur/patches/server/0013-EMC-MonsterEggSpawnEvent.patch
@@ -0,0 +1,63 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Aikar
+Date: Sat, 20 Jul 2013 22:40:56 -0400
+Subject: [PATCH] EMC - MonsterEggSpawnEvent
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/EntityTypes.java b/src/main/java/net/minecraft/world/entity/EntityTypes.java
+index 6335e9046a6288f7bcf945ad8c381e080744653d..1bfde4cfc0f27705238abf7852ad9bb7997e23e6 100644
+--- a/src/main/java/net/minecraft/world/entity/EntityTypes.java
++++ b/src/main/java/net/minecraft/world/entity/EntityTypes.java
+@@ -318,13 +318,20 @@ public class EntityTypes {
+
+ @Nullable
+ public Entity spawnCreature(WorldServer worldserver, @Nullable ItemStack itemstack, @Nullable EntityHuman entityhuman, BlockPosition blockposition, EnumMobSpawn enummobspawn, boolean flag, boolean flag1) {
+- return this.spawnCreature(worldserver, itemstack == null ? null : itemstack.getTag(), itemstack != null && itemstack.hasName() ? itemstack.getName() : null, entityhuman, blockposition, enummobspawn, flag, flag1);
++ return this.spawnCreature(worldserver, itemstack, itemstack == null ? null : itemstack.getTag(), itemstack != null && itemstack.hasName() ? itemstack.getName() : null, entityhuman, blockposition, enummobspawn, flag, flag1); // Purpur
+ }
+
+ @Nullable
+ public T spawnCreature(WorldServer worldserver, @Nullable NBTTagCompound nbttagcompound, @Nullable IChatBaseComponent ichatbasecomponent, @Nullable EntityHuman entityhuman, BlockPosition blockposition, EnumMobSpawn enummobspawn, boolean flag, boolean flag1) {
++ // Purpur start
++ return spawnCreature(worldserver, null, nbttagcompound, ichatbasecomponent, entityhuman, blockposition, enummobspawn, flag, flag1);
++ }
++
++ @Nullable
++ public T spawnCreature(WorldServer worldserver, @Nullable ItemStack itemstack, @Nullable NBTTagCompound nbttagcompound, @Nullable IChatBaseComponent ichatbasecomponent, @Nullable EntityHuman entityhuman, BlockPosition blockposition, EnumMobSpawn enummobspawn, boolean flag, boolean flag1) {
++ // Purpur end
+ // CraftBukkit start
+- return this.spawnCreature(worldserver, nbttagcompound, ichatbasecomponent, entityhuman, blockposition, enummobspawn, flag, flag1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG);
++ return this.spawnCreature(worldserver, itemstack, nbttagcompound, ichatbasecomponent, entityhuman, blockposition, enummobspawn, flag, flag1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG, null); // Purpur
+ }
+
+ @Nullable
+@@ -349,9 +356,29 @@ public class EntityTypes {
+ }
+ }
+ // Paper end
++ // Purpur start
++ return spawnCreature(worldserver, null, nbttagcompound, ichatbasecomponent, entityhuman, blockposition, enummobspawn, flag, flag1, spawnReason, op);
++ }
++
++ @Nullable
++ public T spawnCreature(WorldServer worldserver, @Nullable ItemStack itemstack, @Nullable NBTTagCompound nbttagcompound, @Nullable IChatBaseComponent ichatbasecomponent, @Nullable EntityHuman entityhuman, BlockPosition blockposition, EnumMobSpawn enummobspawn, boolean flag, boolean flag1, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason spawnReason, @Nullable java.util.function.Consumer op) {
++ // Purpur end
+ T t0 = this.createCreature(worldserver, nbttagcompound, ichatbasecomponent, entityhuman, blockposition, enummobspawn, flag, flag1);
+ if (t0 != null && op != null) op.accept(t0); // Paper
+
++ // Purpur start
++ if (spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.SPAWNER_EGG && itemstack != null && t0 != null) {
++ final net.pl3x.purpur.event.entity.MonsterEggSpawnEvent event = new net.pl3x.purpur.event.entity.MonsterEggSpawnEvent(entityhuman != null ? entityhuman.getBukkitEntity() : null, t0.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack));
++ if (!event.callEvent()) {
++ worldserver.removeEntity(t0);
++ return null;
++ }
++ if (event.getEntity().getEntityId() != t0.getId()) {
++ return (T) ((org.bukkit.craftbukkit.entity.CraftEntity) event.getEntity()).getHandle();
++ }
++ }
++ // Purpur end
++
+ if (t0 != null) {
+ worldserver.addAllEntities(t0, spawnReason);
+ return !t0.dead ? t0 : null; // Don't return an entity when CreatureSpawnEvent is canceled
diff --git a/patches/Purpur/patches/server/0014-Player-invulnerabilities.patch b/patches/Purpur/patches/server/0014-Player-invulnerabilities.patch
new file mode 100644
index 00000000..84f8b042
--- /dev/null
+++ b/patches/Purpur/patches/server/0014-Player-invulnerabilities.patch
@@ -0,0 +1,134 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sat, 2 May 2020 20:55:44 -0500
+Subject: [PATCH] Player invulnerabilities
+
+
+diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java
+index b302d1f7c86a8df2ede46871397189f6f2e6b912..79ff69f9e2dc92ffb4880cf1e059cd1d6a7bdc8a 100644
+--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java
++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java
+@@ -285,6 +285,8 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
+ this.canPickUpLoot = true;
+ this.maxHealthCache = this.getMaxHealth();
+ this.cachedSingleMobDistanceMap = new com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper
++
++ this.invulnerableTicks = world.purpurConfig.playerSpawnInvulnerableTicks; // Purpur
+ }
+ // Paper start
+ public BlockPosition getPointInFront(double inFront) {
+@@ -1129,6 +1131,12 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
+
+ }
+
++ // Purpur start
++ public boolean isSpawnInvulnerable() {
++ return invulnerableTicks > 0 || frozen;
++ }
++ // Purpur end
++
+ @Override
+ public boolean damageEntity(DamageSource damagesource, float f) {
+ if (this.isInvulnerable(damagesource)) {
+@@ -1136,7 +1144,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
+ } else {
+ boolean flag = this.server.j() && this.canPvP() && "fall".equals(damagesource.translationIndex);
+
+- if (!flag && this.invulnerableTicks > 0 && damagesource != DamageSource.OUT_OF_WORLD) {
++ if (!flag && isSpawnInvulnerable() && damagesource != DamageSource.OUT_OF_WORLD) { // Purpur
+ return false;
+ } else {
+ if (damagesource instanceof EntityDamageSource) {
+@@ -1313,6 +1321,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
+ }
+ // Paper end
+
++ this.invulnerableTicks = worldserver.purpurConfig.playerSpawnInvulnerableTicks; // Purpur
+ return this;
+ }
+ }
+@@ -2521,9 +2530,17 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
+
+ @Override
+ public boolean isFrozen() { // Paper - protected > public
+- return super.isFrozen() || (this.playerConnection != null && this.playerConnection.isDisconnected()); // Paper
++ return super.isFrozen() || frozen || (this.playerConnection != null && this.playerConnection.isDisconnected()); // Paper // Purpur
+ }
+
++ // Purpur start
++ private boolean frozen = false;
++
++ public void setFrozen(boolean frozen) {
++ this.frozen = frozen;
++ }
++ // Purpur end
++
+ @Override
+ public Scoreboard getScoreboard() {
+ return getBukkitEntity().getScoreboard().getHandle();
+diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java
+index de72ebb94052efe8c63bf28f6741a4645b3ee721..d1fe1242272a422b7b528876da0d76e1aa6f3eaa 100644
+--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java
++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java
+@@ -1910,6 +1910,7 @@ public class PlayerConnection implements PacketListenerPlayIn {
+ PlayerConnectionUtils.ensureMainThread(packetplayinresourcepackstatus, this, this.player.getWorldServer());
+ // Paper start
+ PlayerResourcePackStatusEvent.Status packStatus = PlayerResourcePackStatusEvent.Status.values()[packetplayinresourcepackstatus.status.ordinal()];
++ if (player.world.purpurConfig.playerInvulnerableWhileAcceptingResourcePack) player.setFrozen(packStatus == PlayerResourcePackStatusEvent.Status.ACCEPTED); // Purpur
+ player.getBukkitEntity().setResourcePackStatus(packStatus);
+ this.server.getPluginManager().callEvent(new PlayerResourcePackStatusEvent(getPlayer(), packStatus));
+ // Paper end
+diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
+index 5936075251ef3d6dda3e93866009d0e996598698..5897e4e02dad470174a31d3710520d04d4b87fb4 100644
+--- a/src/main/java/net/minecraft/server/players/PlayerList.java
++++ b/src/main/java/net/minecraft/server/players/PlayerList.java
+@@ -1015,6 +1015,8 @@ public abstract class PlayerList {
+ }
+ // Paper end
+
++ entityplayer1.invulnerableTicks = entityplayer1.world.purpurConfig.playerSpawnInvulnerableTicks; // Purpur
++
+ // CraftBukkit end
+ return entityplayer1;
+ }
+diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+index 2578a4677d1ee060f687be531e696b7c7be89e84..c441fcea9b2b5a77b801c8a69541cf42050927dc 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+@@ -67,4 +67,11 @@ public class PurpurWorldConfig {
+ idleTimeoutCountAsSleeping = getBoolean("gameplay-mechanics.player.idle-timeout.count-as-sleeping", idleTimeoutCountAsSleeping);
+ idleTimeoutUpdateTabList = getBoolean("gameplay-mechanics.player.idle-timeout.update-tab-list", idleTimeoutUpdateTabList);
+ }
++
++ public int playerSpawnInvulnerableTicks = 60;
++ public boolean playerInvulnerableWhileAcceptingResourcePack = false;
++ private void playerInvulnerabilities() {
++ playerSpawnInvulnerableTicks = getInt("gameplay-mechanics.player.spawn-invulnerable-ticks", playerSpawnInvulnerableTicks);
++ playerInvulnerableWhileAcceptingResourcePack = getBoolean("gameplay-mechanics.player.invulnerable-while-accepting-resource-pack", playerInvulnerableWhileAcceptingResourcePack);
++ }
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+index f3fb405c92a35796baa30cafcd96df2d8bf162e6..b7180dea1d71a68e4025388916600093dfd000c7 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+@@ -2515,5 +2515,20 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
+ public void resetIdleTimer() {
+ getHandle().resetIdleTimer();
+ }
++
++ @Override
++ public boolean isSpawnInvulnerable() {
++ return getHandle().isSpawnInvulnerable();
++ }
++
++ @Override
++ public int getSpawnInvulnerableTicks() {
++ return getHandle().invulnerableTicks;
++ }
++
++ @Override
++ public void setSpawnInvulnerableTicks(int invulnerableTicks) {
++ getHandle().invulnerableTicks = invulnerableTicks;
++ }
+ // Purpur end
+ }
diff --git a/patches/Purpur/patches/server/0015-Anvil-API.patch b/patches/Purpur/patches/server/0015-Anvil-API.patch
new file mode 100644
index 00000000..8465320b
--- /dev/null
+++ b/patches/Purpur/patches/server/0015-Anvil-API.patch
@@ -0,0 +1,147 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sun, 19 Apr 2020 00:17:56 -0500
+Subject: [PATCH] Anvil API
+
+
+diff --git a/src/main/java/net/minecraft/world/inventory/ContainerAnvil.java b/src/main/java/net/minecraft/world/inventory/ContainerAnvil.java
+index ae5674ae9c539720a657838a640050cd3b4dc5b5..1b2d633f3d5d735039f18f27fb1387bd5a74f0d8 100644
+--- a/src/main/java/net/minecraft/world/inventory/ContainerAnvil.java
++++ b/src/main/java/net/minecraft/world/inventory/ContainerAnvil.java
+@@ -2,8 +2,13 @@ package net.minecraft.world.inventory;
+
+ import java.util.Iterator;
+ import java.util.Map;
++
++import net.minecraft.nbt.NBTTagInt;
+ import net.minecraft.network.chat.ChatComponentText;
+ import net.minecraft.network.chat.IChatBaseComponent;
++import net.minecraft.network.protocol.game.PacketPlayOutSetSlot;
++import net.minecraft.network.protocol.game.PacketPlayOutWindowData;
++import net.minecraft.server.level.EntityPlayer;
+ import net.minecraft.tags.Tag;
+ import net.minecraft.tags.TagsBlock;
+ import net.minecraft.world.entity.player.EntityHuman;
+@@ -33,6 +38,8 @@ public class ContainerAnvil extends ContainerAnvilAbstract {
+ public int maximumRepairCost = 40;
+ private CraftInventoryView bukkitEntity;
+ // CraftBukkit end
++ public boolean bypassCost = false; // Purpur
++ public boolean canDoUnsafeEnchants = false; // Purpur
+
+ public ContainerAnvil(int i, PlayerInventory playerinventory) {
+ this(i, playerinventory, ContainerAccess.a);
+@@ -51,12 +58,14 @@ public class ContainerAnvil extends ContainerAnvilAbstract {
+
+ @Override
+ protected boolean b(EntityHuman entityhuman, boolean flag) {
+- return (entityhuman.abilities.canInstantlyBuild || entityhuman.expLevel >= this.levelCost.get()) && this.levelCost.get() > 0;
++ return (entityhuman.abilities.canInstantlyBuild || entityhuman.expLevel >= this.levelCost.get()) && (bypassCost || this.levelCost.get() > 0); // Purpur
+ }
+
+ @Override
+ protected ItemStack a(EntityHuman entityhuman, ItemStack itemstack) {
++ if (net.pl3x.purpur.event.inventory.AnvilTakeResultEvent.getHandlerList().getRegisteredListeners().length > 0) new net.pl3x.purpur.event.inventory.AnvilTakeResultEvent(entityhuman.getBukkitEntity(), getBukkitView(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)).callEvent(); // Purpur
+ if (!entityhuman.abilities.canInstantlyBuild) {
++ if (bypassCost) ((EntityPlayer) entityhuman).lastSentExp = -1; else // Purpur
+ entityhuman.levelDown(-this.levelCost.get());
+ }
+
+@@ -107,6 +116,12 @@ public class ContainerAnvil extends ContainerAnvilAbstract {
+
+ @Override
+ public void e() {
++ // Purpur start
++ bypassCost = false;
++ canDoUnsafeEnchants = false;
++ if (net.pl3x.purpur.event.inventory.AnvilUpdateResultEvent.getHandlerList().getRegisteredListeners().length > 0) new net.pl3x.purpur.event.inventory.AnvilUpdateResultEvent(getBukkitView()).callEvent();
++ // Purpur end
++
+ ItemStack itemstack = this.repairInventory.getItem(0);
+
+ this.levelCost.set(1);
+@@ -183,7 +198,7 @@ public class ContainerAnvil extends ContainerAnvilAbstract {
+ int i2 = (Integer) map1.get(enchantment);
+
+ i2 = l1 == i2 ? i2 + 1 : Math.max(i2, l1);
+- boolean flag3 = enchantment.canEnchant(itemstack);
++ boolean flag3 = canDoUnsafeEnchants || enchantment.canEnchant(itemstack); // Purpur
+
+ if (this.player.abilities.canInstantlyBuild || itemstack.getItem() == Items.ENCHANTED_BOOK) {
+ flag3 = true;
+@@ -195,7 +210,7 @@ public class ContainerAnvil extends ContainerAnvilAbstract {
+ Enchantment enchantment1 = (Enchantment) iterator1.next();
+
+ if (enchantment1 != enchantment && !enchantment.isCompatible(enchantment1)) {
+- flag3 = false;
++ flag3 = canDoUnsafeEnchants; // Purpur
+ ++i;
+ }
+ }
+@@ -266,6 +281,13 @@ public class ContainerAnvil extends ContainerAnvilAbstract {
+ this.levelCost.set(maximumRepairCost - 1); // CraftBukkit
+ }
+
++ // Purpur start
++ if (bypassCost && levelCost.get() >= maximumRepairCost) {
++ itemstack.getOrCreateTagAndSet("Purpur.realCost", NBTTagInt.a(levelCost.get()));
++ levelCost.set(maximumRepairCost - 1);
++ }
++ // Purpur end
++
+ if (this.levelCost.get() >= maximumRepairCost && !this.player.abilities.canInstantlyBuild) { // CraftBukkit
+ itemstack1 = ItemStack.b;
+ }
+@@ -287,6 +309,12 @@ public class ContainerAnvil extends ContainerAnvilAbstract {
+
+ org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(getBukkitView(), itemstack1); // CraftBukkit
+ this.c();
++ // Purpur start
++ if (canDoUnsafeEnchants && itemstack1 != ItemStack.NULL_ITEM) {
++ ((EntityPlayer) player).playerConnection.sendPacket(new PacketPlayOutSetSlot(windowId, 2, itemstack1));
++ ((EntityPlayer) player).playerConnection.sendPacket(new PacketPlayOutWindowData(windowId, 0, levelCost.get()));
++ }
++ // Purpur end
+ }
+ }
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java
+index fd59f77d78a97898657919a77405b39ca24cddc9..151c478c6cb19d88000da46b6fbb952e97e58c95 100644
+--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java
++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java
+@@ -9,7 +9,7 @@ import org.bukkit.inventory.AnvilInventory;
+ public class CraftInventoryAnvil extends CraftResultInventory implements AnvilInventory {
+
+ private final Location location;
+- private final ContainerAnvil container;
++ public final ContainerAnvil container; // Purpur - private -> public
+
+ public CraftInventoryAnvil(Location location, IInventory inventory, IInventory resultInventory, ContainerAnvil container) {
+ super(inventory, resultInventory);
+@@ -47,4 +47,26 @@ public class CraftInventoryAnvil extends CraftResultInventory implements AnvilIn
+ Preconditions.checkArgument(levels >= 0, "Maximum repair cost must be positive (or 0)");
+ container.maximumRepairCost = levels;
+ }
++
++ // Purpur start
++ @Override
++ public boolean canBypassCost() {
++ return container.bypassCost;
++ }
++
++ @Override
++ public void setBypassCost(boolean bypassCost) {
++ container.bypassCost = bypassCost;
++ }
++
++ @Override
++ public boolean canDoUnsafeEnchants() {
++ return container.canDoUnsafeEnchants;
++ }
++
++ @Override
++ public void setDoUnsafeEnchants(boolean canDoUnsafeEnchants) {
++ container.canDoUnsafeEnchants = canDoUnsafeEnchants;
++ }
++ // Purpur end
+ }
diff --git a/patches/Purpur/patches/server/0016-Configurable-villager-brain-ticks.patch b/patches/Purpur/patches/server/0016-Configurable-villager-brain-ticks.patch
new file mode 100644
index 00000000..60d84945
--- /dev/null
+++ b/patches/Purpur/patches/server/0016-Configurable-villager-brain-ticks.patch
@@ -0,0 +1,53 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Tue, 23 Jul 2019 08:28:21 -0500
+Subject: [PATCH] Configurable villager brain ticks
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java b/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java
+index 85374ac8f5460790de03b47d7c3ce19ed5596afe..c45e6ae3f912ac582c6ba693517e05cd8fbf33e2 100644
+--- a/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java
++++ b/src/main/java/net/minecraft/world/entity/npc/EntityVillager.java
+@@ -127,6 +127,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation
+ }, MemoryModuleType.MEETING_POINT, (entityvillager, villageplacetype) -> {
+ return villageplacetype == VillagePlaceType.s;
+ });
++ private final int brainTickOffset; // Purpur
+
+ public EntityVillager(EntityTypes extends EntityVillager> entitytypes, World world) {
+ this(entitytypes, world, VillagerType.PLAINS);
+@@ -139,6 +140,7 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation
+ this.getNavigation().d(true);
+ this.setCanPickupLoot(true);
+ this.setVillagerData(this.getVillagerData().withType(villagertype).withProfession(VillagerProfession.NONE));
++ this.brainTickOffset = getRandom().nextInt(100); // Purpur
+ }
+
+ @Override
+@@ -235,6 +237,10 @@ public class EntityVillager extends EntityVillagerAbstract implements Reputation
+ protected void mobTick() { mobTick(false); }
+ protected void mobTick(boolean inactive) {
+ this.world.getMethodProfiler().enter("villagerBrain");
++ // Purpur start
++ boolean tick = (world.getTime() + brainTickOffset) % world.purpurConfig.villagerBrainTicks == 0;
++ if (((WorldServer) world).getMinecraftServer().lagging ? tick : world.purpurConfig.villagerUseBrainTicksOnlyWhenLagging || tick)
++ // Purpur end
+ if (!inactive) this.getBehaviorController().a((WorldServer) this.world, this); // CraftBukkit - decompile error // Paper
+ this.world.getMethodProfiler().exit();
+ if (this.bF) {
+diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+index c441fcea9b2b5a77b801c8a69541cf42050927dc..c7fb5a737cab0083c39732247acb8f4e87562894 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+@@ -74,4 +74,11 @@ public class PurpurWorldConfig {
+ playerSpawnInvulnerableTicks = getInt("gameplay-mechanics.player.spawn-invulnerable-ticks", playerSpawnInvulnerableTicks);
+ playerInvulnerableWhileAcceptingResourcePack = getBoolean("gameplay-mechanics.player.invulnerable-while-accepting-resource-pack", playerInvulnerableWhileAcceptingResourcePack);
+ }
++
++ public int villagerBrainTicks = 1;
++ public boolean villagerUseBrainTicksOnlyWhenLagging = true;
++ private void villagerSettings() {
++ villagerBrainTicks = getInt("mobs.villager.brain-ticks", villagerBrainTicks);
++ villagerUseBrainTicksOnlyWhenLagging = getBoolean("mobs.villager.use-brain-ticks-only-when-lagging", villagerUseBrainTicksOnlyWhenLagging);
++ }
+ }
diff --git a/patches/Purpur/patches/server/0017-Alternative-Keepalive-Handling.patch b/patches/Purpur/patches/server/0017-Alternative-Keepalive-Handling.patch
new file mode 100644
index 00000000..e6342adf
--- /dev/null
+++ b/patches/Purpur/patches/server/0017-Alternative-Keepalive-Handling.patch
@@ -0,0 +1,85 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Fri, 11 Oct 2019 00:17:39 -0500
+Subject: [PATCH] Alternative Keepalive Handling
+
+
+diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayInKeepAlive.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayInKeepAlive.java
+index b4c37287362907b8507d156b978ba5b9d961bb7b..9e6e6636539702507abb78515e002819661027af 100644
+--- a/src/main/java/net/minecraft/network/protocol/game/PacketPlayInKeepAlive.java
++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayInKeepAlive.java
+@@ -24,6 +24,7 @@ public class PacketPlayInKeepAlive implements Packet {
+ packetdataserializer.writeLong(this.a);
+ }
+
++ public long getId() { return b(); } // Purpur - OBFHELPER
+ public long b() {
+ return this.a;
+ }
+diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java
+index d1fe1242272a422b7b528876da0d76e1aa6f3eaa..8856ee8e0e7a3efda7921c0c8df9a2eb4213b1ce 100644
+--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java
++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java
+@@ -233,6 +233,7 @@ public class PlayerConnection implements PacketListenerPlayIn {
+ private long lastKeepAlive = SystemUtils.getMonotonicMillis(); private void setLastPing(long lastPing) { this.lastKeepAlive = lastPing;}; private long getLastPing() { return this.lastKeepAlive;}; // Paper - OBFHELPER
+ private boolean awaitingKeepAlive; private void setPendingPing(boolean isPending) { this.awaitingKeepAlive = isPending;}; private boolean isPendingPing() { return this.awaitingKeepAlive;}; // Paper - OBFHELPER
+ private long h; private void setKeepAliveID(long keepAliveID) { this.h = keepAliveID;}; private long getKeepAliveID() {return this.h; }; // Paper - OBFHELPER
++ private java.util.List keepAlives = new java.util.ArrayList<>(); // Purpur
+ // CraftBukkit start - multithreaded fields
+ private volatile int chatThrottle;
+ private static final AtomicIntegerFieldUpdater chatSpamField = AtomicIntegerFieldUpdater.newUpdater(PlayerConnection.class, "chatThrottle");
+@@ -367,6 +368,21 @@ public class PlayerConnection implements PacketListenerPlayIn {
+ long currentTime = SystemUtils.getMonotonicMillis();
+ long elapsedTime = currentTime - this.getLastPing();
+
++ // Purpur start
++ if (net.pl3x.purpur.PurpurConfig.useAlternateKeepAlive) {
++ if (elapsedTime >= 1000L) { // 1 second
++ if (!processedDisconnect && keepAlives.size() > KEEPALIVE_LIMIT) {
++ PlayerConnection.LOGGER.warn("{} was kicked due to keepalive timeout!", player.getName());
++ disconnect(new ChatMessage("disconnect.timeout"));
++ } else {
++ setLastPing(currentTime); // hijack this field for 1 second intervals
++ keepAlives.add(currentTime); // currentTime is ID
++ sendPacket(new PacketPlayOutKeepAlive(currentTime));
++ }
++ }
++ } else
++ // Purpur end
++
+ if (this.isPendingPing()) {
+ if (!this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // check keepalive limit, don't fire if already disconnected
+ PlayerConnection.LOGGER.warn("{} was kicked due to keepalive timeout!", this.player.getName()); // more info
+@@ -3093,6 +3109,16 @@ public class PlayerConnection implements PacketListenerPlayIn {
+
+ @Override
+ public void a(PacketPlayInKeepAlive packetplayinkeepalive) {
++ // Purpur start
++ if (net.pl3x.purpur.PurpurConfig.useAlternateKeepAlive) {
++ long id = packetplayinkeepalive.getId();
++ if (keepAlives.size() > 0 && keepAlives.contains(id)) {
++ int ping = (int) (SystemUtils.getMonotonicMillis() - id);
++ player.ping = (player.ping * 3 + ping) / 4;
++ keepAlives.clear(); // we got a valid response, lets roll with it and forget the rest
++ }
++ } else
++ // Purpur end
+ //PlayerConnectionUtils.ensureMainThread(packetplayinkeepalive, this, this.player.getWorldServer()); // CraftBukkit // Paper - This shouldn't be on the main thread
+ if (this.awaitingKeepAlive && packetplayinkeepalive.b() == this.h) {
+ int i = (int) (SystemUtils.getMonotonicMillis() - this.lastKeepAlive);
+diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java
+index 089ae62c2189fe774796ecc6caf9961d3edb5ea3..afd0c577069f2a856caf41bd2dd5187db4866fa3 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java
+@@ -155,6 +155,11 @@ public class PurpurConfig {
+ laggingThreshold = getDouble("settings.lagging-threshold", laggingThreshold);
+ }
+
++ public static boolean useAlternateKeepAlive = false;
++ private static void useAlternateKeepAlive() {
++ useAlternateKeepAlive = getBoolean("settings.use-alternate-keepalive", useAlternateKeepAlive);
++ }
++
+ public static boolean barrelSixRows = false;
+ public static boolean enderChestSixRows = false;
+ public static boolean enderChestPermissionRows = false;
diff --git a/patches/Purpur/patches/server/0018-Silk-touch-spawners.patch b/patches/Purpur/patches/server/0018-Silk-touch-spawners.patch
new file mode 100644
index 00000000..65b904c4
--- /dev/null
+++ b/patches/Purpur/patches/server/0018-Silk-touch-spawners.patch
@@ -0,0 +1,235 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Thu, 9 May 2019 14:27:37 -0500
+Subject: [PATCH] Silk touch spawners
+
+
+diff --git a/src/main/java/io/papermc/paper/adventure/PaperAdventure.java b/src/main/java/io/papermc/paper/adventure/PaperAdventure.java
+index 7b14b3c2486f03778d4673cf9684aa576dc2724a..822ffa8e4b554fd6aa7ce1b2bb68c198c863d86c 100644
+--- a/src/main/java/io/papermc/paper/adventure/PaperAdventure.java
++++ b/src/main/java/io/papermc/paper/adventure/PaperAdventure.java
+@@ -81,6 +81,7 @@ public final class PaperAdventure {
+ })
+ .build();
+ public static final LegacyComponentSerializer LEGACY_SECTION_UXRC = LegacyComponentSerializer.builder().flattener(FLATTENER).hexColors().useUnusualXRepeatedCharacterHexFormat().build();
++ public static final LegacyComponentSerializer LEGACY_AMPERSAND = LegacyComponentSerializer.builder().character(LegacyComponentSerializer.AMPERSAND_CHAR).hexColors().build(); // Purpur
+ public static final PlainComponentSerializer PLAIN = PlainComponentSerializer.builder().flattener(FLATTENER).build();
+ public static final GsonComponentSerializer GSON = GsonComponentSerializer.builder()
+ .legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.INSTANCE)
+diff --git a/src/main/java/net/minecraft/server/ItemSpawner.java b/src/main/java/net/minecraft/server/ItemSpawner.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..599672ed4d0fc412ad3c0fa2e9d9df7035694fa2
+--- /dev/null
++++ b/src/main/java/net/minecraft/server/ItemSpawner.java
+@@ -0,0 +1,35 @@
++package net.minecraft.server;
++
++import net.minecraft.core.BlockPosition;
++import net.minecraft.nbt.NBTTagCompound;
++import net.minecraft.world.entity.EntityTypes;
++import net.minecraft.world.entity.player.EntityHuman;
++import net.minecraft.world.item.ItemBlock;
++import net.minecraft.world.item.ItemStack;
++import net.minecraft.world.level.World;
++import net.minecraft.world.level.block.Block;
++import net.minecraft.world.level.block.entity.TileEntity;
++import net.minecraft.world.level.block.entity.TileEntityMobSpawner;
++import net.minecraft.world.level.block.state.IBlockData;
++
++public class ItemSpawner extends ItemBlock {
++ public ItemSpawner(Block block, Info info) {
++ super(block, info);
++ }
++
++ @Override
++ protected boolean a(BlockPosition blockposition, World world, EntityHuman entityhuman, ItemStack itemstack, IBlockData iblockdata) {
++ boolean handled = super.a(blockposition, world, entityhuman, itemstack, iblockdata);
++ if (world.purpurConfig.silkTouchEnabled && entityhuman.getBukkitEntity().hasPermission("purpur.place.spawners")) {
++ TileEntity spawner = world.getTileEntity(blockposition);
++ if (spawner instanceof TileEntityMobSpawner && itemstack.hasTag()) {
++ NBTTagCompound tag = itemstack.getTag();
++ if (tag.hasKey("Purpur.mob_type")) {
++ EntityTypes.getByName(tag.getString("Purpur.mob_type")).ifPresent(type ->
++ ((TileEntityMobSpawner) spawner).getSpawner().setMobName(type));
++ }
++ }
++ }
++ return handled;
++ }
++}
+diff --git a/src/main/java/net/minecraft/world/item/Items.java b/src/main/java/net/minecraft/world/item/Items.java
+index fc5cc610e7ea584ce72600b9d9f47543265725bb..8e9a25495d76251a86268d3059e2960a86dc46b3 100644
+--- a/src/main/java/net/minecraft/world/item/Items.java
++++ b/src/main/java/net/minecraft/world/item/Items.java
+@@ -2,6 +2,7 @@ package net.minecraft.world.item;
+
+ import net.minecraft.core.IRegistry;
+ import net.minecraft.resources.MinecraftKey;
++import net.minecraft.server.ItemSpawner;
+ import net.minecraft.sounds.SoundEffects;
+ import net.minecraft.world.entity.EntityTypes;
+ import net.minecraft.world.entity.EnumItemSlot;
+@@ -193,7 +194,7 @@ public class Items {
+ public static final Item ct = a(Blocks.PURPUR_BLOCK, CreativeModeTab.b);
+ public static final Item cu = a(Blocks.PURPUR_PILLAR, CreativeModeTab.b);
+ public static final Item cv = a(Blocks.PURPUR_STAIRS, CreativeModeTab.b);
+- public static final Item cw = a(Blocks.SPAWNER);
++ public static final Item cw = a(Blocks.SPAWNER, new ItemSpawner(Blocks.SPAWNER, new Item.Info().a(EnumItemRarity.EPIC))); // Purpur
+ public static final Item cx = a(Blocks.OAK_STAIRS, CreativeModeTab.b);
+ public static final Item cy = a(Blocks.CHEST, CreativeModeTab.c);
+ public static final Item cz = a(Blocks.DIAMOND_ORE, CreativeModeTab.b);
+diff --git a/src/main/java/net/minecraft/world/level/block/BlockMobSpawner.java b/src/main/java/net/minecraft/world/level/block/BlockMobSpawner.java
+index 287dd5f1b2b913df4029966860cd1a426947b187..af3c1a6307fb9e244226794508382d2ffa2aeb4b 100644
+--- a/src/main/java/net/minecraft/world/level/block/BlockMobSpawner.java
++++ b/src/main/java/net/minecraft/world/level/block/BlockMobSpawner.java
+@@ -1,14 +1,35 @@
+ package net.minecraft.world.level.block;
+
+ import net.minecraft.core.BlockPosition;
++import net.minecraft.nbt.NBTTagCompound;
++import net.minecraft.nbt.NBTTagList;
++import net.minecraft.nbt.NBTTagString;
++import net.minecraft.resources.MinecraftKey;
+ import net.minecraft.server.level.WorldServer;
++import net.minecraft.world.entity.EntityTypes;
++import net.minecraft.world.entity.player.EntityHuman;
+ import net.minecraft.world.item.ItemStack;
++import net.minecraft.world.item.enchantment.EnchantmentManager;
++import net.minecraft.world.item.enchantment.Enchantments;
+ import net.minecraft.world.level.IBlockAccess;
++import net.minecraft.world.level.World;
+ import net.minecraft.world.level.block.entity.TileEntity;
+ import net.minecraft.world.level.block.entity.TileEntityMobSpawner;
+ import net.minecraft.world.level.block.state.BlockBase;
+ import net.minecraft.world.level.block.state.IBlockData;
+
++// Purpur start
++import io.papermc.paper.adventure.PaperAdventure;
++import net.kyori.adventure.text.Component;
++import net.kyori.adventure.text.TextReplacementConfig;
++import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
++import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
++
++import java.util.List;
++
++import static net.kyori.adventure.text.format.TextDecoration.ITALIC;
++// Purpur end
++
+ public class BlockMobSpawner extends BlockTileEntity {
+
+ protected BlockMobSpawner(BlockBase.Info blockbase_info) {
+@@ -20,6 +41,59 @@ public class BlockMobSpawner extends BlockTileEntity {
+ return new TileEntityMobSpawner();
+ }
+
++ // Purpur start
++ @Override
++ public void a(World world, EntityHuman entityhuman, BlockPosition blockposition, IBlockData iblockdata, TileEntity tileentity, ItemStack itemstack) {
++ if (world.purpurConfig.silkTouchEnabled && entityhuman.getBukkitEntity().hasPermission("purpur.drop.spawners") && isSilkTouch(world, itemstack)) {
++ MinecraftKey type = ((TileEntityMobSpawner) tileentity).getSpawner().getMobName();
++ if (type != null) {
++ final Component mobName = PaperAdventure.asAdventure(EntityTypes.getFromKey(type).getNameComponent());
++ final TextReplacementConfig config = TextReplacementConfig.builder()
++ .matchLiteral("{mob}")
++ .replacement(mobName)
++ .build();
++
++ NBTTagCompound display = new NBTTagCompound();
++ boolean customDisplay = false;
++
++ String name = world.purpurConfig.silkTouchSpawnerName;
++ if (name != null && !name.isEmpty() && !name.equals("Spawner")) {
++ final Component displayName = PaperAdventure.LEGACY_AMPERSAND.deserialize(name).replaceText(config).decoration(ITALIC, false);
++ display.set("Name", NBTTagString.create(GsonComponentSerializer.gson().serialize(displayName)));
++ customDisplay = true;
++ }
++
++ List lore = world.purpurConfig.silkTouchSpawnerLore;
++ if (lore != null && !lore.isEmpty()) {
++ NBTTagList list = new NBTTagList();
++ for (String line : lore) {
++ final Component lineComponent = PaperAdventure.LEGACY_AMPERSAND.deserialize(line).replaceText(config).decoration(ITALIC, false);
++ list.add(NBTTagString.create(GsonComponentSerializer.gson().serialize(lineComponent)));
++ }
++ display.set("Lore", list);
++ customDisplay = true;
++ }
++
++ NBTTagCompound tag = new NBTTagCompound();
++ if (customDisplay) {
++ tag.set("display", display);
++ }
++ tag.setString("Purpur.mob_type", type.toString());
++
++ ItemStack item = new ItemStack(Blocks.SPAWNER.getItem());
++ item.setTag(tag);
++
++ dropItem(world, blockposition, item);
++ }
++ }
++ super.a(world, entityhuman, blockposition, iblockdata, tileentity, itemstack);
++ }
++
++ private boolean isSilkTouch(World world, ItemStack itemstack) {
++ return itemstack != null && world.purpurConfig.silkTouchTools.contains(itemstack.getItem()) && EnchantmentManager.getEnchantmentLevel(Enchantments.SILK_TOUCH, itemstack) > 0;
++ }
++ // Purpur end
++
+ @Override
+ public void dropNaturally(IBlockData iblockdata, WorldServer worldserver, BlockPosition blockposition, ItemStack itemstack) {
+ super.dropNaturally(iblockdata, worldserver, blockposition, itemstack);
+@@ -32,6 +106,7 @@ public class BlockMobSpawner extends BlockTileEntity {
+
+ @Override
+ public int getExpDrop(IBlockData iblockdata, WorldServer worldserver, BlockPosition blockposition, ItemStack itemstack) {
++ if (isSilkTouch(worldserver, itemstack)) return 0; // Purpur
+ int i = 15 + worldserver.random.nextInt(15) + worldserver.random.nextInt(15);
+
+ return i;
+diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+index c7fb5a737cab0083c39732247acb8f4e87562894..10a6fcd70869719ed2b2d1442a83ab912e00c898 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+@@ -1,6 +1,12 @@
+ package net.pl3x.purpur;
+
++import net.minecraft.core.IRegistry;
++import net.minecraft.world.item.Item;
++import net.minecraft.world.item.Items;
++import net.minecraft.resources.MinecraftKey;
+ import org.bukkit.configuration.ConfigurationSection;
++
++import java.util.ArrayList;
+ import java.util.List;
+ import static net.pl3x.purpur.PurpurConfig.log;
+
+@@ -75,6 +81,29 @@ public class PurpurWorldConfig {
+ playerInvulnerableWhileAcceptingResourcePack = getBoolean("gameplay-mechanics.player.invulnerable-while-accepting-resource-pack", playerInvulnerableWhileAcceptingResourcePack);
+ }
+
++ public boolean silkTouchEnabled = false;
++ public String silkTouchSpawnerName = "Spawner";
++ public List silkTouchSpawnerLore = new ArrayList<>();
++ public List- silkTouchTools = new ArrayList<>();
++ private void silkTouchSettings() {
++ silkTouchEnabled = getBoolean("gameplay-mechanics.silk-touch.enabled", silkTouchEnabled);
++ silkTouchSpawnerName = getString("gameplay-mechanics.silk-touch.spawner-name", silkTouchSpawnerName);
++ silkTouchSpawnerLore.clear();
++ getList("gameplay-mechanics.silk-touch.spawner-lore", new ArrayList(){{
++ add("Spawns a {mob}");
++ }}).forEach(line -> silkTouchSpawnerLore.add(line.toString()));
++ silkTouchTools.clear();
++ getList("gameplay-mechanics.silk-touch.tools", new ArrayList(){{
++ add("minecraft:iron_pickaxe");
++ add("minecraft:golden_pickaxe");
++ add("minecraft:diamond_pickaxe");
++ add("minecraft:netherite_pickaxe");
++ }}).forEach(key -> {
++ Item item = IRegistry.ITEM.get(new MinecraftKey(key.toString()));
++ if (item != Items.AIR) silkTouchTools.add(item);
++ });
++ }
++
+ public int villagerBrainTicks = 1;
+ public boolean villagerUseBrainTicksOnlyWhenLagging = true;
+ private void villagerSettings() {
diff --git a/patches/Purpur/patches/server/0019-MC-168772-Fix-Add-turtle-egg-block-options.patch b/patches/Purpur/patches/server/0019-MC-168772-Fix-Add-turtle-egg-block-options.patch
new file mode 100644
index 00000000..ae221310
--- /dev/null
+++ b/patches/Purpur/patches/server/0019-MC-168772-Fix-Add-turtle-egg-block-options.patch
@@ -0,0 +1,71 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Thu, 6 Jun 2019 22:15:46 -0500
+Subject: [PATCH] MC-168772 Fix - Add turtle egg block options
+
+
+diff --git a/src/main/java/net/minecraft/world/level/block/BlockTurtleEgg.java b/src/main/java/net/minecraft/world/level/block/BlockTurtleEgg.java
+index 6093d4c7431a286477c9be97163ea8d64168c3b0..04504901b1933ed760b34b8abb994de8ec340a4e 100644
+--- a/src/main/java/net/minecraft/world/level/block/BlockTurtleEgg.java
++++ b/src/main/java/net/minecraft/world/level/block/BlockTurtleEgg.java
+@@ -9,12 +9,15 @@ import net.minecraft.sounds.SoundEffects;
+ import net.minecraft.tags.Tag;
+ import net.minecraft.tags.TagsBlock;
+ import net.minecraft.world.entity.Entity;
++import net.minecraft.world.entity.EntityExperienceOrb;
+ import net.minecraft.world.entity.EntityLiving;
+ import net.minecraft.world.entity.EntityTypes;
+ import net.minecraft.world.entity.ambient.EntityBat;
+ import net.minecraft.world.entity.animal.EntityTurtle;
++import net.minecraft.world.entity.item.EntityItem;
+ import net.minecraft.world.entity.monster.EntityZombie;
+ import net.minecraft.world.entity.player.EntityHuman;
++import net.minecraft.world.entity.vehicle.EntityMinecartAbstract;
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.item.context.BlockActionContext;
+ import net.minecraft.world.level.GameRules;
+@@ -189,6 +192,23 @@ public class BlockTurtleEgg extends Block {
+ }
+
+ private boolean a(World world, Entity entity) {
+- return !(entity instanceof EntityTurtle) && !(entity instanceof EntityBat) ? (!(entity instanceof EntityLiving) ? false : entity instanceof EntityHuman || world.getGameRules().getBoolean(GameRules.MOB_GRIEFING)) : false;
++ // Purpur start - fix MC-168772
++ if (entity instanceof EntityTurtle) {
++ return false;
++ }
++ if (!world.purpurConfig.turtleEggsBreakFromExpOrbs && entity instanceof EntityExperienceOrb) {
++ return false;
++ }
++ if (!world.purpurConfig.turtleEggsBreakFromItems && entity instanceof EntityItem) {
++ return false;
++ }
++ if (!world.purpurConfig.turtleEggsBreakFromMinecarts && entity instanceof EntityMinecartAbstract) {
++ return false;
++ }
++ if (entity instanceof EntityLiving && !(entity instanceof EntityHuman)) {
++ return world.getGameRules().getBoolean(GameRules.MOB_GRIEFING);
++ }
++ return true;
++ // Purpur end
+ }
+ }
+diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+index 10a6fcd70869719ed2b2d1442a83ab912e00c898..2f18ca7ae23e913155f25fd07627f376e401ab0f 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+@@ -104,6 +104,15 @@ public class PurpurWorldConfig {
+ });
+ }
+
++ public boolean turtleEggsBreakFromExpOrbs = true;
++ public boolean turtleEggsBreakFromItems = true;
++ public boolean turtleEggsBreakFromMinecarts = true;
++ private void turtleEggSettings() {
++ turtleEggsBreakFromExpOrbs = getBoolean("blocks.turtle_egg.break-from-exp-orbs", turtleEggsBreakFromExpOrbs);
++ turtleEggsBreakFromItems = getBoolean("blocks.turtle_egg.break-from-items", turtleEggsBreakFromItems);
++ turtleEggsBreakFromMinecarts = getBoolean("blocks.turtle_egg.break-from-minecarts", turtleEggsBreakFromMinecarts);
++ }
++
+ public int villagerBrainTicks = 1;
+ public boolean villagerUseBrainTicksOnlyWhenLagging = true;
+ private void villagerSettings() {
diff --git a/patches/Purpur/patches/server/0020-Fix-vanilla-command-permission-handler.patch b/patches/Purpur/patches/server/0020-Fix-vanilla-command-permission-handler.patch
new file mode 100644
index 00000000..06216b6d
--- /dev/null
+++ b/patches/Purpur/patches/server/0020-Fix-vanilla-command-permission-handler.patch
@@ -0,0 +1,30 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sat, 28 Mar 2020 01:51:32 -0500
+Subject: [PATCH] Fix vanilla command permission handler
+
+
+diff --git a/src/main/java/com/mojang/brigadier/tree/CommandNode.java b/src/main/java/com/mojang/brigadier/tree/CommandNode.java
+index c0fac7369b111e65b896a15848ae22457e5e8914..5278997e522b495b83e53cac5968388d6eca45e4 100644
+--- a/src/main/java/com/mojang/brigadier/tree/CommandNode.java
++++ b/src/main/java/com/mojang/brigadier/tree/CommandNode.java
+@@ -35,6 +35,7 @@ public abstract class CommandNode
implements Comparable> {
+ private final RedirectModifier modifier;
+ private final boolean forks;
+ private Command command;
++ private String permission = null; public String getPermission() { return permission; } public void setPermission(String permission) { this.permission = permission; } // Purpur
+ // CraftBukkit start
+ public void removeCommand(String name) {
+ children.remove(name);
+diff --git a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java
+index 56a0665127c7c55049b8438c91e72b6881ed11e0..575bc7d8a433cd6d4755757d82fe3a18da184d5a 100644
+--- a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java
++++ b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java
+@@ -87,6 +87,7 @@ public final class VanillaCommandWrapper extends BukkitCommand {
+ }
+
+ public static String getPermission(CommandNode vanillaCommand) {
++ if (vanillaCommand.getPermission() != null) return vanillaCommand.getPermission(); // Purpur
+ return "minecraft.command." + ((vanillaCommand.getRedirect() == null) ? vanillaCommand.getName() : vanillaCommand.getRedirect().getName());
+ }
+
diff --git a/patches/Purpur/patches/server/0021-Logger-settings-suppressing-pointless-logs.patch b/patches/Purpur/patches/server/0021-Logger-settings-suppressing-pointless-logs.patch
new file mode 100644
index 00000000..2819f1b7
--- /dev/null
+++ b/patches/Purpur/patches/server/0021-Logger-settings-suppressing-pointless-logs.patch
@@ -0,0 +1,46 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sat, 19 Oct 2019 00:52:12 -0500
+Subject: [PATCH] Logger settings (suppressing pointless logs)
+
+
+diff --git a/src/main/java/net/minecraft/server/AdvancementDataPlayer.java b/src/main/java/net/minecraft/server/AdvancementDataPlayer.java
+index dfdbc028f68ced197ad179248ed3b1e9d70ba057..a1ee1066108985a95abddb03ff447b5a14f4f85f 100644
+--- a/src/main/java/net/minecraft/server/AdvancementDataPlayer.java
++++ b/src/main/java/net/minecraft/server/AdvancementDataPlayer.java
+@@ -190,6 +190,7 @@ public class AdvancementDataPlayer {
+ if (advancement == null) {
+ // CraftBukkit start
+ if (entry.getKey().getNamespace().equals("minecraft")) {
++ if (!net.pl3x.purpur.PurpurConfig.loggerSuppressIgnoredAdvancementWarnings) // Purpur
+ AdvancementDataPlayer.LOGGER.warn("Ignored advancement '{}' in progress file {} - it doesn't exist anymore?", entry.getKey(), this.f);
+ }
+ // CraftBukkit end
+diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java
+index afd0c577069f2a856caf41bd2dd5187db4866fa3..c7755cea5e8337af7acc96c6a34afa547b391035 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java
+@@ -178,4 +178,11 @@ public class PurpurConfig {
+ InventoryType.ENDER_CHEST.setDefaultSize(enderChestSixRows ? 54 : 27);
+ enderChestPermissionRows = getBoolean("settings.blocks.ender_chest.use-permissions-for-rows", enderChestPermissionRows);
+ }
++
++ public static boolean loggerSuppressInitLegacyMaterialError = false;
++ public static boolean loggerSuppressIgnoredAdvancementWarnings = false;
++ private static void loggerSettings() {
++ loggerSuppressInitLegacyMaterialError = getBoolean("settings.logger.suppress-init-legacy-material-errors", loggerSuppressInitLegacyMaterialError);
++ loggerSuppressIgnoredAdvancementWarnings = getBoolean("settings.logger.suppress-ignored-advancement-warnings", loggerSuppressIgnoredAdvancementWarnings);
++ }
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java b/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java
+index b86604cbf3543b978df000d8f74c6185aa2ae7ec..5df82b0409278bd298e837aa43941247de3f94fe 100644
+--- a/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java
++++ b/src/main/java/org/bukkit/craftbukkit/legacy/CraftLegacy.java
+@@ -254,6 +254,7 @@ public final class CraftLegacy {
+ }
+
+ static {
++ if (!net.pl3x.purpur.PurpurConfig.loggerSuppressInitLegacyMaterialError) // Purpur
+ System.err.println("Initializing Legacy Material Support. Unless you have legacy plugins and/or data this is a bug!");
+ if (MinecraftServer.getServer() != null && MinecraftServer.getServer().isDebugging()) {
+ new Exception().printStackTrace();
diff --git a/patches/Purpur/patches/server/0022-Disable-outdated-build-check.patch b/patches/Purpur/patches/server/0022-Disable-outdated-build-check.patch
new file mode 100644
index 00000000..4fed75af
--- /dev/null
+++ b/patches/Purpur/patches/server/0022-Disable-outdated-build-check.patch
@@ -0,0 +1,19 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sun, 15 Dec 2019 12:53:59 -0600
+Subject: [PATCH] Disable outdated build check
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java
+index 424d55f51a36d9033db3dfbf2f7a86749b0e3b91..73513638dd024cb1cdd49c705921f0bcf1935968 100644
+--- a/src/main/java/org/bukkit/craftbukkit/Main.java
++++ b/src/main/java/org/bukkit/craftbukkit/Main.java
+@@ -272,7 +272,7 @@ public class Main {
+ System.setProperty(TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY, "false"); // Paper
+ }
+
+- if (false && Main.class.getPackage().getImplementationVendor() != null && System.getProperty("IReallyKnowWhatIAmDoingISwear") == null) {
++ if (false && Main.class.getPackage().getImplementationVendor() != null && System.getProperty("IReallyKnowWhatIAmDoingISwear") == null) { // Purpur - break on change
+ Date buildDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").parse(Main.class.getPackage().getImplementationVendor()); // Paper
+
+ Calendar deadline = Calendar.getInstance();
diff --git a/patches/Purpur/patches/server/0023-Giants-AI-settings.patch b/patches/Purpur/patches/server/0023-Giants-AI-settings.patch
new file mode 100644
index 00000000..afa44eae
--- /dev/null
+++ b/patches/Purpur/patches/server/0023-Giants-AI-settings.patch
@@ -0,0 +1,215 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sun, 12 May 2019 00:43:12 -0500
+Subject: [PATCH] Giants AI settings
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
+index 89852779fd9cfd19058afe40feb0cf14ca8d2896..611a2c34c80462826564705eb5a079bd6fdda4e1 100644
+--- a/src/main/java/net/minecraft/world/entity/Entity.java
++++ b/src/main/java/net/minecraft/world/entity/Entity.java
+@@ -228,7 +228,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne
+ public double D;
+ public double E;
+ public double F;
+- public float G; public final float getStepHeight() { return this.G; } // Tuinity - OBFHELPER
++ public float G; public final float getStepHeight() { return this.G; } public void setStepHeight(float stepHeight) { this.G = stepHeight; } // Tuinity - OBFHELPER // Purpur - OBFHELPER
+ public boolean noclip;
+ public float I;
+ protected final Random random;
+diff --git a/src/main/java/net/minecraft/world/entity/EntityInsentient.java b/src/main/java/net/minecraft/world/entity/EntityInsentient.java
+index 7ba59ff9a7ed39bf69c46973a85f874c43134dc1..46e9a232ca5df81cafd4c3fc6af43b933c95d04f 100644
+--- a/src/main/java/net/minecraft/world/entity/EntityInsentient.java
++++ b/src/main/java/net/minecraft/world/entity/EntityInsentient.java
+@@ -1021,6 +1021,7 @@ public abstract class EntityInsentient extends EntityLiving {
+ return f;
+ }
+
++ protected void setEquipmentBasedOnDifficulty(DifficultyDamageScaler difficultydamagescaler) { a(difficultydamagescaler); } // Purpur - OBFHELPER
+ protected void a(DifficultyDamageScaler difficultydamagescaler) {
+ if (this.random.nextFloat() < 0.15F * difficultydamagescaler.d()) {
+ int i = this.random.nextInt(2);
+@@ -1128,6 +1129,7 @@ public abstract class EntityInsentient extends EntityLiving {
+ }
+ }
+
++ protected void setEnchantmentBasedOnDifficulty(DifficultyDamageScaler difficultydamagescaler) { b(difficultydamagescaler); } // Purpur - OBFHELPER
+ protected void b(DifficultyDamageScaler difficultydamagescaler) {
+ float f = difficultydamagescaler.d();
+
+diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java
+index 7d0bb706fb106709432c3fae8758d2cea1d14db8..032b1a519de56583990fe47a216665ce71cf93ab 100644
+--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java
++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java
+@@ -260,6 +260,7 @@ public abstract class EntityLiving extends Entity {
+ this.activeItem = ItemStack.b;
+ this.by = Optional.empty();
+ this.attributeMap = new AttributeMapBase(AttributeDefaults.a(entitytypes));
++ this.initAttributes(); // Purpur
+ this.craftAttributes = new CraftAttributeMap(attributeMap); // CraftBukkit
+ // CraftBukkit - setHealth(getMaxHealth()) inlined and simplified to skip the instanceof check for EntityPlayer, as getBukkitEntity() is not initialized in constructor
+ this.datawatcher.set(EntityLiving.HEALTH, (float) this.getAttributeInstance(GenericAttributes.MAX_HEALTH).getValue());
+@@ -275,6 +276,8 @@ public abstract class EntityLiving extends Entity {
+ this.bg = this.a(new Dynamic(dynamicopsnbt, dynamicopsnbt.createMap((Map) ImmutableMap.of(dynamicopsnbt.createString("memories"), dynamicopsnbt.emptyMap()))));
+ }
+
++ protected void initAttributes() {} // Purpur
++
+ public BehaviorController> getBehaviorController() {
+ return this.bg;
+ }
+@@ -2273,7 +2276,7 @@ public abstract class EntityLiving extends Entity {
+ this.enderTeleportTo(vec3d.x, vec3d.y, vec3d.z);
+ }
+
+- protected float dJ() {
++ protected float dJ() { return getJumpHeight(); } public float getJumpHeight() { // Purpur - OBFHELPER
+ return 0.42F * this.getBlockJumpFactor();
+ }
+
+diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityGiantZombie.java b/src/main/java/net/minecraft/world/entity/monster/EntityGiantZombie.java
+index 5e6a92dcdbca686d5a8cfc4aaff72b70b81b111f..a188a89143cb1b0243dacdec33c446ca4120219f 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/EntityGiantZombie.java
++++ b/src/main/java/net/minecraft/world/entity/monster/EntityGiantZombie.java
+@@ -1,21 +1,99 @@
+ package net.minecraft.world.entity.monster;
+
+ import net.minecraft.core.BlockPosition;
++import net.minecraft.nbt.NBTTagCompound;
++import net.minecraft.world.DifficultyDamageScaler;
++import net.minecraft.world.EnumDifficulty;
+ import net.minecraft.world.entity.EntityPose;
+ import net.minecraft.world.entity.EntitySize;
+ import net.minecraft.world.entity.EntityTypes;
++import net.minecraft.world.entity.EnumItemSlot;
++import net.minecraft.world.entity.EnumMobSpawn;
++import net.minecraft.world.entity.GroupDataEntity;
+ import net.minecraft.world.entity.ai.attributes.AttributeProvider;
+ import net.minecraft.world.entity.ai.attributes.GenericAttributes;
++import net.minecraft.world.entity.ai.goal.PathfinderGoalFloat;
++import net.minecraft.world.entity.ai.goal.PathfinderGoalLookAtPlayer;
++import net.minecraft.world.entity.ai.goal.PathfinderGoalMeleeAttack;
++import net.minecraft.world.entity.ai.goal.PathfinderGoalMoveTowardsRestriction;
++import net.minecraft.world.entity.ai.goal.PathfinderGoalRandomLookaround;
++import net.minecraft.world.entity.ai.goal.PathfinderGoalRandomStrollLand;
++import net.minecraft.world.entity.ai.goal.target.PathfinderGoalHurtByTarget;
++import net.minecraft.world.entity.ai.goal.target.PathfinderGoalNearestAttackableTarget;
++import net.minecraft.world.entity.animal.EntityIronGolem;
++import net.minecraft.world.entity.animal.EntityTurtle;
++import net.minecraft.world.entity.npc.EntityVillager;
++import net.minecraft.world.entity.player.EntityHuman;
++import net.minecraft.world.item.ItemStack;
++import net.minecraft.world.item.Items;
+ import net.minecraft.world.level.IWorldReader;
+ import net.minecraft.world.level.World;
++import net.minecraft.world.level.WorldAccess;
+
+ public class EntityGiantZombie extends EntityMonster {
+
+ public EntityGiantZombie(EntityTypes extends EntityGiantZombie> entitytypes, World world) {
+ super(entitytypes, world);
+- this.safeFallDistance = 10.0F; // Purpur
++ // Purpur start
++ this.safeFallDistance = 10.0F;
++ setStepHeight(world.purpurConfig.giantStepHeight);
++ // Purpur end
+ }
+
++ // Purpur start
++ @Override
++ protected void initPathfinder() {
++ if (world.purpurConfig.giantHaveAI) {
++ this.goalSelector.a(0, new PathfinderGoalFloat(this));
++ this.goalSelector.a(7, new PathfinderGoalRandomStrollLand(this, 1.0D));
++ this.goalSelector.a(8, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 16.0F));
++ this.goalSelector.a(8, new PathfinderGoalRandomLookaround(this));
++ this.goalSelector.a(5, new PathfinderGoalMoveTowardsRestriction(this, 1.0D));
++ if (world.purpurConfig.giantHaveHostileAI) {
++ this.goalSelector.a(2, new PathfinderGoalMeleeAttack(this, 1.0D, false));
++ this.targetSelector.a(1, new PathfinderGoalHurtByTarget(this).a(EntityPigZombie.class));
++ this.targetSelector.a(2, new PathfinderGoalNearestAttackableTarget<>(this, EntityHuman.class, true));
++ this.targetSelector.a(3, new PathfinderGoalNearestAttackableTarget<>(this, EntityVillager.class, false));
++ this.targetSelector.a(4, new PathfinderGoalNearestAttackableTarget<>(this, EntityIronGolem.class, true));
++ this.targetSelector.a(5, new PathfinderGoalNearestAttackableTarget<>(this, EntityTurtle.class, true));
++ }
++ }
++ }
++
++ @Override
++ protected void initAttributes() {
++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.giantMaxHealth);
++ this.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).setValue(this.world.purpurConfig.giantMovementSpeed);
++ this.getAttributeInstance(GenericAttributes.ATTACK_DAMAGE).setValue(this.world.purpurConfig.giantAttackDamage);
++ }
++
++ @Override
++ public GroupDataEntity prepare(WorldAccess worldaccess, DifficultyDamageScaler difficultydamagescaler, EnumMobSpawn enummobspawn, @javax.annotation.Nullable GroupDataEntity groupdataentity, @javax.annotation.Nullable NBTTagCompound nbttagcompound) {
++ GroupDataEntity groupData = super.prepare(worldaccess, difficultydamagescaler, enummobspawn, groupdataentity, nbttagcompound);
++ if (groupData == null) {
++ setEquipmentBasedOnDifficulty(difficultydamagescaler);
++ setEnchantmentBasedOnDifficulty(difficultydamagescaler);
++ }
++ return groupData;
++ }
++
++ @Override
++ protected void setEquipmentBasedOnDifficulty(DifficultyDamageScaler difficulty) {
++ super.setEquipmentBasedOnDifficulty(difficulty);
++ // TODO make configurable
++ if (random.nextFloat() < (world.getDifficulty() == EnumDifficulty.HARD ? 0.1F : 0.05F)) {
++ setSlot(EnumItemSlot.MAINHAND, new ItemStack(Items.IRON_SWORD));
++ }
++ }
++
++ @Override
++ public float getJumpHeight() {
++ // make giants jump as high as everything else relative to their size
++ // 1.0 makes bottom of feet about as high as their waist when they jump
++ return world.purpurConfig.giantJumpHeight;
++ }
++ // Purpur end
++
+ @Override
+ protected float b(EntityPose entitypose, EntitySize entitysize) {
+ return 10.440001F;
+@@ -27,6 +105,6 @@ public class EntityGiantZombie extends EntityMonster {
+
+ @Override
+ public float a(BlockPosition blockposition, IWorldReader iworldreader) {
+- return iworldreader.y(blockposition) - 0.5F;
++ return super.a(blockposition, iworldreader); // Purpur - fix light requirements for natural spawns
+ }
+ }
+diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+index 2f18ca7ae23e913155f25fd07627f376e401ab0f..1c87c929aaae17631100d1aa30b3e7ecce52686f 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+@@ -113,6 +113,28 @@ public class PurpurWorldConfig {
+ turtleEggsBreakFromMinecarts = getBoolean("blocks.turtle_egg.break-from-minecarts", turtleEggsBreakFromMinecarts);
+ }
+
++ public float giantStepHeight = 2.0F;
++ public float giantJumpHeight = 1.0F;
++ public double giantMovementSpeed = 0.5D;
++ public double giantAttackDamage = 50.0D;
++ public boolean giantHaveAI = false;
++ public boolean giantHaveHostileAI = false;
++ public double giantMaxHealth = 100.0D;
++ private void giantSettings() {
++ giantStepHeight = (float) getDouble("mobs.giant.step-height", giantStepHeight);
++ giantJumpHeight = (float) getDouble("mobs.giant.jump-height", giantJumpHeight);
++ giantMovementSpeed = getDouble("mobs.giant.movement-speed", giantMovementSpeed);
++ giantAttackDamage = getDouble("mobs.giant.attack-damage", giantAttackDamage);
++ giantHaveAI = getBoolean("mobs.giant.have-ai", giantHaveAI);
++ giantHaveHostileAI = getBoolean("mobs.giant.have-hostile-ai", giantHaveHostileAI);
++ if (PurpurConfig.version < 8) {
++ double oldValue = getDouble("mobs.giant.max-health", giantMaxHealth);
++ set("mobs.giant.attributes.max-health", oldValue);
++ set("mobs.giant.max-health", null);
++ }
++ giantMaxHealth = getDouble("mobs.giant.attributes.max-health", giantMaxHealth);
++ }
++
+ public int villagerBrainTicks = 1;
+ public boolean villagerUseBrainTicksOnlyWhenLagging = true;
+ private void villagerSettings() {
diff --git a/patches/Purpur/patches/server/0024-Illusioners-AI-settings.patch b/patches/Purpur/patches/server/0024-Illusioners-AI-settings.patch
new file mode 100644
index 00000000..ffcf44ac
--- /dev/null
+++ b/patches/Purpur/patches/server/0024-Illusioners-AI-settings.patch
@@ -0,0 +1,51 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Fri, 5 Jul 2019 11:09:25 -0500
+Subject: [PATCH] Illusioners AI settings
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityIllagerIllusioner.java b/src/main/java/net/minecraft/world/entity/monster/EntityIllagerIllusioner.java
+index fee9a5140f097225b5da58b18bfbd528dffdc77b..cb092bee9d6827d4b0276bfa9b033cf7ca86ead4 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/EntityIllagerIllusioner.java
++++ b/src/main/java/net/minecraft/world/entity/monster/EntityIllagerIllusioner.java
+@@ -56,6 +56,15 @@ public class EntityIllagerIllusioner extends EntityIllagerWizard implements IRan
+
+ }
+
++ // Purpur start
++ @Override
++ protected void initAttributes() {
++ this.getAttributeInstance(GenericAttributes.MOVEMENT_SPEED).setValue(this.world.purpurConfig.illusionerMovementSpeed);
++ this.getAttributeInstance(GenericAttributes.FOLLOW_RANGE).setValue(this.world.purpurConfig.illusionerFollowRange);
++ this.getAttributeInstance(GenericAttributes.MAX_HEALTH).setValue(this.world.purpurConfig.illusionerMaxHealth);
++ }
++ // Purpur end
++
+ @Override
+ protected void initPathfinder() {
+ super.initPathfinder();
+diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+index 1c87c929aaae17631100d1aa30b3e7ecce52686f..d6bd971a62af7341f8dc8b3afe32786ced6fcd41 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+@@ -135,6 +135,20 @@ public class PurpurWorldConfig {
+ giantMaxHealth = getDouble("mobs.giant.attributes.max-health", giantMaxHealth);
+ }
+
++ public double illusionerMovementSpeed = 0.5D;
++ public double illusionerFollowRange = 18.0D;
++ public double illusionerMaxHealth = 32.0D;
++ private void illusionerSettings() {
++ illusionerMovementSpeed = getDouble("mobs.illusioner.movement-speed", illusionerMovementSpeed);
++ illusionerFollowRange = getDouble("mobs.illusioner.follow-range", illusionerFollowRange);
++ if (PurpurConfig.version < 8) {
++ double oldValue = getDouble("mobs.illusioner.max-health", illusionerMaxHealth);
++ set("mobs.illusioner.attributes.max-health", oldValue);
++ set("mobs.illusioner.max-health", null);
++ }
++ illusionerMaxHealth = getDouble("mobs.illusioner.attributes.max-health", illusionerMaxHealth);
++ }
++
+ public int villagerBrainTicks = 1;
+ public boolean villagerUseBrainTicksOnlyWhenLagging = true;
+ private void villagerSettings() {
diff --git a/patches/Purpur/patches/server/0025-Zombie-horse-naturally-spawn.patch b/patches/Purpur/patches/server/0025-Zombie-horse-naturally-spawn.patch
new file mode 100644
index 00000000..24f10369
--- /dev/null
+++ b/patches/Purpur/patches/server/0025-Zombie-horse-naturally-spawn.patch
@@ -0,0 +1,57 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sun, 7 Jul 2019 19:52:16 -0500
+Subject: [PATCH] Zombie horse naturally spawn
+
+
+diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java
+index a97b8d8116920237964cc3a887525f8c3804c640..a0c731af6dffa30832b321f8fd86cef76ec06522 100644
+--- a/src/main/java/net/minecraft/server/level/WorldServer.java
++++ b/src/main/java/net/minecraft/server/level/WorldServer.java
+@@ -96,6 +96,7 @@ import net.minecraft.world.entity.ai.village.poi.VillagePlace;
+ import net.minecraft.world.entity.ai.village.poi.VillagePlaceType;
+ import net.minecraft.world.entity.animal.EntityAnimal;
+ import net.minecraft.world.entity.animal.EntityWaterAnimal;
++import net.minecraft.world.entity.animal.horse.EntityHorseAbstract;
+ import net.minecraft.world.entity.animal.horse.EntityHorseSkeleton;
+ import net.minecraft.world.entity.boss.EntityComplexPart;
+ import net.minecraft.world.entity.boss.enderdragon.EntityEnderDragon;
+@@ -1219,12 +1220,18 @@ public class WorldServer extends World implements GeneratorAccessSeed {
+ boolean flag1 = this.getGameRules().getBoolean(GameRules.DO_MOB_SPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.b() * paperConfig.skeleHorseSpawnChance; // Paper
+
+ if (flag1) {
+- EntityHorseSkeleton entityhorseskeleton = (EntityHorseSkeleton) EntityTypes.SKELETON_HORSE.a((World) this);
+-
+- entityhorseskeleton.t(true);
+- entityhorseskeleton.setAgeRaw(0);
+- entityhorseskeleton.setPosition((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ());
+- this.addEntity(entityhorseskeleton, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING); // CraftBukkit
++ // Purpur start
++ EntityHorseAbstract horse;
++ if (purpurConfig.zombieHorseSpawnChance > 0D && random.nextDouble() <= purpurConfig.zombieHorseSpawnChance) {
++ horse = EntityTypes.ZOMBIE_HORSE.create(this);
++ } else {
++ horse = EntityTypes.SKELETON_HORSE.create(this);
++ ((EntityHorseSkeleton) horse).setTrap(true);
++ }
++ horse.setAgeRaw(0);
++ horse.setPosition(blockposition.getX(), blockposition.getY(), blockposition.getZ());
++ addEntity(horse, CreatureSpawnEvent.SpawnReason.LIGHTNING); // CraftBukkit
++ // Purpur end
+ }
+
+ EntityLightning entitylightning = (EntityLightning) EntityTypes.LIGHTNING_BOLT.a((World) this);
+diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+index d6bd971a62af7341f8dc8b3afe32786ced6fcd41..81b0e17a4bc5022ea757f03c2546808148d6e957 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+@@ -155,4 +155,9 @@ public class PurpurWorldConfig {
+ villagerBrainTicks = getInt("mobs.villager.brain-ticks", villagerBrainTicks);
+ villagerUseBrainTicksOnlyWhenLagging = getBoolean("mobs.villager.use-brain-ticks-only-when-lagging", villagerUseBrainTicksOnlyWhenLagging);
+ }
++
++ public double zombieHorseSpawnChance = 0.0D;
++ private void zombieHorseSettings() {
++ zombieHorseSpawnChance = getDouble("mobs.zombie_horse.spawn-chance", zombieHorseSpawnChance);
++ }
+ }
diff --git a/patches/Purpur/patches/server/0026-Charged-creeper-naturally-spawn.patch b/patches/Purpur/patches/server/0026-Charged-creeper-naturally-spawn.patch
new file mode 100644
index 00000000..306f6851
--- /dev/null
+++ b/patches/Purpur/patches/server/0026-Charged-creeper-naturally-spawn.patch
@@ -0,0 +1,69 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Fri, 29 Nov 2019 22:37:44 -0600
+Subject: [PATCH] Charged creeper naturally spawn
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/monster/EntityCreeper.java b/src/main/java/net/minecraft/world/entity/monster/EntityCreeper.java
+index b47f71ca1f1c8bbd1a521836d9cb5d676a33ec76..63a6b1820f60db9eea49a3a589dd50ad25a3c0a2 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/EntityCreeper.java
++++ b/src/main/java/net/minecraft/world/entity/monster/EntityCreeper.java
+@@ -9,6 +9,7 @@ import net.minecraft.network.syncher.DataWatcherRegistry;
+ import net.minecraft.server.level.WorldServer;
+ import net.minecraft.sounds.SoundEffect;
+ import net.minecraft.sounds.SoundEffects;
++import net.minecraft.world.DifficultyDamageScaler;
+ import net.minecraft.world.EnumHand;
+ import net.minecraft.world.EnumInteractionResult;
+ import net.minecraft.world.damagesource.DamageSource;
+@@ -17,6 +18,8 @@ import net.minecraft.world.entity.Entity;
+ import net.minecraft.world.entity.EntityAreaEffectCloud;
+ import net.minecraft.world.entity.EntityLightning;
+ import net.minecraft.world.entity.EntityTypes;
++import net.minecraft.world.entity.EnumMobSpawn;
++import net.minecraft.world.entity.GroupDataEntity;
+ import net.minecraft.world.entity.ai.attributes.AttributeProvider;
+ import net.minecraft.world.entity.ai.attributes.GenericAttributes;
+ import net.minecraft.world.entity.ai.goal.PathfinderGoalAvoidTarget;
+@@ -39,6 +42,7 @@ import net.minecraft.world.level.IMaterial;
+ import net.minecraft.world.level.World;
+
+ // CraftBukkit start
++import net.minecraft.world.level.WorldAccess;
+ import org.bukkit.craftbukkit.event.CraftEventFactory;
+ import org.bukkit.event.entity.CreatureSpawnEvent;
+ import org.bukkit.event.entity.ExplosionPrimeEvent;
+@@ -59,6 +63,17 @@ public class EntityCreeper extends EntityMonster {
+ super(entitytypes, world);
+ }
+
++ // Purpur start
++ @Override
++ public GroupDataEntity prepare(WorldAccess worldaccess, DifficultyDamageScaler difficultydamagescaler, EnumMobSpawn enummobspawn, @javax.annotation.Nullable GroupDataEntity groupdataentity, @javax.annotation.Nullable NBTTagCompound nbttagcompound) {
++ double chance = worldaccess.getMinecraftWorld().purpurConfig.creeperChargedChance;
++ if (chance > 0D && random.nextDouble() <= chance) {
++ setPowered(true);
++ }
++ return super.prepare(worldaccess, difficultydamagescaler, enummobspawn, groupdataentity, nbttagcompound);
++ }
++ // Purpur end
++
+ @Override
+ protected void initPathfinder() {
+ this.goalSelector.a(1, new PathfinderGoalFloat(this));
+diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+index 81b0e17a4bc5022ea757f03c2546808148d6e957..638ee71a78d9e75de6ddd7f0aec67a023bb8c06a 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+@@ -113,6 +113,11 @@ public class PurpurWorldConfig {
+ turtleEggsBreakFromMinecarts = getBoolean("blocks.turtle_egg.break-from-minecarts", turtleEggsBreakFromMinecarts);
+ }
+
++ public double creeperChargedChance = 0.0D;
++ private void creeperSettings() {
++ creeperChargedChance = getDouble("mobs.creeper.naturally-charged-chance", creeperChargedChance);
++ }
++
+ public float giantStepHeight = 2.0F;
+ public float giantJumpHeight = 1.0F;
+ public double giantMovementSpeed = 0.5D;
diff --git a/patches/Purpur/patches/server/0027-Rabbit-naturally-spawn-toast-and-killer.patch b/patches/Purpur/patches/server/0027-Rabbit-naturally-spawn-toast-and-killer.patch
new file mode 100644
index 00000000..53243413
--- /dev/null
+++ b/patches/Purpur/patches/server/0027-Rabbit-naturally-spawn-toast-and-killer.patch
@@ -0,0 +1,56 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sat, 31 Aug 2019 17:47:11 -0500
+Subject: [PATCH] Rabbit naturally spawn toast and killer
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/animal/EntityRabbit.java b/src/main/java/net/minecraft/world/entity/animal/EntityRabbit.java
+index dcbb07fb6ab799d4526a2da0614c193c7abba715..180fc927074dc683ad4d482a00dd4e04ff7923d0 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/EntityRabbit.java
++++ b/src/main/java/net/minecraft/world/entity/animal/EntityRabbit.java
+@@ -352,6 +352,10 @@ public class EntityRabbit extends EntityAnimal {
+ if (!this.hasCustomName()) {
+ this.setCustomName(new ChatMessage(SystemUtils.a("entity", EntityRabbit.bp)));
+ }
++ // Purpur start
++ } else if (i == 98) {
++ setCustomName(new ChatMessage("Toast"));
++ // Purpur end
+ }
+
+ this.datawatcher.set(EntityRabbit.bo, i);
+@@ -373,6 +377,16 @@ public class EntityRabbit extends EntityAnimal {
+ }
+
+ private int a(GeneratorAccess generatoraccess) {
++ // Purpur start
++ World world = generatoraccess.getMinecraftWorld();
++ if (world.purpurConfig.rabbitNaturalKiller > 0D && random.nextDouble() <= world.purpurConfig.rabbitNaturalKiller) {
++ return 99;
++ }
++ if (world.purpurConfig.rabbitNaturalToast > 0D && random.nextDouble() <= world.purpurConfig.rabbitNaturalToast) {
++ return 98;
++ }
++ // Purpur end
++
+ BiomeBase biomebase = generatoraccess.getBiome(this.getChunkCoordinates());
+ int i = this.random.nextInt(100);
+
+diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+index 638ee71a78d9e75de6ddd7f0aec67a023bb8c06a..6dcfbebd850d71587da5a78a3acf09d8ae413072 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+@@ -154,6 +154,13 @@ public class PurpurWorldConfig {
+ illusionerMaxHealth = getDouble("mobs.illusioner.attributes.max-health", illusionerMaxHealth);
+ }
+
++ public double rabbitNaturalToast = 0.0D;
++ public double rabbitNaturalKiller = 0.0D;
++ private void rabbitSettings() {
++ rabbitNaturalToast = getDouble("mobs.rabbit.spawn-toast-chance", rabbitNaturalToast);
++ rabbitNaturalKiller = getDouble("mobs.rabbit.spawn-killer-rabbit-chance", rabbitNaturalKiller);
++ }
++
+ public int villagerBrainTicks = 1;
+ public boolean villagerUseBrainTicksOnlyWhenLagging = true;
+ private void villagerSettings() {
diff --git a/patches/Purpur/patches/server/0028-Fix-outdated-server-showing-in-ping-before-server-fu.patch b/patches/Purpur/patches/server/0028-Fix-outdated-server-showing-in-ping-before-server-fu.patch
new file mode 100644
index 00000000..fecb9d6b
--- /dev/null
+++ b/patches/Purpur/patches/server/0028-Fix-outdated-server-showing-in-ping-before-server-fu.patch
@@ -0,0 +1,19 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Tue, 4 Jun 2019 15:50:08 -0500
+Subject: [PATCH] Fix 'outdated server' showing in ping before server fully
+ boots
+
+
+diff --git a/src/main/java/net/minecraft/server/network/PacketStatusListener.java b/src/main/java/net/minecraft/server/network/PacketStatusListener.java
+index e1997563984540e6edf5d3b697d029dc5f3c40e1..847c91305d23b99e612b9e5f988df14d3fb84a8c 100644
+--- a/src/main/java/net/minecraft/server/network/PacketStatusListener.java
++++ b/src/main/java/net/minecraft/server/network/PacketStatusListener.java
+@@ -146,6 +146,7 @@ public class PacketStatusListener implements PacketStatusInListener {
+
+ this.networkManager.sendPacket(new PacketStatusOutServerInfo(ping));
+ */
++ if (this.minecraftServer.getServerPing().getServerData() == null) return; // Purpur - do not respond to pings before we know the protocol version
+ com.destroystokyo.paper.network.StandardPaperServerListPingEventImpl.processRequest(this.minecraftServer, this.networkManager);
+ // Paper end
+ }
diff --git a/patches/Purpur/patches/server/0029-Make-Iron-Golems-Swim.patch b/patches/Purpur/patches/server/0029-Make-Iron-Golems-Swim.patch
new file mode 100644
index 00000000..13b90cad
--- /dev/null
+++ b/patches/Purpur/patches/server/0029-Make-Iron-Golems-Swim.patch
@@ -0,0 +1,42 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sat, 15 Jun 2019 03:12:15 -0500
+Subject: [PATCH] Make Iron Golems Swim
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/animal/EntityIronGolem.java b/src/main/java/net/minecraft/world/entity/animal/EntityIronGolem.java
+index 5e2b49d120b724cb5a7ae00940ded4f4875ea8a1..62cff5faafa076d05ebc59ad5c4fb020bea0509e 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/EntityIronGolem.java
++++ b/src/main/java/net/minecraft/world/entity/animal/EntityIronGolem.java
+@@ -30,6 +30,7 @@ import net.minecraft.world.entity.EntityTypes;
+ import net.minecraft.world.entity.IEntityAngerable;
+ import net.minecraft.world.entity.ai.attributes.AttributeProvider;
+ import net.minecraft.world.entity.ai.attributes.GenericAttributes;
++import net.minecraft.world.entity.ai.goal.PathfinderGoalFloat;
+ import net.minecraft.world.entity.ai.goal.PathfinderGoalLookAtPlayer;
+ import net.minecraft.world.entity.ai.goal.PathfinderGoalMeleeAttack;
+ import net.minecraft.world.entity.ai.goal.PathfinderGoalMoveTowardsTarget;
+@@ -70,6 +71,7 @@ public class EntityIronGolem extends EntityGolem implements IEntityAngerable {
+
+ @Override
+ protected void initPathfinder() {
++ if (world.purpurConfig.ironGolemCanSwim) this.goalSelector.a(0, new PathfinderGoalFloat(this)); // Purpur
+ this.goalSelector.a(1, new PathfinderGoalMeleeAttack(this, 1.0D, true));
+ this.goalSelector.a(2, new PathfinderGoalMoveTowardsTarget(this, 0.9D, 32.0F));
+ this.goalSelector.a(2, new PathfinderGoalStrollVillage(this, 0.6D, false));
+diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+index 6dcfbebd850d71587da5a78a3acf09d8ae413072..decd0e755deede2b092866a8f7f6b46520435bbe 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+@@ -154,6 +154,11 @@ public class PurpurWorldConfig {
+ illusionerMaxHealth = getDouble("mobs.illusioner.attributes.max-health", illusionerMaxHealth);
+ }
+
++ public boolean ironGolemCanSwim = false;
++ private void ironGolemSettings() {
++ ironGolemCanSwim = getBoolean("mobs.iron_golem.can-swim", ironGolemCanSwim);
++ }
++
+ public double rabbitNaturalToast = 0.0D;
+ public double rabbitNaturalKiller = 0.0D;
+ private void rabbitSettings() {
diff --git a/patches/Purpur/patches/server/0030-Dont-send-useless-entity-packets.patch b/patches/Purpur/patches/server/0030-Dont-send-useless-entity-packets.patch
new file mode 100644
index 00000000..609fff06
--- /dev/null
+++ b/patches/Purpur/patches/server/0030-Dont-send-useless-entity-packets.patch
@@ -0,0 +1,78 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sat, 6 Jul 2019 17:00:04 -0500
+Subject: [PATCH] Dont send useless entity packets
+
+
+diff --git a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutEntity.java b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutEntity.java
+index 0eed10a6c4e0c7245f219d19ed1e2e5c94364db9..2b54a5f3347f788b751892105f888663bb349629 100644
+--- a/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutEntity.java
++++ b/src/main/java/net/minecraft/network/protocol/game/PacketPlayOutEntity.java
+@@ -9,11 +9,11 @@ import net.minecraft.world.phys.Vec3D;
+ public class PacketPlayOutEntity implements Packet {
+
+ protected int a;
+- protected short b;
+- protected short c;
+- protected short d;
+- protected byte e;
+- protected byte f;
++ protected short b; public short getX() { return b; } // Purpur - OBFHELPER
++ protected short c; public short getY() { return c; } // Purpur - OBFHELPER
++ protected short d; public short getZ() { return d; } // Purpur - OBFHELPER
++ protected byte e; public byte getYaw() { return e; } // Purpur - OBFHELPER
++ protected byte f; public byte getPitch() { return f; } // Purpur - OBFHELPER
+ protected boolean g;
+ protected boolean h;
+ protected boolean i;
+diff --git a/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java b/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java
+index 67ca28463f5add7c18f7f16b918c3f36f8feeeda..53e773c14689967d5b12467bf209eefb05f7a812 100644
+--- a/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java
++++ b/src/main/java/net/minecraft/server/level/EntityTrackerEntry.java
+@@ -200,6 +200,7 @@ public class EntityTrackerEntry {
+ this.o = 0;
+ packet1 = new PacketPlayOutEntityTeleport(this.tracker);
+ }
++ if (net.pl3x.purpur.PurpurConfig.dontSendUselessEntityPackets && isUselessPacket(packet1)) packet1 = null; // Purpur
+ }
+
+ if ((this.e || this.tracker.impulse || this.tracker instanceof EntityLiving && ((EntityLiving) this.tracker).isGliding()) && this.tickCounter > 0) {
+@@ -286,6 +287,22 @@ public class EntityTrackerEntry {
+
+ }
+
++ // Purpur start
++ private boolean isUselessPacket(Packet> possibleUselessPacket) {
++ if (possibleUselessPacket instanceof PacketPlayOutEntity) {
++ PacketPlayOutEntity packet = (PacketPlayOutEntity) possibleUselessPacket;
++ if (possibleUselessPacket instanceof PacketPlayOutEntity.PacketPlayOutRelEntityMove) {
++ return packet.getX() == 0 && packet.getY() == 0 && packet.getZ() == 0;
++ } else if (possibleUselessPacket instanceof PacketPlayOutEntity.PacketPlayOutRelEntityMoveLook) {
++ return packet.getX() == 0 && packet.getY() == 0 && packet.getZ() == 0 && packet.getYaw() == 0 && packet.getPitch() == 0;
++ } else if (possibleUselessPacket instanceof PacketPlayOutEntity.PacketPlayOutEntityLook) {
++ return packet.getYaw() == 0 && packet.getPitch() == 0;
++ }
++ }
++ return false;
++ }
++ // Purpur end
++
+ public void a(EntityPlayer entityplayer) {
+ this.tracker.c(entityplayer);
+ entityplayer.c(this.tracker);
+diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java
+index c7755cea5e8337af7acc96c6a34afa547b391035..6d5c2f469e4a7a13a69ac3f7a1dadeac6aabb531 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java
+@@ -179,6 +179,11 @@ public class PurpurConfig {
+ enderChestPermissionRows = getBoolean("settings.blocks.ender_chest.use-permissions-for-rows", enderChestPermissionRows);
+ }
+
++ public static boolean dontSendUselessEntityPackets = false;
++ private static void dontSendUselessEntityPackets() {
++ dontSendUselessEntityPackets = getBoolean("settings.dont-send-useless-entity-packets", dontSendUselessEntityPackets);
++ }
++
+ public static boolean loggerSuppressInitLegacyMaterialError = false;
+ public static boolean loggerSuppressIgnoredAdvancementWarnings = false;
+ private static void loggerSettings() {
diff --git a/patches/Purpur/patches/server/0031-Tulips-change-fox-type.patch b/patches/Purpur/patches/server/0031-Tulips-change-fox-type.patch
new file mode 100644
index 00000000..0a18c286
--- /dev/null
+++ b/patches/Purpur/patches/server/0031-Tulips-change-fox-type.patch
@@ -0,0 +1,111 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sat, 13 Jul 2019 15:56:22 -0500
+Subject: [PATCH] Tulips change fox type
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/animal/EntityFox.java b/src/main/java/net/minecraft/world/entity/animal/EntityFox.java
+index b7fa24318ef43918b6b10ff4ea8acb960527296e..19a9affdaba52d8e7dc1c4c20d5c0d52829f4989 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/EntityFox.java
++++ b/src/main/java/net/minecraft/world/entity/animal/EntityFox.java
+@@ -30,6 +30,8 @@ import net.minecraft.tags.Tag;
+ import net.minecraft.tags.TagsFluid;
+ import net.minecraft.util.MathHelper;
+ import net.minecraft.world.DifficultyDamageScaler;
++import net.minecraft.world.EnumHand;
++import net.minecraft.world.EnumInteractionResult;
+ import net.minecraft.world.damagesource.DamageSource;
+ import net.minecraft.world.entity.Entity;
+ import net.minecraft.world.entity.EntityAgeable;
+@@ -107,9 +109,9 @@ public class EntityFox extends EntityAnimal {
+ private static final Predicate bv = (entity) -> {
+ return !entity.bx() && IEntitySelector.e.test(entity);
+ };
+- private PathfinderGoal bw;
+- private PathfinderGoal bx;
+- private PathfinderGoal by;
++ private PathfinderGoal bw; private PathfinderGoal attackAnimalGoal() { return bw; } // Purpur - OBFHELPER
++ private PathfinderGoal bx; private PathfinderGoal attackTurtleGoal() { return bx; } // Purpur - OBFHELPER
++ private PathfinderGoal by; private PathfinderGoal attackFishGoal() { return by; } // Purpur - OBFHELPER
+ private float bz;
+ private float bA;
+ private float bB;
+@@ -297,6 +299,11 @@ public class EntityFox extends EntityAnimal {
+ }
+
+ private void initializePathFinderGoals() {
++ // Purpur start - do not add duplicate goals
++ this.targetSelector.a(attackAnimalGoal());
++ this.targetSelector.a(attackTurtleGoal());
++ this.targetSelector.a(attackFishGoal());
++ // Purpur end
+ if (this.getFoxType() == EntityFox.Type.RED) {
+ this.targetSelector.a(4, this.bw);
+ this.targetSelector.a(4, this.bx);
+@@ -329,6 +336,7 @@ public class EntityFox extends EntityAnimal {
+
+ public void setFoxType(EntityFox.Type entityfox_type) {
+ this.datawatcher.set(EntityFox.bo, entityfox_type.b());
++ initializePathFinderGoals(); // Purpur - fix API bug not updating pathfinders on type change
+ }
+
+ private List fa() {
+@@ -646,6 +654,27 @@ public class EntityFox extends EntityAnimal {
+ return this.fa().contains(uuid);
+ }
+
++ @Override
++ public EnumInteractionResult b(EntityHuman entityhuman, EnumHand enumhand) {
++ if (world.purpurConfig.foxTypeChangesWithTulips) {
++ ItemStack itemstack = entityhuman.b(enumhand);
++ if (getFoxType() == Type.RED && itemstack.getItem() == Items.whiteTulip()) {
++ setFoxType(Type.SNOW);
++ if (!entityhuman.abilities.canInstantlyBuild) {
++ itemstack.subtract(1);
++ }
++ return EnumInteractionResult.SUCCESS;
++ } else if (getFoxType() == Type.SNOW && itemstack.getItem() == Items.orangeTulip()) {
++ setFoxType(Type.RED);
++ if (!entityhuman.abilities.canInstantlyBuild) {
++ itemstack.subtract(1);
++ }
++ return EnumInteractionResult.SUCCESS;
++ }
++ }
++ return super.b(entityhuman, enumhand);
++ }
++
+ @Override
+ protected org.bukkit.event.entity.EntityDeathEvent d(DamageSource damagesource) { // Paper
+ ItemStack itemstack = this.getEquipment(EnumItemSlot.MAINHAND).cloneItemStack(); // Paper
+diff --git a/src/main/java/net/minecraft/world/item/Items.java b/src/main/java/net/minecraft/world/item/Items.java
+index 8e9a25495d76251a86268d3059e2960a86dc46b3..993a88a5937417016821ef9d7cd58e4ee097491c 100644
+--- a/src/main/java/net/minecraft/world/item/Items.java
++++ b/src/main/java/net/minecraft/world/item/Items.java
+@@ -133,8 +133,8 @@ public class Items {
+ public static final Item bk = a(Blocks.ALLIUM, CreativeModeTab.c);
+ public static final Item bl = a(Blocks.AZURE_BLUET, CreativeModeTab.c);
+ public static final Item bm = a(Blocks.RED_TULIP, CreativeModeTab.c);
+- public static final Item bn = a(Blocks.ORANGE_TULIP, CreativeModeTab.c);
+- public static final Item bo = a(Blocks.WHITE_TULIP, CreativeModeTab.c);
++ public static final Item bn = a(Blocks.ORANGE_TULIP, CreativeModeTab.c); public static Item orangeTulip() { return bn; } // Purpur - OBFHELPER
++ public static final Item bo = a(Blocks.WHITE_TULIP, CreativeModeTab.c); public static Item whiteTulip() { return bo; } // Purpur - OBFHELPER
+ public static final Item bp = a(Blocks.PINK_TULIP, CreativeModeTab.c);
+ public static final Item bq = a(Blocks.OXEYE_DAISY, CreativeModeTab.c);
+ public static final Item br = a(Blocks.CORNFLOWER, CreativeModeTab.c);
+diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+index decd0e755deede2b092866a8f7f6b46520435bbe..081675005077c5070f7745e24fd2ee7400fe2320 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+@@ -118,6 +118,11 @@ public class PurpurWorldConfig {
+ creeperChargedChance = getDouble("mobs.creeper.naturally-charged-chance", creeperChargedChance);
+ }
+
++ public boolean foxTypeChangesWithTulips = false;
++ private void foxSettings() {
++ foxTypeChangesWithTulips = getBoolean("mobs.fox.tulips-change-type", foxTypeChangesWithTulips);
++ }
++
+ public float giantStepHeight = 2.0F;
+ public float giantJumpHeight = 1.0F;
+ public double giantMovementSpeed = 0.5D;
diff --git a/patches/Purpur/patches/server/0032-Breedable-Polar-Bears.patch b/patches/Purpur/patches/server/0032-Breedable-Polar-Bears.patch
new file mode 100644
index 00000000..4a2ecee2
--- /dev/null
+++ b/patches/Purpur/patches/server/0032-Breedable-Polar-Bears.patch
@@ -0,0 +1,117 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Thu, 26 Mar 2020 19:46:44 -0500
+Subject: [PATCH] Breedable Polar Bears
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/animal/EntityPolarBear.java b/src/main/java/net/minecraft/world/entity/animal/EntityPolarBear.java
+index 49f26d487229a732902d5bf48d305b0864e90d63..f25f5ced218555af0d62844a78842cfc7599d608 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/EntityPolarBear.java
++++ b/src/main/java/net/minecraft/world/entity/animal/EntityPolarBear.java
+@@ -34,6 +34,7 @@ import net.minecraft.world.entity.GroupDataEntity;
+ import net.minecraft.world.entity.IEntityAngerable;
+ import net.minecraft.world.entity.ai.attributes.AttributeProvider;
+ import net.minecraft.world.entity.ai.attributes.GenericAttributes;
++import net.minecraft.world.entity.ai.goal.PathfinderGoalBreed;
+ import net.minecraft.world.entity.ai.goal.PathfinderGoalFloat;
+ import net.minecraft.world.entity.ai.goal.PathfinderGoalFollowParent;
+ import net.minecraft.world.entity.ai.goal.PathfinderGoalLookAtPlayer;
+@@ -41,11 +42,13 @@ import net.minecraft.world.entity.ai.goal.PathfinderGoalMeleeAttack;
+ import net.minecraft.world.entity.ai.goal.PathfinderGoalPanic;
+ import net.minecraft.world.entity.ai.goal.PathfinderGoalRandomLookaround;
+ import net.minecraft.world.entity.ai.goal.PathfinderGoalRandomStroll;
++import net.minecraft.world.entity.ai.goal.PathfinderGoalTempt;
+ import net.minecraft.world.entity.ai.goal.target.PathfinderGoalHurtByTarget;
+ import net.minecraft.world.entity.ai.goal.target.PathfinderGoalNearestAttackableTarget;
+ import net.minecraft.world.entity.ai.goal.target.PathfinderGoalUniversalAngerReset;
+ import net.minecraft.world.entity.player.EntityHuman;
+ import net.minecraft.world.item.ItemStack;
++import net.minecraft.world.item.crafting.RecipeItemStack;
+ import net.minecraft.world.level.GeneratorAccess;
+ import net.minecraft.world.level.World;
+ import net.minecraft.world.level.WorldAccess;
+@@ -68,6 +71,30 @@ public class EntityPolarBear extends EntityAnimal implements IEntityAngerable {
+ super(entitytypes, world);
+ }
+
++ // Purpur start
++ @Override
++ public boolean mate(EntityAnimal entityanimal) {
++ if (entityanimal == this) {
++ return false;
++ } else if (this.isStanding()) {
++ return false;
++ } else if (this.getGoalTarget() != null) {
++ return false;
++ } else if (!(entityanimal instanceof EntityPolarBear)) {
++ return false;
++ } else {
++ EntityPolarBear polarbear = (EntityPolarBear) entityanimal;
++ if (polarbear.isStanding()) {
++ return false;
++ }
++ if (polarbear.getGoalTarget() != null) {
++ return false;
++ }
++ return this.isInLove() && polarbear.isInLove();
++ }
++ }
++ // Purpur end
++
+ @Override
+ public EntityAgeable createChild(WorldServer worldserver, EntityAgeable entityageable) {
+ return (EntityAgeable) EntityTypes.POLAR_BEAR.a((World) worldserver);
+@@ -75,7 +102,7 @@ public class EntityPolarBear extends EntityAnimal implements IEntityAngerable {
+
+ @Override
+ public boolean k(ItemStack itemstack) {
+- return false;
++ return world.purpurConfig.polarBearBreedableItem != null && itemstack.getItem() == world.purpurConfig.polarBearBreedableItem; // Purpur;
+ }
+
+ @Override
+@@ -84,6 +111,12 @@ public class EntityPolarBear extends EntityAnimal implements IEntityAngerable {
+ this.goalSelector.a(0, new PathfinderGoalFloat(this));
+ this.goalSelector.a(1, new EntityPolarBear.c());
+ this.goalSelector.a(1, new EntityPolarBear.d());
++ // Purpur start
++ if (world.purpurConfig.polarBearBreedableItem != null) {
++ this.goalSelector.a(2, new PathfinderGoalBreed(this, 1.0D));
++ this.goalSelector.a(3, new PathfinderGoalTempt(this, 1.0D, RecipeItemStack.a(world.purpurConfig.polarBearBreedableItem), false));
++ }
++ // Purpur end
+ this.goalSelector.a(4, new PathfinderGoalFollowParent(this, 1.25D));
+ this.goalSelector.a(5, new PathfinderGoalRandomStroll(this, 1.0D));
+ this.goalSelector.a(6, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 6.0F));
+@@ -225,10 +258,12 @@ public class EntityPolarBear extends EntityAnimal implements IEntityAngerable {
+ return flag;
+ }
+
++ public boolean isStanding() { return eM(); } // Purpur - OBFHELPER
+ public boolean eM() {
+ return (Boolean) this.datawatcher.get(EntityPolarBear.bo);
+ }
+
++ public void setStanding(boolean standing) { t(standing); } // Purpur - OBFHELPER
+ public void t(boolean flag) {
+ this.datawatcher.set(EntityPolarBear.bo, flag);
+ }
+diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+index 081675005077c5070f7745e24fd2ee7400fe2320..fa43c015976aabaae8843983976c9c939a49016f 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+@@ -164,6 +164,14 @@ public class PurpurWorldConfig {
+ ironGolemCanSwim = getBoolean("mobs.iron_golem.can-swim", ironGolemCanSwim);
+ }
+
++ public String polarBearBreedableItemString = "";
++ public Item polarBearBreedableItem = null;
++ private void polarBearSettings() {
++ polarBearBreedableItemString = getString("mobs.polar_bear.breedable-item", polarBearBreedableItemString);
++ Item item = IRegistry.ITEM.get(new MinecraftKey(polarBearBreedableItemString));
++ if (item != Items.AIR) polarBearBreedableItem = item;
++ }
++
+ public double rabbitNaturalToast = 0.0D;
+ public double rabbitNaturalKiller = 0.0D;
+ private void rabbitSettings() {
diff --git a/patches/Purpur/patches/server/0033-Chickens-can-retaliate.patch b/patches/Purpur/patches/server/0033-Chickens-can-retaliate.patch
new file mode 100644
index 00000000..3fa2e23d
--- /dev/null
+++ b/patches/Purpur/patches/server/0033-Chickens-can-retaliate.patch
@@ -0,0 +1,83 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sun, 12 Apr 2020 13:19:34 -0500
+Subject: [PATCH] Chickens can retaliate
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/animal/EntityChicken.java b/src/main/java/net/minecraft/world/entity/animal/EntityChicken.java
+index cd6fb8efb20a2d32de59d479b1dbf5ee69d5df37..600d6ebdf554dbaa8ca46a22a12d8b4e3255d987 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/EntityChicken.java
++++ b/src/main/java/net/minecraft/world/entity/animal/EntityChicken.java
+@@ -20,10 +20,12 @@ import net.minecraft.world.entity.ai.goal.PathfinderGoalBreed;
+ import net.minecraft.world.entity.ai.goal.PathfinderGoalFloat;
+ import net.minecraft.world.entity.ai.goal.PathfinderGoalFollowParent;
+ import net.minecraft.world.entity.ai.goal.PathfinderGoalLookAtPlayer;
++import net.minecraft.world.entity.ai.goal.PathfinderGoalMeleeAttack;
+ import net.minecraft.world.entity.ai.goal.PathfinderGoalPanic;
+ import net.minecraft.world.entity.ai.goal.PathfinderGoalRandomLookaround;
+ import net.minecraft.world.entity.ai.goal.PathfinderGoalRandomStrollLand;
+ import net.minecraft.world.entity.ai.goal.PathfinderGoalTempt;
++import net.minecraft.world.entity.ai.goal.target.PathfinderGoalHurtByTarget;
+ import net.minecraft.world.entity.player.EntityHuman;
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.item.Items;
+@@ -51,16 +53,33 @@ public class EntityChicken extends EntityAnimal {
+ this.a(PathType.WATER, 0.0F);
+ }
+
++ // Purpur start
++ @Override
++ protected void initAttributes() {
++ if (world.purpurConfig.chickenRetaliate) {
++ this.getAttributeInstance(GenericAttributes.ATTACK_DAMAGE).setValue(2.0D);
++ }
++ }
++ // Purpur end
++
+ @Override
+ protected void initPathfinder() {
+ this.goalSelector.a(0, new PathfinderGoalFloat(this));
+- this.goalSelector.a(1, new PathfinderGoalPanic(this, 1.4D));
++ //this.goalSelector.a(1, new PathfinderGoalPanic(this, 1.4D)); // Purpur - moved down
+ this.goalSelector.a(2, new PathfinderGoalBreed(this, 1.0D));
+ this.goalSelector.a(3, new PathfinderGoalTempt(this, 1.0D, false, EntityChicken.bv));
+ this.goalSelector.a(4, new PathfinderGoalFollowParent(this, 1.1D));
+ this.goalSelector.a(5, new PathfinderGoalRandomStrollLand(this, 1.0D));
+ this.goalSelector.a(6, new PathfinderGoalLookAtPlayer(this, EntityHuman.class, 6.0F));
+ this.goalSelector.a(7, new PathfinderGoalRandomLookaround(this));
++ // Purpur start
++ if (world.purpurConfig.chickenRetaliate) {
++ this.goalSelector.a(1, new PathfinderGoalMeleeAttack(this, 1.0D, false));
++ this.targetSelector.a(1, new PathfinderGoalHurtByTarget(this));
++ } else {
++ this.goalSelector.a(1, new PathfinderGoalPanic(this, 1.4D));
++ }
++ // Purpur end
+ }
+
+ @Override
+@@ -69,7 +88,7 @@ public class EntityChicken extends EntityAnimal {
+ }
+
+ public static AttributeProvider.Builder eK() {
+- return EntityInsentient.p().a(GenericAttributes.MAX_HEALTH, 4.0D).a(GenericAttributes.MOVEMENT_SPEED, 0.25D);
++ return EntityInsentient.p().a(GenericAttributes.MAX_HEALTH, 4.0D).a(GenericAttributes.MOVEMENT_SPEED, 0.25D).a(GenericAttributes.ATTACK_DAMAGE, 0.0D); // Purpur
+ }
+
+ @Override
+diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+index fa43c015976aabaae8843983976c9c939a49016f..78218f2d59203b8f2b286fd09b3f6bdebb47565c 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+@@ -113,6 +113,11 @@ public class PurpurWorldConfig {
+ turtleEggsBreakFromMinecarts = getBoolean("blocks.turtle_egg.break-from-minecarts", turtleEggsBreakFromMinecarts);
+ }
+
++ public boolean chickenRetaliate = false;
++ private void chickenSettings() {
++ chickenRetaliate = getBoolean("mobs.chicken.retaliate", chickenRetaliate);
++ }
++
+ public double creeperChargedChance = 0.0D;
+ private void creeperSettings() {
+ creeperChargedChance = getDouble("mobs.creeper.naturally-charged-chance", creeperChargedChance);
diff --git a/patches/Purpur/patches/server/0034-Add-option-to-set-armorstand-step-height.patch b/patches/Purpur/patches/server/0034-Add-option-to-set-armorstand-step-height.patch
new file mode 100644
index 00000000..0a3d1916
--- /dev/null
+++ b/patches/Purpur/patches/server/0034-Add-option-to-set-armorstand-step-height.patch
@@ -0,0 +1,34 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sun, 6 Oct 2019 12:46:35 -0500
+Subject: [PATCH] Add option to set armorstand step height
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java
+index c0e0750adef0ae6aff7635c84f6585f06c5fc38d..89d3734489b65245e815376edf4e2d9baea1563a 100644
+--- a/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java
++++ b/src/main/java/net/minecraft/world/entity/decoration/EntityArmorStand.java
+@@ -660,6 +660,7 @@ public class EntityArmorStand extends EntityLiving {
+
+ @Override
+ public void tick() {
++ setStepHeight(world.purpurConfig.armorstandStepHeight); // Purpur
+ // Paper start
+ if (!this.canTick) {
+ if (this.noTickPoseDirty) {
+diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+index 78218f2d59203b8f2b286fd09b3f6bdebb47565c..90f367f2f04f9bf66b7f54ffe784db16c7ca868b 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+@@ -63,6 +63,11 @@ public class PurpurWorldConfig {
+ return PurpurConfig.config.getString("world-settings." + worldName + "." + path, PurpurConfig.config.getString("world-settings.default." + path));
+ }
+
++ public float armorstandStepHeight = 0.0F;
++ private void armorstandSettings() {
++ armorstandStepHeight = (float) getDouble("gameplay-mechanics.armorstand.step-height", armorstandStepHeight);
++ }
++
+ public boolean idleTimeoutKick = true;
+ public boolean idleTimeoutTickNearbyEntities = true;
+ public boolean idleTimeoutCountAsSleeping = false;
diff --git a/patches/Purpur/patches/server/0035-Cat-spawning-options.patch b/patches/Purpur/patches/server/0035-Cat-spawning-options.patch
new file mode 100644
index 00000000..5ba975a9
--- /dev/null
+++ b/patches/Purpur/patches/server/0035-Cat-spawning-options.patch
@@ -0,0 +1,116 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Thu, 26 Dec 2019 18:52:55 -0600
+Subject: [PATCH] Cat spawning options
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java
+index 13d94ecd703b3cd0412e138532d2dd74e5bf250d..6082eed2d28f3be65daa7e7eb6f2c2a89bb28ff1 100644
+--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java
++++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlace.java
+@@ -191,6 +191,7 @@ public class VillagePlace extends RegionFileSection {
+ ((VillagePlaceSection) this.e(SectionPosition.a(blockposition).s())).a(blockposition);
+ }
+
++ public long count(Predicate predicate, BlockPosition blockposition, int i, VillagePlace.Occupancy villageplace_occupancy) { return a(predicate, blockposition, i, villageplace_occupancy); } // Purpur - OBFHELPER
+ public long a(Predicate predicate, BlockPosition blockposition, int i, VillagePlace.Occupancy villageplace_occupancy) {
+ return this.c(predicate, blockposition, i, villageplace_occupancy).count();
+ }
+diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlaceType.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlaceType.java
+index db1ddce5774754891dc8a3ea5b66951ebc3a07a8..6a45ab049a4beeeaf7b3b5acf2946767f6e1198f 100644
+--- a/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlaceType.java
++++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/VillagePlaceType.java
+@@ -53,7 +53,7 @@ public class VillagePlaceType {
+ public static final VillagePlaceType o = a("shepherd", a(Blocks.LOOM), 1, 1);
+ public static final VillagePlaceType p = a("toolsmith", a(Blocks.SMITHING_TABLE), 1, 1);
+ public static final VillagePlaceType q = a("weaponsmith", a(Blocks.GRINDSTONE), 1, 1);
+- public static final VillagePlaceType r = a("home", VillagePlaceType.z, 1, 1);
++ public static final VillagePlaceType r = a("home", VillagePlaceType.z, 1, 1); public static VillagePlaceType home() { return r; } // Purpur - OBFHELPER
+ public static final VillagePlaceType s = a("meeting", a(Blocks.BELL), 32, 6);
+ public static final VillagePlaceType t = a("beehive", a(Blocks.BEEHIVE), 0, 1);
+ public static final VillagePlaceType u = a("bee_nest", a(Blocks.BEE_NEST), 0, 1);
+@@ -92,6 +92,7 @@ public class VillagePlaceType {
+ return this.D;
+ }
+
++ public Predicate predicate() { return c(); } // Purpur - OBFHELPER
+ public Predicate c() {
+ return this.E;
+ }
+diff --git a/src/main/java/net/minecraft/world/entity/npc/MobSpawnerCat.java b/src/main/java/net/minecraft/world/entity/npc/MobSpawnerCat.java
+index 7a495cf88d723790ee3f63645cb4792052284f32..2f54c26151c049df9d071c887dd33e48df041437 100644
+--- a/src/main/java/net/minecraft/world/entity/npc/MobSpawnerCat.java
++++ b/src/main/java/net/minecraft/world/entity/npc/MobSpawnerCat.java
+@@ -34,7 +34,7 @@ public class MobSpawnerCat implements MobSpawner {
+ if (this.a > 0) {
+ return 0;
+ } else {
+- this.a = 1200;
++ this.a = worldserver.purpurConfig.catSpawnDelay; // Purpur;
+ EntityPlayer entityplayer = worldserver.q_();
+
+ if (entityplayer == null) {
+@@ -68,10 +68,12 @@ public class MobSpawnerCat implements MobSpawner {
+ }
+
+ private int a(WorldServer worldserver, BlockPosition blockposition) {
+- boolean flag = true;
+-
+- if (worldserver.y().a(VillagePlaceType.r.c(), blockposition, 48, VillagePlace.Occupancy.IS_OCCUPIED) > 4L) {
+- List list = worldserver.a(EntityCat.class, (new AxisAlignedBB(blockposition)).grow(48.0D, 8.0D, 48.0D));
++ // Purpur start
++ int range = worldserver.purpurConfig.catSpawnVillageScanRange;
++ if (range <= 0) return 0;
++ if (worldserver.getPoiStorage().count(VillagePlaceType.home().predicate(), blockposition, range, VillagePlace.Occupancy.IS_OCCUPIED) > 4L) {
++ List list = worldserver.getEntitiesInAABB(EntityCat.class, (new AxisAlignedBB(blockposition)).grow(range, 8.0D, range));
++ // Purpur end
+
+ if (list.size() < 5) {
+ return this.a(blockposition, worldserver);
+@@ -82,9 +84,11 @@ public class MobSpawnerCat implements MobSpawner {
+ }
+
+ private int b(WorldServer worldserver, BlockPosition blockposition) {
+- boolean flag = true;
+- List list = worldserver.a(EntityCat.class, (new AxisAlignedBB(blockposition)).grow(16.0D, 8.0D, 16.0D));
+-
++ // Purpur start
++ int range = worldserver.purpurConfig.catSpawnSwampHutScanRange;
++ if (range <= 0) return 0;
++ List list = worldserver.getEntitiesInAABB(EntityCat.class, (new AxisAlignedBB(blockposition)).grow(range, 8.0D, range));
++ // Purpur end
+ return list.size() < 1 ? this.a(blockposition, worldserver) : 0;
+ }
+
+diff --git a/src/main/java/net/minecraft/world/level/IEntityAccess.java b/src/main/java/net/minecraft/world/level/IEntityAccess.java
+index fd56b2f15e570f266a79c25823a3b3530a693510..18a5d11cce748695c8f03be565d2ea37a276a981 100644
+--- a/src/main/java/net/minecraft/world/level/IEntityAccess.java
++++ b/src/main/java/net/minecraft/world/level/IEntityAccess.java
+@@ -56,6 +56,7 @@ public interface IEntityAccess {
+ }
+ }
+
++ default List getEntitiesInAABB(Class extends T> oclass, AxisAlignedBB axisalignedbb) { return a(oclass, axisalignedbb); } // Purpur - OBFHELPER
+ default List a(Class extends T> oclass, AxisAlignedBB axisalignedbb) {
+ return this.a(oclass, axisalignedbb, IEntitySelector.g);
+ }
+diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+index 90f367f2f04f9bf66b7f54ffe784db16c7ca868b..9e935668147d1cd822f33c9e8d41e9541022aa8a 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+@@ -118,6 +118,15 @@ public class PurpurWorldConfig {
+ turtleEggsBreakFromMinecarts = getBoolean("blocks.turtle_egg.break-from-minecarts", turtleEggsBreakFromMinecarts);
+ }
+
++ public int catSpawnDelay = 1200;
++ public int catSpawnSwampHutScanRange = 16;
++ public int catSpawnVillageScanRange = 48;
++ private void catSettings() {
++ catSpawnDelay = getInt("mobs.cat.spawn-delay", catSpawnDelay);
++ catSpawnSwampHutScanRange = getInt("mobs.cat.scan-range-for-other-cats.swamp-hut", catSpawnSwampHutScanRange);
++ catSpawnVillageScanRange = getInt("mobs.cat.scan-range-for-other-cats.village", catSpawnVillageScanRange);
++ }
++
+ public boolean chickenRetaliate = false;
+ private void chickenSettings() {
+ chickenRetaliate = getBoolean("mobs.chicken.retaliate", chickenRetaliate);
diff --git a/patches/Purpur/patches/server/0036-MC-147659-Fix-non-black-cats-spawning-in-swamp-huts.patch b/patches/Purpur/patches/server/0036-MC-147659-Fix-non-black-cats-spawning-in-swamp-huts.patch
new file mode 100644
index 00000000..d57feb8e
--- /dev/null
+++ b/patches/Purpur/patches/server/0036-MC-147659-Fix-non-black-cats-spawning-in-swamp-huts.patch
@@ -0,0 +1,21 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Thu, 2 Jan 2020 01:23:22 -0600
+Subject: [PATCH] MC-147659 - Fix non black cats spawning in swamp huts
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/npc/MobSpawnerCat.java b/src/main/java/net/minecraft/world/entity/npc/MobSpawnerCat.java
+index 2f54c26151c049df9d071c887dd33e48df041437..5d0da07970bfe304debe244e5df39981f90161f6 100644
+--- a/src/main/java/net/minecraft/world/entity/npc/MobSpawnerCat.java
++++ b/src/main/java/net/minecraft/world/entity/npc/MobSpawnerCat.java
+@@ -98,8 +98,9 @@ public class MobSpawnerCat implements MobSpawner {
+ if (entitycat == null) {
+ return 0;
+ } else {
++ entitycat.setPositionRotation(blockposition, 0.0F, 0.0F); // Purpur
+ entitycat.prepare(worldserver, worldserver.getDamageScaler(blockposition), EnumMobSpawn.NATURAL, (GroupDataEntity) null, (NBTTagCompound) null);
+- entitycat.setPositionRotation(blockposition, 0.0F, 0.0F);
++ //entitycat.setPositionRotation(blockposition, 0.0F, 0.0F); // Purpur - move up - fixes non black cat types spawning inside swamp huts
+ worldserver.addAllEntities(entitycat);
+ return 1;
+ }
diff --git a/patches/Purpur/patches/server/0037-Cows-eat-mushrooms.patch b/patches/Purpur/patches/server/0037-Cows-eat-mushrooms.patch
new file mode 100644
index 00000000..bafcfc31
--- /dev/null
+++ b/patches/Purpur/patches/server/0037-Cows-eat-mushrooms.patch
@@ -0,0 +1,156 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sat, 4 May 2019 01:10:30 -0500
+Subject: [PATCH] Cows eat mushrooms
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
+index 611a2c34c80462826564705eb5a079bd6fdda4e1..0f11984743ba49fc6f094c8fa6c563febf0ab7d6 100644
+--- a/src/main/java/net/minecraft/world/entity/Entity.java
++++ b/src/main/java/net/minecraft/world/entity/Entity.java
+@@ -2920,6 +2920,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, ne
+ this.invulnerable = flag;
+ }
+
++ public void copyPositionRotation(Entity entity) { this.u(entity); } // Purpur - OBFHELPER
+ public void u(Entity entity) {
+ this.setPositionRotation(entity.locX(), entity.locY(), entity.locZ(), entity.yaw, entity.pitch);
+ }
+diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java
+index 032b1a519de56583990fe47a216665ce71cf93ab..9d950f5d5d6b9d686bd3bbaa12a8d933fd1e2ec2 100644
+--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java
++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java
+@@ -175,7 +175,7 @@ public abstract class EntityLiving extends Entity {
+ public int maxNoDamageTicks;
+ public final float ay;
+ public final float az;
+- public float aA;
++ public float aA; public float getRenderYawOffset() { return this.aA; } public void setRenderYawOffset(float f) { this.aA = f; } // Purpur - OBFHELPER
+ public float aB;
+ public float aC;
+ public float aD;
+diff --git a/src/main/java/net/minecraft/world/entity/animal/EntityCow.java b/src/main/java/net/minecraft/world/entity/animal/EntityCow.java
+index 1b43688ad232620410aa924cef02b54630ab1313..962dde5fcc617bc39b7d06a1e295370b9d60696c 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/EntityCow.java
++++ b/src/main/java/net/minecraft/world/entity/animal/EntityCow.java
+@@ -1,6 +1,7 @@
+ package net.minecraft.world.entity.animal;
+
+ import net.minecraft.core.BlockPosition;
++import net.minecraft.core.particles.Particles;
+ import net.minecraft.server.level.WorldServer;
+ import net.minecraft.sounds.SoundEffect;
+ import net.minecraft.sounds.SoundEffects;
+@@ -28,6 +29,7 @@ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.item.Items;
+ import net.minecraft.world.item.crafting.RecipeItemStack;
+ import net.minecraft.world.level.World;
++import net.minecraft.world.level.block.Blocks;
+ import net.minecraft.world.level.block.state.IBlockData;
+
+ // CraftBukkit start
+@@ -46,6 +48,7 @@ public class EntityCow extends EntityAnimal {
+ this.goalSelector.a(0, new PathfinderGoalFloat(this));
+ this.goalSelector.a(1, new PathfinderGoalPanic(this, 2.0D));
+ this.goalSelector.a(2, new PathfinderGoalBreed(this, 1.0D));
++ if (world.purpurConfig.cowFeedMushrooms > 0) this.goalSelector.a(3, new PathfinderGoalTempt(this, 1.25D, RecipeItemStack.a(Items.WHEAT, Blocks.RED_MUSHROOM.getItem(), Blocks.BROWN_MUSHROOM.getItem()), false)); else // Purpur
+ this.goalSelector.a(3, new PathfinderGoalTempt(this, 1.25D, RecipeItemStack.a(Items.WHEAT), false));
+ this.goalSelector.a(4, new PathfinderGoalFollowParent(this, 1.25D));
+ this.goalSelector.a(5, new PathfinderGoalRandomStrollLand(this, 1.0D));
+@@ -100,11 +103,80 @@ public class EntityCow extends EntityAnimal {
+
+ entityhuman.a(enumhand, itemstack1);
+ return EnumInteractionResult.a(this.world.isClientSide);
++ // Purpur start - feed mushroom to change to mooshroom
++ } else if (world.purpurConfig.cowFeedMushrooms > 0 && getEntityType() != EntityTypes.MOOSHROOM && isMushroom(itemstack)) {
++ return feedMushroom(entityhuman, itemstack);
++ // Purpur end
+ } else {
+ return super.b(entityhuman, enumhand);
+ }
+ }
+
++ // Purpur start - feed mushroom to change to mooshroom
++ private int redMushroomsFed = 0;
++ private int brownMushroomsFed = 0;
++
++ private boolean isMushroom(ItemStack itemstack) {
++ return itemstack.getItem() == Blocks.RED_MUSHROOM.getItem() || itemstack.getItem() == Blocks.BROWN_MUSHROOM.getItem();
++ }
++
++ private int incrementFeedCount(ItemStack itemstack) {
++ if (itemstack.getItem() == Blocks.RED_MUSHROOM.getItem()) {
++ return ++redMushroomsFed;
++ } else {
++ return ++brownMushroomsFed;
++ }
++ }
++
++ private EnumInteractionResult feedMushroom(EntityHuman entityhuman, ItemStack itemstack) {
++ world.broadcastEntityEffect(this, (byte) 18); // hearts
++ playSound(SoundEffects.ENTITY_COW_MILK, 1.0F, 1.0F);
++ if (incrementFeedCount(itemstack) < world.purpurConfig.cowFeedMushrooms) {
++ if (!entityhuman.abilities.canInstantlyBuild) {
++ itemstack.subtract(1);
++ }
++ return EnumInteractionResult.CONSUME; // require 5 mushrooms to transform (prevents mushroom duping)
++ }
++ EntityMushroomCow mooshroom = EntityTypes.MOOSHROOM.create(world);
++ if (mooshroom == null) {
++ return EnumInteractionResult.PASS;
++ }
++ if (itemstack.getItem() == Blocks.BROWN_MUSHROOM.getItem()) {
++ mooshroom.setVariant(EntityMushroomCow.Type.BROWN);
++ } else {
++ mooshroom.setVariant(EntityMushroomCow.Type.RED);
++ }
++ mooshroom.setPositionRotation(this.locX(), this.locY(), this.locZ(), this.yaw, this.pitch);
++ mooshroom.setHealth(this.getHealth());
++ mooshroom.setAge(getAge());
++ mooshroom.copyPositionRotation(this);
++ mooshroom.setRenderYawOffset(this.getRenderYawOffset());
++ mooshroom.setHeadRotation(this.getHeadRotation());
++ mooshroom.lastYaw = this.lastYaw;
++ mooshroom.lastPitch = this.lastPitch;
++ if (this.hasCustomName()) {
++ mooshroom.setCustomName(this.getCustomName());
++ }
++ if (CraftEventFactory.callEntityTransformEvent(this, mooshroom, org.bukkit.event.entity.EntityTransformEvent.TransformReason.INFECTION).isCancelled()) {
++ return EnumInteractionResult.PASS;
++ }
++ if (!new com.destroystokyo.paper.event.entity.EntityTransformedEvent(this.getBukkitEntity(), mooshroom.getBukkitEntity(), com.destroystokyo.paper.event.entity.EntityTransformedEvent.TransformedReason.INFECTED).callEvent()) {
++ return EnumInteractionResult.PASS;
++ }
++ this.world.addEntity(mooshroom);
++ this.die();
++ if (!entityhuman.abilities.canInstantlyBuild) {
++ itemstack.subtract(1);
++ }
++ for (int i = 0; i < 15; ++i) {
++ ((WorldServer) world).sendParticles(((WorldServer) world).players, null, Particles.HAPPY_VILLAGER,
++ locX() + random.nextFloat(), locY() + (random.nextFloat() * 2), locZ() + random.nextFloat(), 1,
++ random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, 0, true);
++ }
++ return EnumInteractionResult.SUCCESS;
++ }
++ // Purpur end
++
+ @Override
+ public EntityCow createChild(WorldServer worldserver, EntityAgeable entityageable) {
+ return (EntityCow) EntityTypes.COW.a((World) worldserver);
+diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+index 9e935668147d1cd822f33c9e8d41e9541022aa8a..277cc8361e8faf54b95be1e9f1467a97de14ecc4 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+@@ -132,6 +132,11 @@ public class PurpurWorldConfig {
+ chickenRetaliate = getBoolean("mobs.chicken.retaliate", chickenRetaliate);
+ }
+
++ public int cowFeedMushrooms = 0;
++ private void cowSettings() {
++ cowFeedMushrooms = getInt("mobs.cow.feed-mushrooms-for-mooshroom", cowFeedMushrooms);
++ }
++
+ public double creeperChargedChance = 0.0D;
+ private void creeperSettings() {
+ creeperChargedChance = getDouble("mobs.creeper.naturally-charged-chance", creeperChargedChance);
diff --git a/patches/Purpur/patches/server/0038-Fix-cow-rotation-when-shearing-mooshroom.patch b/patches/Purpur/patches/server/0038-Fix-cow-rotation-when-shearing-mooshroom.patch
new file mode 100644
index 00000000..6f213940
--- /dev/null
+++ b/patches/Purpur/patches/server/0038-Fix-cow-rotation-when-shearing-mooshroom.patch
@@ -0,0 +1,25 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Fri, 3 May 2019 23:53:16 -0500
+Subject: [PATCH] Fix cow rotation when shearing mooshroom
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/animal/EntityMushroomCow.java b/src/main/java/net/minecraft/world/entity/animal/EntityMushroomCow.java
+index 9face4480dcc89d9106ebe596020c1888350ef2d..d28d4d2c1eff2c130f49c2bce3c19da212dba5dc 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/EntityMushroomCow.java
++++ b/src/main/java/net/minecraft/world/entity/animal/EntityMushroomCow.java
+@@ -172,7 +172,13 @@ public class EntityMushroomCow extends EntityCow implements IShearable {
+
+ entitycow.setPositionRotation(this.locX(), this.locY(), this.locZ(), this.yaw, this.pitch);
+ entitycow.setHealth(this.getHealth());
+- entitycow.aA = this.aA;
++ // Purpur start - correctly copy rotation
++ entitycow.copyPositionRotation(this);
++ entitycow.setRenderYawOffset(this.getRenderYawOffset());
++ entitycow.setHeadRotation(this.getHeadRotation());
++ entitycow.lastYaw = this.lastYaw;
++ entitycow.lastPitch = this.lastPitch;
++ // Purpur end
+ if (this.hasCustomName()) {
+ entitycow.setCustomName(this.getCustomName());
+ entitycow.setCustomNameVisible(this.getCustomNameVisible());
diff --git a/patches/Purpur/patches/server/0039-Pigs-give-saddle-back.patch b/patches/Purpur/patches/server/0039-Pigs-give-saddle-back.patch
new file mode 100644
index 00000000..4645c333
--- /dev/null
+++ b/patches/Purpur/patches/server/0039-Pigs-give-saddle-back.patch
@@ -0,0 +1,45 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sun, 12 May 2019 01:14:46 -0500
+Subject: [PATCH] Pigs give saddle back
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/animal/EntityPig.java b/src/main/java/net/minecraft/world/entity/animal/EntityPig.java
+index 676ca381a5e111fc15f319e73504e4e60dbf0d2b..1a540e41e6161d011ca4ed30c68ae9df4567b8db 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/EntityPig.java
++++ b/src/main/java/net/minecraft/world/entity/animal/EntityPig.java
+@@ -156,6 +156,18 @@ public class EntityPig extends EntityAnimal implements ISteerable, ISaddleable {
+
+ if (!flag && this.hasSaddle() && !this.isVehicle() && !entityhuman.eq()) {
+ if (!this.world.isClientSide) {
++ // Purpur start
++ if (world.purpurConfig.pigGiveSaddleBack && entityhuman.isSneaking()) {
++ this.saddleStorage.setSaddle(false);
++ if (!entityhuman.abilities.canInstantlyBuild) {
++ ItemStack saddle = new ItemStack(Items.SADDLE);
++ if (!entityhuman.inventory.pickup(saddle)) {
++ entityhuman.drop(saddle, false);
++ }
++ }
++ return EnumInteractionResult.SUCCESS;
++ }
++ // Purpur end
+ entityhuman.startRiding(this);
+ }
+
+diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+index 277cc8361e8faf54b95be1e9f1467a97de14ecc4..5ac1aba522151c42255caf9d29c5b780218ccd32 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+@@ -188,6 +188,11 @@ public class PurpurWorldConfig {
+ ironGolemCanSwim = getBoolean("mobs.iron_golem.can-swim", ironGolemCanSwim);
+ }
+
++ public boolean pigGiveSaddleBack = false;
++ private void pigSettings() {
++ pigGiveSaddleBack = getBoolean("mobs.pig.give-saddle-back", pigGiveSaddleBack);
++ }
++
+ public String polarBearBreedableItemString = "";
+ public Item polarBearBreedableItem = null;
+ private void polarBearSettings() {
diff --git a/patches/Purpur/patches/server/0040-Snowman-drop-and-put-back-pumpkin.patch b/patches/Purpur/patches/server/0040-Snowman-drop-and-put-back-pumpkin.patch
new file mode 100644
index 00000000..42df6eae
--- /dev/null
+++ b/patches/Purpur/patches/server/0040-Snowman-drop-and-put-back-pumpkin.patch
@@ -0,0 +1,51 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Fri, 3 May 2019 23:58:44 -0500
+Subject: [PATCH] Snowman drop and put back pumpkin
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/animal/EntitySnowman.java b/src/main/java/net/minecraft/world/entity/animal/EntitySnowman.java
+index b1e2892c7c9f0e35f69332e93917593d97c304a8..44119f52a4f169ffcea53fb69393bfedfd1a62a7 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/EntitySnowman.java
++++ b/src/main/java/net/minecraft/world/entity/animal/EntitySnowman.java
+@@ -160,6 +160,14 @@ public class EntitySnowman extends EntityGolem implements IShearable, IRangedEnt
+ }
+
+ return EnumInteractionResult.a(this.world.isClientSide);
++ // Purpur start
++ } else if (world.purpurConfig.snowGolemPutPumpkinBack && !hasPumpkin() && itemstack.getItem() == Blocks.CARVED_PUMPKIN.getItem()) {
++ setHasPumpkin(true);
++ if (!entityhuman.abilities.canInstantlyBuild) {
++ itemstack.subtract(1);
++ }
++ return EnumInteractionResult.SUCCESS;
++ // Purpur end
+ } else {
+ return EnumInteractionResult.PASS;
+ }
+@@ -170,6 +178,7 @@ public class EntitySnowman extends EntityGolem implements IShearable, IRangedEnt
+ this.world.playSound((EntityHuman) null, (Entity) this, SoundEffects.ENTITY_SNOW_GOLEM_SHEAR, soundcategory, 1.0F, 1.0F);
+ if (!this.world.s_()) {
+ this.setHasPumpkin(false);
++ if (world.purpurConfig.snowGolemDropsPumpkin) // Purpur
+ this.a(new ItemStack(Items.dj), 1.7F);
+ }
+
+diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+index 5ac1aba522151c42255caf9d29c5b780218ccd32..fa01ef3c0cc723acaaf348066cddf91f1deb3c72 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+@@ -208,6 +208,13 @@ public class PurpurWorldConfig {
+ rabbitNaturalKiller = getDouble("mobs.rabbit.spawn-killer-rabbit-chance", rabbitNaturalKiller);
+ }
+
++ public boolean snowGolemDropsPumpkin = true;
++ public boolean snowGolemPutPumpkinBack = false;
++ private void snowGolemSettings() {
++ snowGolemDropsPumpkin = getBoolean("mobs.snow_golem.drop-pumpkin-when-sheared", snowGolemDropsPumpkin);
++ snowGolemPutPumpkinBack = getBoolean("mobs.snow_golem.pumpkin-can-be-added-back", snowGolemPutPumpkinBack);
++ }
++
+ public int villagerBrainTicks = 1;
+ public boolean villagerUseBrainTicksOnlyWhenLagging = true;
+ private void villagerSettings() {
diff --git a/patches/Purpur/patches/server/0041-Ender-dragon-always-drop-full-exp.patch b/patches/Purpur/patches/server/0041-Ender-dragon-always-drop-full-exp.patch
new file mode 100644
index 00000000..9dd6fd48
--- /dev/null
+++ b/patches/Purpur/patches/server/0041-Ender-dragon-always-drop-full-exp.patch
@@ -0,0 +1,35 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sat, 24 Aug 2019 14:42:54 -0500
+Subject: [PATCH] Ender dragon always drop full exp
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderDragon.java
+index 07160de8725787551df327c0790b2d6e0876524f..3fff101637708a1a12f9a457bd3512ae94a8f884 100644
+--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderDragon.java
++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EntityEnderDragon.java
+@@ -613,7 +613,7 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster {
+ boolean flag = this.world.getGameRules().getBoolean(GameRules.DO_MOB_LOOT);
+ short short0 = 500;
+
+- if (this.bF != null && !this.bF.isPreviouslyKilled()) {
++ if (getEnderDragonBattle() != null && (world.purpurConfig.enderDragonAlwaysDropsFullExp || !getEnderDragonBattle().isPreviouslyKilled())) { // Purpur
+ short0 = 12000;
+ }
+
+diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+index fa01ef3c0cc723acaaf348066cddf91f1deb3c72..bab91f0416f31a8273593bb7725658674eb41621 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+@@ -142,6 +142,11 @@ public class PurpurWorldConfig {
+ creeperChargedChance = getDouble("mobs.creeper.naturally-charged-chance", creeperChargedChance);
+ }
+
++ public boolean enderDragonAlwaysDropsFullExp = false;
++ private void enderDragonSettings() {
++ enderDragonAlwaysDropsFullExp = getBoolean("mobs.ender_dragon.always-drop-full-exp", enderDragonAlwaysDropsFullExp);
++ }
++
+ public boolean foxTypeChangesWithTulips = false;
+ private void foxSettings() {
+ foxTypeChangesWithTulips = getBoolean("mobs.fox.tulips-change-type", foxTypeChangesWithTulips);
diff --git a/patches/Purpur/patches/server/0042-Signs-editable-on-right-click.patch b/patches/Purpur/patches/server/0042-Signs-editable-on-right-click.patch
new file mode 100644
index 00000000..706c4e9a
--- /dev/null
+++ b/patches/Purpur/patches/server/0042-Signs-editable-on-right-click.patch
@@ -0,0 +1,64 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Fri, 24 May 2019 02:39:25 -0500
+Subject: [PATCH] Signs editable on right click
+
+
+diff --git a/src/main/java/net/minecraft/world/level/block/BlockSign.java b/src/main/java/net/minecraft/world/level/block/BlockSign.java
+index 6b461080439dd9ce5b8d34b79d446558bbd5c1f1..19f4403b9f1142c71e7b2c7c0fd5a78027d4ffe4 100644
+--- a/src/main/java/net/minecraft/world/level/block/BlockSign.java
++++ b/src/main/java/net/minecraft/world/level/block/BlockSign.java
+@@ -6,6 +6,7 @@ import net.minecraft.world.EnumHand;
+ import net.minecraft.world.EnumInteractionResult;
+ import net.minecraft.world.entity.player.EntityHuman;
+ import net.minecraft.world.item.ItemDye;
++import net.minecraft.world.item.ItemSign;
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.level.GeneratorAccess;
+ import net.minecraft.world.level.IBlockAccess;
+@@ -80,6 +81,17 @@ public abstract class BlockSign extends BlockTileEntity implements IBlockWaterlo
+ }
+ }
+
++ // Purpur start - right click to open sign editor
++ if (world.purpurConfig.signRightClickEdit && itemstack.getItem() instanceof ItemSign &&
++ !entityhuman.isSneaking() && entityhuman.abilities.mayBuild &&
++ entityhuman.getBukkitEntity().hasPermission("purpur.sign.edit")) {
++ tileentitysign.isEditable = true;
++ tileentitysign.a(entityhuman);
++ entityhuman.openSign(tileentitysign);
++ return EnumInteractionResult.SUCCESS;
++ }
++ // Purpur end
++
+ return tileentitysign.b(entityhuman) ? EnumInteractionResult.SUCCESS : EnumInteractionResult.PASS;
+ } else {
+ return EnumInteractionResult.PASS;
+diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntitySign.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntitySign.java
+index 7f78f388584899b13ff983f0dc37c679bfb1507e..96d0524482281f8570464962c0fd5319199440d7 100644
+--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntitySign.java
++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntitySign.java
+@@ -132,6 +132,7 @@ public class TileEntitySign extends TileEntity implements ICommandListener { //
+ return this.isEditable;
+ }
+
++ public void setEditor(EntityHuman entityhuman) { a(entityhuman); } // Purpur - OBFHELPER
+ public void a(EntityHuman entityhuman) {
+ // Paper start
+ //this.c = entityhuman;
+diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+index bab91f0416f31a8273593bb7725658674eb41621..ea95a4116371d58eb36be51093af7f0fa28e3dfd 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+@@ -109,6 +109,11 @@ public class PurpurWorldConfig {
+ });
+ }
+
++ public boolean signRightClickEdit = false;
++ private void signSettings() {
++ signRightClickEdit = getBoolean("blocks.sign.right-click-edit", signRightClickEdit);
++ }
++
+ public boolean turtleEggsBreakFromExpOrbs = true;
+ public boolean turtleEggsBreakFromItems = true;
+ public boolean turtleEggsBreakFromMinecarts = true;
diff --git a/patches/Purpur/patches/server/0043-Signs-allow-color-codes.patch b/patches/Purpur/patches/server/0043-Signs-allow-color-codes.patch
new file mode 100644
index 00000000..21db1113
--- /dev/null
+++ b/patches/Purpur/patches/server/0043-Signs-allow-color-codes.patch
@@ -0,0 +1,84 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Thu, 6 Jun 2019 17:40:30 -0500
+Subject: [PATCH] Signs allow color codes
+
+
+diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java
+index 79ff69f9e2dc92ffb4880cf1e059cd1d6a7bdc8a..1ce550ecae370e9e5247d653b259436592b072ab 100644
+--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java
++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java
+@@ -1604,6 +1604,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
+
+ @Override
+ public void openSign(TileEntitySign tileentitysign) {
++ if (world.purpurConfig.signAllowColors) this.playerConnection.sendPacket(tileentitysign.getTranslatedUpdatePacket()); // Purpur
+ tileentitysign.a((EntityHuman) this);
+ this.playerConnection.sendPacket(new PacketPlayOutOpenSignEditor(tileentitysign.getPosition()));
+ }
+diff --git a/src/main/java/net/minecraft/server/network/PlayerConnection.java b/src/main/java/net/minecraft/server/network/PlayerConnection.java
+index 8856ee8e0e7a3efda7921c0c8df9a2eb4213b1ce..e8a9c8a7fc4089e48e09afc1638cf1ccde7b0fab 100644
+--- a/src/main/java/net/minecraft/server/network/PlayerConnection.java
++++ b/src/main/java/net/minecraft/server/network/PlayerConnection.java
+@@ -3087,6 +3087,15 @@ public class PlayerConnection implements PacketListenerPlayIn {
+ }
+ }
+ // Paper end
++ // Purpur start
++ if (worldserver.purpurConfig.signAllowColors) {
++ final org.bukkit.entity.Player bukkitPlayer = player.getBukkitEntity();
++ if (bukkitPlayer.hasPermission("purpur.sign.color")) currentLine = currentLine.replaceAll("(?i)&([0-9a-fr])", "\u00a7$1");
++ if (bukkitPlayer.hasPermission("purpur.sign.style")) currentLine = currentLine.replaceAll("(?i)&([l-or])", "\u00a7$1");
++ if (bukkitPlayer.hasPermission("purpur.sign.magic")) currentLine = currentLine.replaceAll("(?i)&([kr])", "\u00a7$1");
++ lines.add(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(currentLine));
++ } else
++ // Purpur end
+ lines.add(net.kyori.adventure.text.Component.text(SharedConstants.filterAllowedChatCharacters(currentLine))); // Paper - Replaced with anvil color stripping method to stop exploits that allow colored signs to be created.
+ }
+ SignChangeEvent event = new SignChangeEvent(org.bukkit.craftbukkit.block.CraftBlock.at(worldserver, blockposition), this.getPlayer(), lines);
+diff --git a/src/main/java/net/minecraft/world/level/block/entity/TileEntitySign.java b/src/main/java/net/minecraft/world/level/block/entity/TileEntitySign.java
+index 96d0524482281f8570464962c0fd5319199440d7..a87a2fa507dc3bd6d9979db5868e7ef6ea1eddbd 100644
+--- a/src/main/java/net/minecraft/world/level/block/entity/TileEntitySign.java
++++ b/src/main/java/net/minecraft/world/level/block/entity/TileEntitySign.java
+@@ -1,6 +1,7 @@
+ package net.minecraft.world.level.block.entity;
+
+ import com.mojang.brigadier.exceptions.CommandSyntaxException;
++import io.papermc.paper.adventure.PaperAdventure; // Purpur
+ import javax.annotation.Nullable;
+ import net.minecraft.commands.CommandListenerWrapper;
+ import net.minecraft.commands.ICommandListener;
+@@ -112,6 +113,18 @@ public class TileEntitySign extends TileEntity implements ICommandListener { //
+ this.g[i] = null;
+ }
+
++ // Purpur start
++ public PacketPlayOutTileEntityData getTranslatedUpdatePacket() {
++ NBTTagCompound nbt = save(new NBTTagCompound());
++ for (int i = 0; i < 4; ++i) {
++ String line = PaperAdventure.LEGACY_AMPERSAND.serialize(PaperAdventure.asAdventure(lines[i]));
++ nbt.setString("Text" + (i + 1), net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson().serialize(net.kyori.adventure.text.Component.text(line)));
++ }
++ nbt.setString("PurpurEditor", "true");
++ return new PacketPlayOutTileEntityData(position, 9, nbt);
++ }
++ // Purpur end
++
+ @Nullable
+ @Override
+ public PacketPlayOutTileEntityData getUpdatePacket() {
+diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+index ea95a4116371d58eb36be51093af7f0fa28e3dfd..b67f87f38d0ff99ff62d2103ecc737317a435102 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+@@ -109,8 +109,10 @@ public class PurpurWorldConfig {
+ });
+ }
+
++ public boolean signAllowColors = false;
+ public boolean signRightClickEdit = false;
+ private void signSettings() {
++ signAllowColors = getBoolean("blocks.sign.allow-colors", signAllowColors);
+ signRightClickEdit = getBoolean("blocks.sign.right-click-edit", signRightClickEdit);
+ }
+
diff --git a/patches/Purpur/patches/server/0044-Allow-soil-to-moisten-from-water-directly-under-it.patch b/patches/Purpur/patches/server/0044-Allow-soil-to-moisten-from-water-directly-under-it.patch
new file mode 100644
index 00000000..73f44531
--- /dev/null
+++ b/patches/Purpur/patches/server/0044-Allow-soil-to-moisten-from-water-directly-under-it.patch
@@ -0,0 +1,35 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Fri, 21 Jun 2019 14:37:10 -0500
+Subject: [PATCH] Allow soil to moisten from water directly under it
+
+
+diff --git a/src/main/java/net/minecraft/world/level/block/BlockSoil.java b/src/main/java/net/minecraft/world/level/block/BlockSoil.java
+index ac830ea21e639652908fe82a253853b26b412e4d..50cf0f3a67a32fe221afaee095189de87135f355 100644
+--- a/src/main/java/net/minecraft/world/level/block/BlockSoil.java
++++ b/src/main/java/net/minecraft/world/level/block/BlockSoil.java
+@@ -159,7 +159,7 @@ public class BlockSoil extends Block {
+ }
+ }
+
+- return false;
++ return ((WorldServer) iworldreader).purpurConfig.farmlandGetsMoistFromBelow && iworldreader.getFluid(blockposition.shift(EnumDirection.DOWN)).isTagged(TagsFluid.WATER); // Purpur
+ // Tuinity end - remove abstract block iteration
+ }
+
+diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+index b67f87f38d0ff99ff62d2103ecc737317a435102..2e4a928a3e88fb3d961d9530695cd7154321c79b 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+@@ -109,6 +109,11 @@ public class PurpurWorldConfig {
+ });
+ }
+
++ public boolean farmlandGetsMoistFromBelow = false;
++ private void farmlandSettings() {
++ farmlandGetsMoistFromBelow = getBoolean("blocks.farmland.gets-moist-from-below", farmlandGetsMoistFromBelow);
++ }
++
+ public boolean signAllowColors = false;
+ public boolean signRightClickEdit = false;
+ private void signSettings() {
diff --git a/patches/Purpur/patches/server/0045-Minecart-settings-and-WASD-controls.patch b/patches/Purpur/patches/server/0045-Minecart-settings-and-WASD-controls.patch
new file mode 100644
index 00000000..1d04085b
--- /dev/null
+++ b/patches/Purpur/patches/server/0045-Minecart-settings-and-WASD-controls.patch
@@ -0,0 +1,275 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sat, 29 Jun 2019 02:32:40 -0500
+Subject: [PATCH] Minecart settings and WASD controls
+
+
+diff --git a/src/main/java/net/minecraft/core/BlockPosition.java b/src/main/java/net/minecraft/core/BlockPosition.java
+index 6a6381e85fef2ae2b9b5e6dff0b7917b92fa01e5..17d63cd4ca152adc66ffe9ffd3227a0770738a29 100644
+--- a/src/main/java/net/minecraft/core/BlockPosition.java
++++ b/src/main/java/net/minecraft/core/BlockPosition.java
+@@ -42,6 +42,12 @@ public class BlockPosition extends BaseBlockPosition {
+ private static final int m = 38;
+ // Paper end
+
++ // Purpur start
++ public BlockPosition(net.minecraft.world.entity.Entity entity) {
++ super(entity.locX(), entity.locY(), entity.locZ());
++ }
++ // Purpur end
++
+ public BlockPosition(int i, int j, int k) {
+ super(i, j, k);
+ }
+diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java
+index 1ce550ecae370e9e5247d653b259436592b072ab..9746ade740ab36e68f24e0ee09d24e23f9e6a68f 100644
+--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java
++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java
+@@ -1142,6 +1142,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
+ if (this.isInvulnerable(damagesource)) {
+ return false;
+ } else {
++ if (damagesource == DamageSource.FALL && getRootVehicle() instanceof net.minecraft.world.entity.vehicle.EntityMinecartAbstract && world.purpurConfig.minecartControllable && !world.purpurConfig.minecartControllableFallDamage) return false; // Purpur
+ boolean flag = this.server.j() && this.canPvP() && "fall".equals(damagesource.translationIndex);
+
+ if (!flag && isSpawnInvulnerable() && damagesource != DamageSource.OUT_OF_WORLD) { // Purpur
+diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java
+index 9d950f5d5d6b9d686bd3bbaa12a8d933fd1e2ec2..f940a5460fe443bb97f23f29cff12827adb2eca4 100644
+--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java
++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java
+@@ -193,9 +193,9 @@ public abstract class EntityLiving extends Entity {
+ protected int aO;protected int getKillCount() { return this.aO; } // Paper - OBFHELPER
+ public float lastDamage;
+ public boolean jumping; // Paper protected -> public
+- public float aR;
+- public float aS;
+- public float aT;
++ public float aR; public float getStrafe() { return aR; } public void setStrafe(float strafe) { aR = strafe; } // Purpur - OBFHELPER
++ public float aS; public float getVertical() { return aS; } public void setVertical(float vertical) { aS = vertical; } // Purpur - OBFHELPER
++ public float aT; public float getForward() { return aT; } public void setForward(float forward) { aT = forward; } // Purpur - OBFHELPER
+ protected int aU;
+ protected double aV;
+ protected double aW;
+diff --git a/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartAbstract.java b/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartAbstract.java
+index 75a88ab5d5b0fdb98ea8d61bb6b82049b21101f3..7b49544210d087f5006a83c2a0d5c47c785c567f 100644
+--- a/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartAbstract.java
++++ b/src/main/java/net/minecraft/world/entity/vehicle/EntityMinecartAbstract.java
+@@ -105,11 +105,13 @@ public abstract class EntityMinecartAbstract extends Entity {
+ private double flyingY = 0.949999988079071D; // Paper - restore vanilla precision
+ private double flyingZ = 0.949999988079071D; // Paper - restore vanilla precision
+ public double maxSpeed = 0.4D;
++ public double storedMaxSpeed; // Purpur
+ // CraftBukkit end
+
+ protected EntityMinecartAbstract(EntityTypes> entitytypes, World world) {
+ super(entitytypes, world);
+ this.i = true;
++ if (world != null) maxSpeed = storedMaxSpeed = world.purpurConfig.minecartMaxSpeed; // Purpur
+ }
+
+ protected EntityMinecartAbstract(EntityTypes> entitytypes, World world, double d0, double d1, double d2) {
+@@ -315,6 +317,12 @@ public abstract class EntityMinecartAbstract extends Entity {
+
+ @Override
+ public void tick() {
++ // Purpur start
++ if (storedMaxSpeed != world.purpurConfig.minecartMaxSpeed) {
++ maxSpeed = storedMaxSpeed = world.purpurConfig.minecartMaxSpeed;
++ }
++ // Purpur end
++
+ // CraftBukkit start
+ double prevX = this.locX();
+ double prevY = this.locY();
+@@ -484,16 +492,62 @@ public abstract class EntityMinecartAbstract extends Entity {
+
+ public void a(int i, int j, int k, boolean flag) {}
+
++ // Purpur start
++ private Double lastSpeed;
++
++ public double getControllableSpeed() {
++ BlockPosition position = new BlockPosition(this);
++ Block block = world.getType(position).getBlock();
++ if (!block.getMaterial().isSolid()) {
++ block = world.getType(position.shift(EnumDirection.DOWN)).getBlock();
++ }
++ Double speed = world.purpurConfig.minecartControllableBlockSpeeds.get(block);
++ if (!block.getMaterial().isSolid()) {
++ speed = lastSpeed;
++ } else if (speed == null) {
++ speed = world.purpurConfig.minecartControllableBaseSpeed;
++ }
++ return lastSpeed = speed;
++ }
++ // Purpur end
++
+ protected void h() {
+ double d0 = this.getMaxSpeed();
+ Vec3D vec3d = this.getMot();
+
+ this.setMot(MathHelper.a(vec3d.x, -d0, d0), vec3d.y, MathHelper.a(vec3d.z, -d0, d0));
++
++ // Purpur start
++ if (world.purpurConfig.minecartControllable && !isInWater() && !isInLava() && !passengers.isEmpty()) {
++ Entity passenger = passengers.get(0);
++ if (passenger instanceof EntityHuman) {
++ EntityHuman entityhuman = (EntityHuman) passenger;
++ if (entityhuman.jumping && this.onGround) {
++ setMot(new Vec3D(getMot().x, world.purpurConfig.minecartControllableHopBoost, getMot().z));
++ }
++ if (entityhuman.getForward() != 0.0F) {
++ Vector velocity = entityhuman.getBukkitEntity().getEyeLocation().getDirection().normalize().multiply(getControllableSpeed());
++ if (entityhuman.getForward() < 0.0) {
++ velocity.multiply(-0.5);
++ }
++ setMot(new Vec3D(velocity.getX(), getMot().y, velocity.getZ()));
++ }
++ this.yaw = passenger.yaw - 90;
++ setStepHeight(world.purpurConfig.minecartControllableStepHeight);
++ } else {
++ setStepHeight(0.0F);
++ }
++ } else {
++ setStepHeight(0.0F);
++ }
++ // Purpur end
++
+ if (this.onGround) {
+ // CraftBukkit start - replace magic numbers with our variables
+ this.setMot(new Vec3D(this.getMot().x * this.derailedX, this.getMot().y * this.derailedY, this.getMot().z * this.derailedZ));
+ // CraftBukkit end
+ }
++ else if (world.purpurConfig.minecartControllable) setMot(new Vec3D(getMot().x * derailedX, getMot().y, getMot().z * derailedZ)); // Purpur
+
+ this.move(EnumMoveType.SELF, this.getMot());
+ if (!this.onGround) {
+diff --git a/src/main/java/net/minecraft/world/item/ItemMinecart.java b/src/main/java/net/minecraft/world/item/ItemMinecart.java
+index 527f3ed664854cdd938c34f00a064bc2f77148cc..1a1de9491a50e9e746e714fcb35633c22674f042 100644
+--- a/src/main/java/net/minecraft/world/item/ItemMinecart.java
++++ b/src/main/java/net/minecraft/world/item/ItemMinecart.java
+@@ -121,8 +121,10 @@ public class ItemMinecart extends Item {
+ IBlockData iblockdata = world.getType(blockposition);
+
+ if (!iblockdata.a((Tag) TagsBlock.RAILS)) {
+- return EnumInteractionResult.FAIL;
+- } else {
++ // Purpur start - place minecarts anywhere
++ if (!world.purpurConfig.minecartPlaceAnywhere) return EnumInteractionResult.FAIL;
++ if (iblockdata.getMaterial().isSolid()) blockposition = blockposition.shift(itemactioncontext.getClickedFace());
++ } //else { // Purpur end - place minecarts anywhere
+ ItemStack itemstack = itemactioncontext.getItemStack();
+
+ if (!world.isClientSide) {
+@@ -149,6 +151,6 @@ public class ItemMinecart extends Item {
+
+ itemstack.subtract(1);
+ return EnumInteractionResult.a(world.isClientSide);
+- }
++ //} // Purpur - place minecarts anywhere
+ }
+ }
+diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java
+index 7279893d599351785652279c8827fe0efbd72f12..cfdc602947548970b3fde00dd9fddf4e82c28841 100644
+--- a/src/main/java/net/minecraft/world/level/block/Block.java
++++ b/src/main/java/net/minecraft/world/level/block/Block.java
+@@ -83,6 +83,7 @@ public class Block extends BlockBase implements IMaterial {
+ return timing;
+ }
+ // Paper end
++ public net.minecraft.world.level.material.Material getMaterial() { return material; } // Purpur - OBFHELPER
+ @Nullable
+ private String name;
+ @Nullable
+diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+index 2e4a928a3e88fb3d961d9530695cd7154321c79b..ee4b36f1aad78bcd7e9cc45acb4ca5b957d0d5c5 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+@@ -1,13 +1,18 @@
+ package net.pl3x.purpur;
+
+ import net.minecraft.core.IRegistry;
++import net.minecraft.world.level.block.Block;
++import net.minecraft.world.level.block.Blocks;
+ import net.minecraft.world.item.Item;
+ import net.minecraft.world.item.Items;
+ import net.minecraft.resources.MinecraftKey;
+ import org.bukkit.configuration.ConfigurationSection;
+
+ import java.util.ArrayList;
++import java.util.HashMap;
+ import java.util.List;
++import java.util.Map;
++
+ import static net.pl3x.purpur.PurpurConfig.log;
+
+ public class PurpurWorldConfig {
+@@ -68,6 +73,68 @@ public class PurpurWorldConfig {
+ armorstandStepHeight = (float) getDouble("gameplay-mechanics.armorstand.step-height", armorstandStepHeight);
+ }
+
++ public double minecartMaxSpeed = 0.4D;
++ public boolean minecartPlaceAnywhere = false;
++ public boolean minecartControllable = false;
++ public float minecartControllableStepHeight = 1.0F;
++ public double minecartControllableHopBoost = 0.5D;
++ public boolean minecartControllableFallDamage = true;
++ public double minecartControllableBaseSpeed = 0.1D;
++ public Map minecartControllableBlockSpeeds = new HashMap<>();
++ private void minecartSettings() {
++ if (PurpurConfig.version < 12) {
++ boolean oldBool = getBoolean("gameplay-mechanics.controllable-minecarts.place-anywhere", minecartPlaceAnywhere);
++ set("gameplay-mechanics.controllable-minecarts.place-anywhere", null);
++ set("gameplay-mechanics.minecart.place-anywhere", oldBool);
++ oldBool = getBoolean("gameplay-mechanics.controllable-minecarts.enabled", minecartControllable);
++ set("gameplay-mechanics.controllable-minecarts.enabled", null);
++ set("gameplay-mechanics.minecart.controllable.enabled", oldBool);
++ double oldDouble = getDouble("gameplay-mechanics.controllable-minecarts.step-height", minecartControllableStepHeight);
++ set("gameplay-mechanics.controllable-minecarts.step-height", null);
++ set("gameplay-mechanics.minecart.controllable.step-height", oldDouble);
++ oldDouble = getDouble("gameplay-mechanics.controllable-minecarts.hop-boost", minecartControllableHopBoost);
++ set("gameplay-mechanics.controllable-minecarts.hop-boost", null);
++ set("gameplay-mechanics.minecart.controllable.hop-boost", oldDouble);
++ oldBool = getBoolean("gameplay-mechanics.controllable-minecarts.fall-damage", minecartControllableFallDamage);
++ set("gameplay-mechanics.controllable-minecarts.fall-damage", null);
++ set("gameplay-mechanics.minecart.controllable.fall-damage", oldBool);
++ oldDouble = getDouble("gameplay-mechanics.controllable-minecarts.base-speed", minecartControllableBaseSpeed);
++ set("gameplay-mechanics.controllable-minecarts.base-speed", null);
++ set("gameplay-mechanics.minecart.controllable.base-speed", oldDouble);
++ ConfigurationSection section = getConfigurationSection("gameplay-mechanics.controllable-minecarts.block-speed");
++ if (section != null) {
++ for (String key : section.getKeys(false)) {
++ if ("grass-block".equals(key)) key = "grass_block"; // oopsie
++ oldDouble = section.getDouble(key, minecartControllableBaseSpeed);
++ set("gameplay-mechanics.controllable-minecarts.block-speed." + key, null);
++ set("gameplay-mechanics.minecart.controllable.block-speed." + key, oldDouble);
++ }
++ set("gameplay-mechanics.controllable-minecarts.block-speed", null);
++ }
++ set("gameplay-mechanics.controllable-minecarts", null);
++ }
++
++ minecartMaxSpeed = getDouble("gameplay-mechanics.minecart.max-speed", minecartMaxSpeed);
++ minecartPlaceAnywhere = getBoolean("gameplay-mechanics.minecart.place-anywhere", minecartPlaceAnywhere);
++ minecartControllable = getBoolean("gameplay-mechanics.minecart.controllable.enabled", minecartControllable);
++ minecartControllableStepHeight = (float) getDouble("gameplay-mechanics.minecart.controllable.step-height", minecartControllableStepHeight);
++ minecartControllableHopBoost = getDouble("gameplay-mechanics.minecart.controllable.hop-boost", minecartControllableHopBoost);
++ minecartControllableFallDamage = getBoolean("gameplay-mechanics.minecart.controllable.fall-damage", minecartControllableFallDamage);
++ minecartControllableBaseSpeed = getDouble("gameplay-mechanics.minecart.controllable.base-speed", minecartControllableBaseSpeed);
++ ConfigurationSection section = getConfigurationSection("gameplay-mechanics.minecart.controllable.block-speed");
++ if (section != null) {
++ for (String key : section.getKeys(false)) {
++ Block block = IRegistry.BLOCK.get(new MinecraftKey(key));
++ if (block != Blocks.AIR) {
++ minecartControllableBlockSpeeds.put(block, section.getDouble(key, minecartControllableBaseSpeed));
++ }
++ }
++ } else {
++ set("gameplay-mechanics.minecart.controllable.block-speed.grass_block", 0.3D);
++ set("gameplay-mechanics.minecart.controllable.block-speed.stone", 0.5D);
++ }
++ }
++
+ public boolean idleTimeoutKick = true;
+ public boolean idleTimeoutTickNearbyEntities = true;
+ public boolean idleTimeoutCountAsSleeping = false;
diff --git a/patches/Purpur/patches/server/0046-Disable-loot-drops-on-death-by-cramming.patch b/patches/Purpur/patches/server/0046-Disable-loot-drops-on-death-by-cramming.patch
new file mode 100644
index 00000000..0645ab30
--- /dev/null
+++ b/patches/Purpur/patches/server/0046-Disable-loot-drops-on-death-by-cramming.patch
@@ -0,0 +1,37 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Wed, 3 Jul 2019 23:58:31 -0500
+Subject: [PATCH] Disable loot drops on death by cramming
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/EntityLiving.java b/src/main/java/net/minecraft/world/entity/EntityLiving.java
+index f940a5460fe443bb97f23f29cff12827adb2eca4..871286b2f26f49aa49611503053cb6b1f0a064dc 100644
+--- a/src/main/java/net/minecraft/world/entity/EntityLiving.java
++++ b/src/main/java/net/minecraft/world/entity/EntityLiving.java
+@@ -1595,8 +1595,10 @@ public abstract class EntityLiving extends Entity {
+
+ this.dropInventory(); // CraftBukkit - from below
+ if (this.cW() && this.world.getGameRules().getBoolean(GameRules.DO_MOB_LOOT)) {
++ if (!(damagesource == DamageSource.CRAMMING && world.purpurConfig.disableDropsOnCrammingDeath)) { // Purpur
+ this.a(damagesource, flag);
+ this.dropDeathLoot(damagesource, i, flag);
++ } // Purpur
+ }
+ // CraftBukkit start - Call death event
+ org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, this.drops); // Paper
+diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+index ee4b36f1aad78bcd7e9cc45acb4ca5b957d0d5c5..e2f49196642a325ac24f9245d5576547f20a044f 100644
+--- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
++++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java
+@@ -146,6 +146,11 @@ public class PurpurWorldConfig {
+ idleTimeoutUpdateTabList = getBoolean("gameplay-mechanics.player.idle-timeout.update-tab-list", idleTimeoutUpdateTabList);
+ }
+
++ public boolean disableDropsOnCrammingDeath = false;
++ private void miscGameplayMechanicsSettings() {
++ disableDropsOnCrammingDeath = getBoolean("gameplay-mechanics.disable-drops-on-cramming-death", disableDropsOnCrammingDeath);
++ }
++
+ public int playerSpawnInvulnerableTicks = 60;
+ public boolean playerInvulnerableWhileAcceptingResourcePack = false;
+ private void playerInvulnerabilities() {
diff --git a/patches/Purpur/patches/server/0047-Players-should-not-cram-to-death.patch b/patches/Purpur/patches/server/0047-Players-should-not-cram-to-death.patch
new file mode 100644
index 00000000..70350e73
--- /dev/null
+++ b/patches/Purpur/patches/server/0047-Players-should-not-cram-to-death.patch
@@ -0,0 +1,19 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath
+Date: Sun, 21 Jul 2019 18:01:46 -0500
+Subject: [PATCH] Players should not cram to death
+
+
+diff --git a/src/main/java/net/minecraft/server/level/EntityPlayer.java b/src/main/java/net/minecraft/server/level/EntityPlayer.java
+index 9746ade740ab36e68f24e0ee09d24e23f9e6a68f..4ce97052092c4b5f0fa59de7442654f7025febb6 100644
+--- a/src/main/java/net/minecraft/server/level/EntityPlayer.java
++++ b/src/main/java/net/minecraft/server/level/EntityPlayer.java
+@@ -1581,7 +1581,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
+
+ @Override
+ public boolean isInvulnerable(DamageSource damagesource) {
+- return super.isInvulnerable(damagesource) || this.H() || this.abilities.isInvulnerable && damagesource == DamageSource.WITHER;
++ return super.isInvulnerable(damagesource) || this.H() || damagesource == DamageSource.CRAMMING || this.abilities.isInvulnerable && damagesource == DamageSource.WITHER; // Purpur
+ }
+
+ @Override
diff --git a/patches/Purpur/patches/server/0048-Option-to-toggle-milk-curing-bad-omen.patch b/patches/Purpur/patches/server/0048-Option-to-toggle-milk-curing-bad-omen.patch
new file mode 100644
index 00000000..8d19f9ae
--- /dev/null
+++ b/patches/Purpur/patches/server/0048-Option-to-toggle-milk-curing-bad-omen.patch
@@ -0,0 +1,44 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath