-Date: Fri, 18 Oct 2019 22:50:05 -0500
-Subject: [PATCH] Llama API
-
-
-diff --git a/src/main/java/org/bukkit/entity/Llama.java b/src/main/java/org/bukkit/entity/Llama.java
-index bc84b892cae5fe7019a3ad481e9da79956efa1fe..48eb5b00c460cccde29d327cef1d63fc04d6a829 100644
---- a/src/main/java/org/bukkit/entity/Llama.java
-+++ b/src/main/java/org/bukkit/entity/Llama.java
-@@ -119,4 +119,20 @@ public interface Llama extends ChestedHorse, RangedEntity { // Paper
- @org.jetbrains.annotations.Nullable
- Llama getCaravanTail();
- // Paper end
-+
-+ // 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);
-+ // Purpur end
- }
-diff --git a/src/main/java/org/purpurmc/purpur/event/entity/LlamaJoinCaravanEvent.java b/src/main/java/org/purpurmc/purpur/event/entity/LlamaJoinCaravanEvent.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..e34c37579dc8a5a108c03b9eff6bb916a910d867
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/entity/LlamaJoinCaravanEvent.java
-@@ -0,0 +1,60 @@
-+package org.purpurmc.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.ApiStatus;
-+import org.jspecify.annotations.NullMarked;
-+
-+/**
-+ * 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)}.
-+ */
-+@NullMarked
-+public class LlamaJoinCaravanEvent extends EntityEvent implements Cancellable {
-+ private static final HandlerList handlers = new HandlerList();
-+ private boolean canceled;
-+ private final Llama head;
-+
-+ @ApiStatus.Internal
-+ public LlamaJoinCaravanEvent(Llama llama, Llama head) {
-+ super(llama);
-+ this.head = head;
-+ }
-+
-+ @Override
-+ public Llama getEntity() {
-+ return (Llama) entity;
-+ }
-+
-+ /**
-+ * Get the Llama that this Llama is about to follow
-+ *
-+ * @return Llama about to be followed
-+ */
-+ public Llama getHead() {
-+ return head;
-+ }
-+
-+ @Override
-+ public boolean isCancelled() {
-+ return canceled;
-+ }
-+
-+ @Override
-+ public void setCancelled(boolean cancel) {
-+ canceled = cancel;
-+ }
-+
-+ @Override
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
-diff --git a/src/main/java/org/purpurmc/purpur/event/entity/LlamaLeaveCaravanEvent.java b/src/main/java/org/purpurmc/purpur/event/entity/LlamaLeaveCaravanEvent.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..23ea41ff5dc43a915a263aeb1a246705de8bf9e1
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/entity/LlamaLeaveCaravanEvent.java
-@@ -0,0 +1,34 @@
-+package org.purpurmc.purpur.event.entity;
-+
-+import org.bukkit.entity.Llama;
-+import org.bukkit.event.HandlerList;
-+import org.bukkit.event.entity.EntityEvent;
-+import org.jetbrains.annotations.ApiStatus;
-+import org.jspecify.annotations.NullMarked;
-+
-+/**
-+ * Called when a Llama leaves a caravan
-+ */
-+@NullMarked
-+public class LlamaLeaveCaravanEvent extends EntityEvent {
-+ private static final HandlerList handlers = new HandlerList();
-+
-+ @ApiStatus.Internal
-+ public LlamaLeaveCaravanEvent(Llama llama) {
-+ super(llama);
-+ }
-+
-+ @Override
-+ public Llama getEntity() {
-+ return (Llama) entity;
-+ }
-+
-+ @Override
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
diff --git a/patches/api/0009-AFK-API.patch b/patches/api/0009-AFK-API.patch
deleted file mode 100644
index 1c3bac8d51..0000000000
--- a/patches/api/0009-AFK-API.patch
+++ /dev/null
@@ -1,113 +0,0 @@
-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/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
-index c8365c38c91b3e6c4f721074f0646fe5adffbdf6..ed0f892ed987419809fe1a0390b6278c99659919 100644
---- a/src/main/java/org/bukkit/entity/Player.java
-+++ b/src/main/java/org/bukkit/entity/Player.java
-@@ -3919,5 +3919,25 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
- * @return True if Player uses Purpur Client
- */
- public boolean usesPurpurClient();
-+
-+ /**
-+ * 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
-+ * @deprecated Use {@link #resetIdleDuration()} instead
-+ */
-+ void resetIdleTimer();
- // Purpur end
- }
-diff --git a/src/main/java/org/purpurmc/purpur/event/PlayerAFKEvent.java b/src/main/java/org/purpurmc/purpur/event/PlayerAFKEvent.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..e9637b82014fe3f4f4671b24d18f77f3d5e4b8ad
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/PlayerAFKEvent.java
-@@ -0,0 +1,71 @@
-+package org.purpurmc.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.ApiStatus;
-+import org.jspecify.annotations.NullMarked;
-+import org.jspecify.annotations.Nullable;
-+
-+@NullMarked
-+public class PlayerAFKEvent extends PlayerEvent implements Cancellable {
-+ private static final HandlerList handlers = new HandlerList();
-+ private final boolean setAfk;
-+ private boolean shouldKick;
-+ private @Nullable String broadcast;
-+ private boolean cancel;
-+
-+ @ApiStatus.Internal
-+ public PlayerAFKEvent(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
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
diff --git a/patches/api/0010-Bring-back-server-name.patch b/patches/api/0010-Bring-back-server-name.patch
deleted file mode 100644
index b88bcca780..0000000000
--- a/patches/api/0010-Bring-back-server-name.patch
+++ /dev/null
@@ -1,44 +0,0 @@
-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 8ab94f8189ebd9d4158231871abdebec399deb2c..c38fa167bbe46dfd34b90b47f6918a399af12c32 100644
---- a/src/main/java/org/bukkit/Bukkit.java
-+++ b/src/main/java/org/bukkit/Bukkit.java
-@@ -2968,4 +2968,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 f90da51a8d1003a5cba86decbd42470f7f7e9211..2d17726681b9001179590e9c33ee5b6561fb7789 100644
---- a/src/main/java/org/bukkit/Server.java
-+++ b/src/main/java/org/bukkit/Server.java
-@@ -2619,4 +2619,13 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
- */
- void allowPausing(@NotNull org.bukkit.plugin.Plugin plugin, boolean value);
- // Paper end - API to check if the server is sleeping
-+
-+ // Purpur start
-+ /**
-+ * Get the name of this server
-+ * @return the name of the server
-+ */
-+ @NotNull
-+ String getServerName();
-+ // Purpur end
- }
diff --git a/patches/api/0011-ExecuteCommandEvent.patch b/patches/api/0011-ExecuteCommandEvent.patch
deleted file mode 100644
index facb1f650e..0000000000
--- a/patches/api/0011-ExecuteCommandEvent.patch
+++ /dev/null
@@ -1,172 +0,0 @@
-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/org/bukkit/command/SimpleCommandMap.java b/src/main/java/org/bukkit/command/SimpleCommandMap.java
-index 5df19bd701c67506689fc7f49d91f99ebfbc83f0..a09b5458191eb5df4787859b72a37fa1fa2bffba 100644
---- a/src/main/java/org/bukkit/command/SimpleCommandMap.java
-+++ b/src/main/java/org/bukkit/command/SimpleCommandMap.java
-@@ -153,6 +153,19 @@ public class SimpleCommandMap implements CommandMap {
- return false;
- }
-
-+ // Purpur start
-+ String[] parsedArgs = Arrays.copyOfRange(args, 1, args.length);
-+ org.purpurmc.purpur.event.ExecuteCommandEvent event = new org.purpurmc.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);
-@@ -162,7 +175,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 com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerCommandException(ex, target, sender, args))); // Paper
-diff --git a/src/main/java/org/purpurmc/purpur/event/ExecuteCommandEvent.java b/src/main/java/org/purpurmc/purpur/event/ExecuteCommandEvent.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..55feef2321c7d966c72a33a58cf10136a9cacfa6
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/ExecuteCommandEvent.java
-@@ -0,0 +1,127 @@
-+package org.purpurmc.purpur.event;
-+
-+import com.google.common.base.Preconditions;
-+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.ApiStatus;
-+import org.jspecify.annotations.NullMarked;
-+import org.jspecify.annotations.Nullable;
-+
-+/**
-+ * This event is called whenever someone runs a command
-+ */
-+@NullMarked
-+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 @Nullable String[] args;
-+
-+ @ApiStatus.Internal
-+ public ExecuteCommandEvent(CommandSender sender, Command command, 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
-+ */
-+ 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(Command command) throws IllegalArgumentException {
-+ Preconditions.checkArgument(command != null, "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
-+ */
-+ 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(final CommandSender sender) throws IllegalArgumentException {
-+ Preconditions.checkArgument(sender != null, "Sender cannot be null");
-+ this.sender = sender;
-+ }
-+
-+ /**
-+ * Get the label used to execute this command
-+ *
-+ * @return Label used to execute this command
-+ */
-+ public String getLabel() {
-+ return label;
-+ }
-+
-+ /**
-+ * Set the label used to execute this command
-+ *
-+ * @param label Label used
-+ */
-+ public void setLabel(String label) {
-+ this.label = label;
-+ }
-+
-+ /**
-+ * Get the args passed to the command
-+ *
-+ * @return Args passed to the command
-+ */
-+ public String[] getArgs() {
-+ return args;
-+ }
-+
-+ /**
-+ * Set the args passed to the command
-+ *
-+ * @param args Args passed to the command
-+ */
-+ public void setArgs(String[] args) {
-+ this.args = args;
-+ }
-+
-+ @Override
-+ public boolean isCancelled() {
-+ return cancel;
-+ }
-+
-+ @Override
-+ public void setCancelled(boolean cancel) {
-+ this.cancel = cancel;
-+ }
-+
-+ @Override
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
diff --git a/patches/api/0012-Lagging-threshold.patch b/patches/api/0012-Lagging-threshold.patch
deleted file mode 100644
index 86737ba982..0000000000
--- a/patches/api/0012-Lagging-threshold.patch
+++ /dev/null
@@ -1,42 +0,0 @@
-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 c38fa167bbe46dfd34b90b47f6918a399af12c32..cd6c712a2ad92f73c7ce8f4ada8b810fcba00ba3 100644
---- a/src/main/java/org/bukkit/Bukkit.java
-+++ b/src/main/java/org/bukkit/Bukkit.java
-@@ -2978,5 +2978,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 2d17726681b9001179590e9c33ee5b6561fb7789..788702d1c5be00a78a0438e267fe5fca9985e4ce 100644
---- a/src/main/java/org/bukkit/Server.java
-+++ b/src/main/java/org/bukkit/Server.java
-@@ -2627,5 +2627,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/api/0013-PlayerSetSpawnerTypeWithEggEvent.patch b/patches/api/0013-PlayerSetSpawnerTypeWithEggEvent.patch
deleted file mode 100644
index 64410cdb4c..0000000000
--- a/patches/api/0013-PlayerSetSpawnerTypeWithEggEvent.patch
+++ /dev/null
@@ -1,184 +0,0 @@
-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/org/purpurmc/purpur/event/PlayerSetSpawnerTypeWithEggEvent.java b/src/main/java/org/purpurmc/purpur/event/PlayerSetSpawnerTypeWithEggEvent.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..795c558b481f4e2a550925bd88b8e7d41711456f
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/PlayerSetSpawnerTypeWithEggEvent.java
-@@ -0,0 +1,83 @@
-+package org.purpurmc.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.ApiStatus;
-+import org.jspecify.annotations.NullMarked;
-+
-+@NullMarked
-+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;
-+
-+ @ApiStatus.Internal
-+ public PlayerSetSpawnerTypeWithEggEvent(Player player, Block block, CreatureSpawner spawner, EntityType type) {
-+ super(player);
-+ this.block = block;
-+ this.spawner = spawner;
-+ this.type = type;
-+ }
-+
-+ /**
-+ * Get the spawner Block in the world
-+ *
-+ * @return Spawner Block
-+ */
-+ public Block getBlock() {
-+ return block;
-+ }
-+
-+ /**
-+ * Get the spawner state
-+ *
-+ * @return Spawner state
-+ */
-+ public CreatureSpawner getSpawner() {
-+ return spawner;
-+ }
-+
-+ /**
-+ * Gets the EntityType being set on the spawner
-+ *
-+ * @return EntityType being set
-+ */
-+ public EntityType getEntityType() {
-+ return type;
-+ }
-+
-+ /**
-+ * Sets the EntityType being set on the spawner
-+ *
-+ * @param type EntityType to set
-+ */
-+ public void setEntityType(EntityType type) {
-+ this.type = type;
-+ }
-+
-+ @Override
-+ public boolean isCancelled() {
-+ return cancel;
-+ }
-+
-+ @Override
-+ public void setCancelled(boolean cancel) {
-+ this.cancel = cancel;
-+ }
-+
-+ @Override
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
-diff --git a/src/main/java/org/purpurmc/purpur/event/PlayerSetTrialSpawnerTypeWithEggEvent.java b/src/main/java/org/purpurmc/purpur/event/PlayerSetTrialSpawnerTypeWithEggEvent.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..1d4dbf60a182a2a5f93c449e387b82743d20616c
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/PlayerSetTrialSpawnerTypeWithEggEvent.java
-@@ -0,0 +1,83 @@
-+package org.purpurmc.purpur.event;
-+
-+import org.bukkit.block.Block;
-+import org.bukkit.block.TrialSpawner;
-+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.ApiStatus;
-+import org.jspecify.annotations.NullMarked;
-+
-+@NullMarked
-+public class PlayerSetTrialSpawnerTypeWithEggEvent extends PlayerEvent implements Cancellable {
-+ private static final HandlerList handlers = new HandlerList();
-+ private final Block block;
-+ private final TrialSpawner spawner;
-+ private EntityType type;
-+ private boolean cancel;
-+
-+ @ApiStatus.Internal
-+ public PlayerSetTrialSpawnerTypeWithEggEvent(Player player, Block block, TrialSpawner spawner, EntityType type) {
-+ super(player);
-+ this.block = block;
-+ this.spawner = spawner;
-+ this.type = type;
-+ }
-+
-+ /**
-+ * Get the spawner Block in the world
-+ *
-+ * @return Spawner Block
-+ */
-+ public Block getBlock() {
-+ return block;
-+ }
-+
-+ /**
-+ * Get the spawner state
-+ *
-+ * @return Spawner state
-+ */
-+ public TrialSpawner getSpawner() {
-+ return spawner;
-+ }
-+
-+ /**
-+ * Gets the EntityType being set on the spawner
-+ *
-+ * @return EntityType being set
-+ */
-+ public EntityType getEntityType() {
-+ return type;
-+ }
-+
-+ /**
-+ * Sets the EntityType being set on the spawner
-+ *
-+ * @param type EntityType to set
-+ */
-+ public void setEntityType(EntityType type) {
-+ this.type = type;
-+ }
-+
-+ @Override
-+ public boolean isCancelled() {
-+ return cancel;
-+ }
-+
-+ @Override
-+ public void setCancelled(boolean cancel) {
-+ this.cancel = cancel;
-+ }
-+
-+ @Override
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
diff --git a/patches/api/0014-Anvil-API.patch b/patches/api/0014-Anvil-API.patch
deleted file mode 100644
index 3601d50003..0000000000
--- a/patches/api/0014-Anvil-API.patch
+++ /dev/null
@@ -1,189 +0,0 @@
-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/org/bukkit/inventory/AnvilInventory.java b/src/main/java/org/bukkit/inventory/AnvilInventory.java
-index f1f97a85ec713c05c882d7588f4a3e4a017f4795..813f6cd253322538bdf96eb323dd23a7809a1c1e 100644
---- a/src/main/java/org/bukkit/inventory/AnvilInventory.java
-+++ b/src/main/java/org/bukkit/inventory/AnvilInventory.java
-@@ -138,4 +138,42 @@ public interface AnvilInventory extends Inventory {
- setItem(2, result);
- }
- // Paper end
-+
-+ // Purpur start
-+ /**
-+ * Gets if the player viewing the anvil inventory can bypass experience cost
-+ *
-+ * @return whether the player viewing the anvil inventory can bypass the experience cost
-+ * @deprecated use {@link AnvilView#canBypassCost()}.
-+ */
-+ @Deprecated(forRemoval = true, since = "1.21")
-+ boolean canBypassCost();
-+
-+ /**
-+ * Set if the player viewing the anvil inventory can bypass the experience cost
-+ *
-+ * @param bypassCost whether the player viewing the anvil inventory can bypass the experience cost
-+ * @deprecated use {@link AnvilView#setBypassCost(boolean)}.
-+ */
-+ @Deprecated(forRemoval = true, since = "1.21")
-+ void setBypassCost(boolean bypassCost);
-+
-+ /**
-+ * Gets if the player viewing the anvil inventory can do unsafe enchants
-+ *
-+ * @return whether the player viewing the anvil inventory can do unsafe enchants
-+ * @deprecated use {@link AnvilView#canDoUnsafeEnchants()}.
-+ */
-+ @Deprecated(forRemoval = true, since = "1.21")
-+ boolean canDoUnsafeEnchants();
-+
-+ /**
-+ * Set if the player viewing the anvil inventory can do unsafe enchants
-+ *
-+ * @param canDoUnsafeEnchants whether the player viewing the anvil inventory can do unsafe enchants
-+ * @deprecated use {@link AnvilView#setDoUnsafeEnchants(boolean)}.
-+ */
-+ @Deprecated(forRemoval = true, since = "1.21")
-+ void setDoUnsafeEnchants(boolean canDoUnsafeEnchants);
-+ // Purpur end
- }
-diff --git a/src/main/java/org/bukkit/inventory/view/AnvilView.java b/src/main/java/org/bukkit/inventory/view/AnvilView.java
-index 3c1aa1e036bee08304c1cdca59f6a5bc0ba306c0..709fb2d1c7e3253034a651a9f68c003601b598a4 100644
---- a/src/main/java/org/bukkit/inventory/view/AnvilView.java
-+++ b/src/main/java/org/bukkit/inventory/view/AnvilView.java
-@@ -89,4 +89,34 @@ public interface AnvilView extends InventoryView {
- */
- void bypassEnchantmentLevelRestriction(boolean bypassEnchantmentLevelRestriction);
- // Paper end - bypass anvil level restrictions
-+
-+ // Purpur start
-+ /**
-+ * Gets if the player viewing the anvil inventory can bypass experience cost
-+ *
-+ * @return whether the player viewing the anvil inventory can bypass the experience cost
-+ */
-+ boolean canBypassCost();
-+
-+ /**
-+ * Set if the player viewing the anvil inventory can bypass the experience cost
-+ *
-+ * @param bypassCost whether the player viewing the anvil inventory can bypass the experience cost
-+ */
-+ void setBypassCost(boolean bypassCost);
-+
-+ /**
-+ * Gets if the player viewing the anvil inventory can do unsafe enchants
-+ *
-+ * @return whether the player viewing the anvil inventory can do unsafe enchants
-+ */
-+ boolean canDoUnsafeEnchants();
-+
-+ /**
-+ * Set if the player viewing the anvil inventory can do unsafe enchants
-+ *
-+ * @param canDoUnsafeEnchants whether the player viewing the anvil inventory can do unsafe enchants
-+ */
-+ void setDoUnsafeEnchants(boolean canDoUnsafeEnchants);
-+ // Purpur end
- }
-diff --git a/src/main/java/org/purpurmc/purpur/event/inventory/AnvilTakeResultEvent.java b/src/main/java/org/purpurmc/purpur/event/inventory/AnvilTakeResultEvent.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..b2199854b5c7e74a673cbadbe584e5aaebbe3883
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/inventory/AnvilTakeResultEvent.java
-@@ -0,0 +1,50 @@
-+package org.purpurmc.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.ApiStatus;
-+import org.jspecify.annotations.NullMarked;
-+
-+/**
-+ * Called when a player takes the result item out of an anvil
-+ */
-+@NullMarked
-+public class AnvilTakeResultEvent extends InventoryEvent {
-+ private static final HandlerList handlers = new HandlerList();
-+ private final Player player;
-+ private final ItemStack result;
-+
-+ @ApiStatus.Internal
-+ public AnvilTakeResultEvent(HumanEntity player, InventoryView view, ItemStack result) {
-+ super(view);
-+ this.player = (Player) player;
-+ this.result = result;
-+ }
-+
-+ public Player getPlayer() {
-+ return player;
-+ }
-+
-+ public ItemStack getResult() {
-+ return result;
-+ }
-+
-+ @Override
-+ public AnvilInventory getInventory() {
-+ return (AnvilInventory) super.getInventory();
-+ }
-+
-+ @Override
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
-diff --git a/src/main/java/org/purpurmc/purpur/event/inventory/AnvilUpdateResultEvent.java b/src/main/java/org/purpurmc/purpur/event/inventory/AnvilUpdateResultEvent.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..4293c4a57c1c054e8248b7712e8664bd4cb1a972
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/inventory/AnvilUpdateResultEvent.java
-@@ -0,0 +1,35 @@
-+package org.purpurmc.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.ApiStatus;
-+import org.jspecify.annotations.NullMarked;
-+
-+/**
-+ * Called when anvil slots change, triggering the result slot to be updated
-+ */
-+@NullMarked
-+public class AnvilUpdateResultEvent extends InventoryEvent {
-+ private static final HandlerList handlers = new HandlerList();
-+
-+ @ApiStatus.Internal
-+ public AnvilUpdateResultEvent(InventoryView view) {
-+ super(view);
-+ }
-+
-+ @Override
-+ public AnvilInventory getInventory() {
-+ return (AnvilInventory) super.getInventory();
-+ }
-+
-+ @Override
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
diff --git a/patches/api/0019-Rabid-Wolf-API.patch b/patches/api/0019-Rabid-Wolf-API.patch
deleted file mode 100644
index cbf725884a..0000000000
--- a/patches/api/0019-Rabid-Wolf-API.patch
+++ /dev/null
@@ -1,31 +0,0 @@
-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/org/bukkit/entity/Wolf.java b/src/main/java/org/bukkit/entity/Wolf.java
-index c73489f4b745bc84501ce94f0227b034d9768eae..a97129e71f16ec691759add664bdfd35ab90aaed 100644
---- a/src/main/java/org/bukkit/entity/Wolf.java
-+++ b/src/main/java/org/bukkit/entity/Wolf.java
-@@ -108,4 +108,20 @@ public interface Wolf extends Tameable, Sittable, io.papermc.paper.entity.Collar
- return Registry.WOLF_VARIANT.getOrThrow(NamespacedKey.minecraft(key));
- }
- }
-+
-+ // 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/api/0020-PlayerBookTooLargeEvent.patch b/patches/api/0020-PlayerBookTooLargeEvent.patch
deleted file mode 100644
index 248342c518..0000000000
--- a/patches/api/0020-PlayerBookTooLargeEvent.patch
+++ /dev/null
@@ -1,77 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: William Blake Galbreath
-Date: Wed, 23 Dec 2020 00:43:27 -0600
-Subject: [PATCH] PlayerBookTooLargeEvent
-
-
-diff --git a/src/main/java/org/purpurmc/purpur/event/player/PlayerBookTooLargeEvent.java b/src/main/java/org/purpurmc/purpur/event/player/PlayerBookTooLargeEvent.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..31cce9f4e398135016114b96254376325a22ba7c
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/player/PlayerBookTooLargeEvent.java
-@@ -0,0 +1,65 @@
-+package org.purpurmc.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.ApiStatus;
-+import org.jspecify.annotations.NullMarked;
-+
-+/**
-+ * Called when a player tries to bypass book limitations
-+ */
-+@NullMarked
-+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
-+ */
-+ @ApiStatus.Internal
-+ public PlayerBookTooLargeEvent(Player player, ItemStack book) {
-+ super(player, !Bukkit.isPrimaryThread());
-+ this.book = book;
-+ }
-+
-+ /**
-+ * Get the book containing the wanted edits
-+ *
-+ * @return The book
-+ */
-+ 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
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
diff --git a/patches/api/0021-Full-netherite-armor-grants-fire-resistance.patch b/patches/api/0021-Full-netherite-armor-grants-fire-resistance.patch
deleted file mode 100644
index 4f1acab261..0000000000
--- a/patches/api/0021-Full-netherite-armor-grants-fire-resistance.patch
+++ /dev/null
@@ -1,23 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: William Blake Galbreath
-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 8fdfcbc7d20fe0af6b220ab94516247093637621..f6a8928408e11a5ae723366e4ea1280dfcc6111e 100644
---- a/src/main/java/org/bukkit/event/entity/EntityPotionEffectEvent.java
-+++ b/src/main/java/org/bukkit/event/entity/EntityPotionEffectEvent.java
-@@ -216,6 +216,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/api/0022-Add-EntityTeleportHinderedEvent.patch b/patches/api/0022-Add-EntityTeleportHinderedEvent.patch
deleted file mode 100644
index 39b584d99d..0000000000
--- a/patches/api/0022-Add-EntityTeleportHinderedEvent.patch
+++ /dev/null
@@ -1,138 +0,0 @@
-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/org/purpurmc/purpur/event/entity/EntityTeleportHinderedEvent.java b/src/main/java/org/purpurmc/purpur/event/entity/EntityTeleportHinderedEvent.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..daf3bbf83ee76322828a38814b483fa2b337bd60
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/entity/EntityTeleportHinderedEvent.java
-@@ -0,0 +1,114 @@
-+package org.purpurmc.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.ApiStatus;
-+import org.jspecify.annotations.NullMarked;
-+import org.jspecify.annotations.Nullable;
-+
-+/**
-+ * Fired when an entity is hindered from teleporting.
-+ */
-+@NullMarked
-+public class EntityTeleportHinderedEvent extends EntityEvent {
-+ private static final HandlerList handlers = new HandlerList();
-+
-+ private final Reason reason;
-+
-+ private final @Nullable TeleportCause teleportCause;
-+
-+ private boolean retry = false;
-+
-+ @ApiStatus.Internal
-+ public EntityTeleportHinderedEvent(Entity what, Reason reason, @Nullable TeleportCause teleportCause) {
-+ super(what);
-+ this.reason = reason;
-+ this.teleportCause = teleportCause;
-+ }
-+
-+ /**
-+ * @return why the teleport was hindered.
-+ */
-+ 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
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ 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/api/0024-API-for-any-mob-to-burn-daylight.patch b/patches/api/0024-API-for-any-mob-to-burn-daylight.patch
deleted file mode 100644
index 2a27a45f48..0000000000
--- a/patches/api/0024-API-for-any-mob-to-burn-daylight.patch
+++ /dev/null
@@ -1,49 +0,0 @@
-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 5603ecc5d8f6ccf29333e1c47db3af36379a27d6..a4136da96f0759ca137a86f2518af58bccb6b632 100644
---- a/src/main/java/org/bukkit/entity/Entity.java
-+++ b/src/main/java/org/bukkit/entity/Entity.java
-@@ -1202,5 +1202,12 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent
- * @return True if ridable in water
- */
- boolean isRidableInWater();
-+
-+ /**
-+ * 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 d21a228bbec0302e75c4db5aa1db54f321143587..a4acc3578e935cd1174474bd1f6ff14db4294fe7 100644
---- a/src/main/java/org/bukkit/entity/LivingEntity.java
-+++ b/src/main/java/org/bukkit/entity/LivingEntity.java
-@@ -1468,4 +1468,20 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource
- */
- boolean canUseEquipmentSlot(org.bukkit.inventory.@NotNull EquipmentSlot slot);
- // Paper end - Expose canUseSlot
-+
-+ // Purpur start - API for any mob to burn daylight
-+ /**
-+ * 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 - API for any mob to burn daylight
- }
diff --git a/patches/api/0026-Fix-default-permission-system.patch b/patches/api/0026-Fix-default-permission-system.patch
deleted file mode 100644
index 83d8a54ee1..0000000000
--- a/patches/api/0026-Fix-default-permission-system.patch
+++ /dev/null
@@ -1,77 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: William Blake Galbreath
-Date: Wed, 30 Jun 2021 17:44:27 -0500
-Subject: [PATCH] Fix default permission system
-
-
-diff --git a/src/main/java/org/bukkit/permissions/PermissibleBase.java b/src/main/java/org/bukkit/permissions/PermissibleBase.java
-index 75b77cc4fe189b4b6baa1af3663dc492e992a266..30b98d1645c571ba5c18e5cc93b0bec3f74b1d3b 100644
---- a/src/main/java/org/bukkit/permissions/PermissibleBase.java
-+++ b/src/main/java/org/bukkit/permissions/PermissibleBase.java
-@@ -169,7 +169,7 @@ public class PermissibleBase implements Permissible {
-
- for (Permission perm : defaults) {
- String name = perm.getName().toLowerCase(Locale.ROOT);
-- permissions.put(name, new PermissionAttachmentInfo(parent, name, null, true));
-+ permissions.put(name, new PermissionAttachmentInfo(parent, name, null, perm.getDefault().getValue(isOp()))); // Purpur
- Bukkit.getServer().getPluginManager().subscribeToPermission(name, parent);
- calculateChildPermissions(perm.getChildren(), false, null);
- }
-@@ -197,7 +197,7 @@ public class PermissibleBase implements Permissible {
- String name = entry.getKey();
-
- Permission perm = Bukkit.getServer().getPluginManager().getPermission(name);
-- boolean value = entry.getValue() ^ invert;
-+ boolean value = (entry.getValue() == null && perm != null ? perm.getDefault().getValue(isOp()) : entry.getValue()) ^ invert; // Purpur
- String lname = name.toLowerCase(Locale.ROOT);
-
- permissions.put(lname, new PermissionAttachmentInfo(parent, lname, attachment, value));
-diff --git a/src/main/java/org/bukkit/util/permissions/DefaultPermissions.java b/src/main/java/org/bukkit/util/permissions/DefaultPermissions.java
-index 8e481e3815f5645ee92f0d229e5ff25c8fc9a6c2..10627d2a11251a8cb01bbc3f6242d66f3505a16e 100644
---- a/src/main/java/org/bukkit/util/permissions/DefaultPermissions.java
-+++ b/src/main/java/org/bukkit/util/permissions/DefaultPermissions.java
-@@ -31,7 +31,7 @@ public final class DefaultPermissions {
-
- if (withLegacy) {
- Permission legacy = new Permission(LEGACY_PREFIX + result.getName(), result.getDescription(), PermissionDefault.FALSE);
-- legacy.getChildren().put(result.getName(), true);
-+ legacy.getChildren().put(result.getName(), null); // Purpur
- registerPermission(perm, false);
- }
-
-@@ -40,7 +40,7 @@ public final class DefaultPermissions {
-
- @NotNull
- public static Permission registerPermission(@NotNull Permission perm, @NotNull Permission parent) {
-- parent.getChildren().put(perm.getName(), true);
-+ parent.getChildren().put(perm.getName(), null); // Purpur
- return registerPermission(perm);
- }
-
-@@ -53,7 +53,7 @@ public final class DefaultPermissions {
- @NotNull
- public static Permission registerPermission(@NotNull String name, @Nullable String desc, @NotNull Permission parent) {
- Permission perm = registerPermission(name, desc);
-- parent.getChildren().put(perm.getName(), true);
-+ parent.getChildren().put(perm.getName(), null); // Purpur
- return perm;
- }
-
-@@ -66,7 +66,7 @@ public final class DefaultPermissions {
- @NotNull
- public static Permission registerPermission(@NotNull String name, @Nullable String desc, @Nullable PermissionDefault def, @NotNull Permission parent) {
- Permission perm = registerPermission(name, desc, def);
-- parent.getChildren().put(perm.getName(), true);
-+ parent.getChildren().put(perm.getName(), null); // Purpur
- return perm;
- }
-
-@@ -79,7 +79,7 @@ public final class DefaultPermissions {
- @NotNull
- public static Permission registerPermission(@NotNull String name, @Nullable String desc, @Nullable PermissionDefault def, @Nullable Map children, @NotNull Permission parent) {
- Permission perm = registerPermission(name, desc, def, children);
-- parent.getChildren().put(perm.getName(), true);
-+ parent.getChildren().put(perm.getName(), null); // Purpur
- return perm;
- }
-
diff --git a/patches/api/0027-Summoner-API.patch b/patches/api/0027-Summoner-API.patch
deleted file mode 100644
index bc94bd07eb..0000000000
--- a/patches/api/0027-Summoner-API.patch
+++ /dev/null
@@ -1,81 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: William Blake Galbreath
-Date: Sat, 3 Jul 2021 18:45:01 -0500
-Subject: [PATCH] Summoner API
-
-
-diff --git a/src/main/java/org/bukkit/entity/IronGolem.java b/src/main/java/org/bukkit/entity/IronGolem.java
-index 655e37cb3a09610a3f3df805d6dcad17d722da62..09fd716c8fc9ea34a1cbf87bcbe22df035422a51 100644
---- a/src/main/java/org/bukkit/entity/IronGolem.java
-+++ b/src/main/java/org/bukkit/entity/IronGolem.java
-@@ -19,4 +19,20 @@ public interface IronGolem extends Golem {
- * player created, false if you want it to be a natural village golem.
- */
- public void setPlayerCreated(boolean playerCreated);
-+
-+ // Purpur start
-+ /**
-+ * Get the player that summoned this iron golem
-+ *
-+ * @return UUID of summoner
-+ */
-+ @org.jetbrains.annotations.Nullable java.util.UUID getSummoner();
-+
-+ /**
-+ * Set the player that summoned this iron golem
-+ *
-+ * @param summoner UUID of summoner
-+ */
-+ void setSummoner(@org.jetbrains.annotations.Nullable java.util.UUID summoner);
-+ // Purpur end
- }
-diff --git a/src/main/java/org/bukkit/entity/Snowman.java b/src/main/java/org/bukkit/entity/Snowman.java
-index 7fbfdb07585c7b28acea1f0c1f58ada0cc744441..21fcca092e2e31baa5ece0de9e44e3fade8c7123 100644
---- a/src/main/java/org/bukkit/entity/Snowman.java
-+++ b/src/main/java/org/bukkit/entity/Snowman.java
-@@ -23,4 +23,20 @@ public interface Snowman extends Golem, RangedEntity, io.papermc.paper.entity.Sh
- * @param derpMode True to remove the pumpkin, false to add a pumpkin
- */
- void setDerp(boolean derpMode);
-+
-+ // Purpur start
-+ /**
-+ * Get the player that summoned this snowman
-+ *
-+ * @return UUID of summoner
-+ */
-+ @org.jetbrains.annotations.Nullable java.util.UUID getSummoner();
-+
-+ /**
-+ * Set the player that summoned this snowman
-+ *
-+ * @param summoner UUID of summoner
-+ */
-+ void setSummoner(@org.jetbrains.annotations.Nullable java.util.UUID summoner);
-+ // Purpur end
- }
-diff --git a/src/main/java/org/bukkit/entity/Wither.java b/src/main/java/org/bukkit/entity/Wither.java
-index 14543c2238b45c526dd9aebea2aa5c22f5df54dc..5312daf33405704c74e2c9e109754285ea6cf734 100644
---- a/src/main/java/org/bukkit/entity/Wither.java
-+++ b/src/main/java/org/bukkit/entity/Wither.java
-@@ -107,4 +107,20 @@ public interface Wither extends Monster, Boss, com.destroystokyo.paper.entity.Ra
- */
- void enterInvulnerabilityPhase();
- // Paper end
-+
-+ // Purpur start
-+ /**
-+ * Get the player that summoned this wither
-+ *
-+ * @return UUID of summoner
-+ */
-+ @org.jetbrains.annotations.Nullable java.util.UUID getSummoner();
-+
-+ /**
-+ * Set the player that summoned this wither
-+ *
-+ * @param summoner UUID of summoner
-+ */
-+ void setSummoner(@org.jetbrains.annotations.Nullable java.util.UUID summoner);
-+ // Purpur end
- }
diff --git a/patches/api/0030-Added-the-ability-to-add-combustible-items.patch b/patches/api/0030-Added-the-ability-to-add-combustible-items.patch
deleted file mode 100644
index 10b834d4c2..0000000000
--- a/patches/api/0030-Added-the-ability-to-add-combustible-items.patch
+++ /dev/null
@@ -1,60 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: DoctaEnkoda
-Date: Mon, 9 Aug 2021 13:22:03 +0200
-Subject: [PATCH] Added the ability to add combustible items
-
-
-diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java
-index cd6c712a2ad92f73c7ce8f4ada8b810fcba00ba3..79528ad4408eac53754ccc810445970e263a1a32 100644
---- a/src/main/java/org/bukkit/Bukkit.java
-+++ b/src/main/java/org/bukkit/Bukkit.java
-@@ -2987,5 +2987,24 @@ public final class Bukkit {
- public static boolean isLagging() {
- return server.isLagging();
- }
-+
-+ /**
-+ * Add an Item as fuel for furnaces
-+ *
-+ * @param material The material that will be the fuel
-+ * @param burnTime The time (in ticks) this item will burn for
-+ */
-+ public static void addFuel(@NotNull Material material, int burnTime) {
-+ server.addFuel(material, burnTime);
-+ }
-+
-+ /**
-+ * Remove an item as fuel for furnaces
-+ *
-+ * @param material The material that will no longer be a fuel
-+ */
-+ public static void removeFuel(@NotNull Material material) {
-+ server.removeFuel(material);
-+ }
- // Purpur end
- }
-diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java
-index 788702d1c5be00a78a0438e267fe5fca9985e4ce..c1523229138d5d07569c8e564cf27b8276cca153 100644
---- a/src/main/java/org/bukkit/Server.java
-+++ b/src/main/java/org/bukkit/Server.java
-@@ -2634,5 +2634,20 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
- * @return True if lagging
- */
- boolean isLagging();
-+
-+ /**
-+ * Add an Item as fuel for furnaces
-+ *
-+ * @param material The material that will be the fuel
-+ * @param burnTime The time (in ticks) this item will burn for
-+ */
-+ public void addFuel(@NotNull Material material, int burnTime);
-+
-+ /**
-+ * Remove an item as fuel for furnaces
-+ *
-+ * @param material The material that will no longer be a fuel
-+ */
-+ public void removeFuel(@NotNull Material material);
- // Purpur end
- }
diff --git a/patches/api/0031-Grindstone-API.patch b/patches/api/0031-Grindstone-API.patch
deleted file mode 100644
index b872c1cc49..0000000000
--- a/patches/api/0031-Grindstone-API.patch
+++ /dev/null
@@ -1,84 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: BillyGalbreath
-Date: Mon, 27 Dec 2021 08:10:50 -0600
-Subject: [PATCH] Grindstone API
-
-
-diff --git a/src/main/java/org/purpurmc/purpur/event/inventory/GrindstoneTakeResultEvent.java b/src/main/java/org/purpurmc/purpur/event/inventory/GrindstoneTakeResultEvent.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..d6db2d355553c9c54b83328d237b9c75e7a8e375
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/inventory/GrindstoneTakeResultEvent.java
-@@ -0,0 +1,72 @@
-+package org.purpurmc.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.GrindstoneInventory;
-+import org.bukkit.inventory.InventoryView;
-+import org.bukkit.inventory.ItemStack;
-+import org.jetbrains.annotations.ApiStatus;
-+import org.jspecify.annotations.NullMarked;
-+
-+/**
-+ * Called when a player takes the result item out of a Grindstone
-+ */
-+@NullMarked
-+public class GrindstoneTakeResultEvent extends InventoryEvent {
-+ private static final HandlerList handlers = new HandlerList();
-+ private final Player player;
-+ private final ItemStack result;
-+ private int experienceAmount;
-+
-+ @ApiStatus.Internal
-+ public GrindstoneTakeResultEvent(HumanEntity player, InventoryView view, ItemStack result, int experienceAmount) {
-+ super(view);
-+ this.player = (Player) player;
-+ this.result = result;
-+ this.experienceAmount = experienceAmount;
-+ }
-+
-+ public Player getPlayer() {
-+ return player;
-+ }
-+
-+ public ItemStack getResult() {
-+ return result;
-+ }
-+
-+ @Override
-+ public GrindstoneInventory getInventory() {
-+ return (GrindstoneInventory) super.getInventory();
-+ }
-+
-+ /**
-+ * Get the amount of experience this transaction will give
-+ * (takes priority over and uses result from {@link org.bukkit.event.block.BlockExpEvent})
-+ *
-+ * @return Amount of experience to give
-+ */
-+ public int getExperienceAmount() {
-+ return this.experienceAmount;
-+ }
-+
-+ /**
-+ * Set the amount of experience this transaction will give
-+ * (takes priority over {@link org.bukkit.event.block.BlockExpEvent})
-+ *
-+ * @param experienceAmount Amount of experience to give
-+ */
-+ public void setExperienceAmount(int experienceAmount) {
-+ this.experienceAmount = experienceAmount;
-+ }
-+
-+ @Override
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
diff --git a/patches/api/0032-Shears-can-have-looting-enchantment.patch b/patches/api/0032-Shears-can-have-looting-enchantment.patch
deleted file mode 100644
index d9c0fb00fd..0000000000
--- a/patches/api/0032-Shears-can-have-looting-enchantment.patch
+++ /dev/null
@@ -1,27 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: BillyGalbreath
-Date: Mon, 3 Jan 2022 02:00:50 -0600
-Subject: [PATCH] Shears can have looting enchantment
-
-
-diff --git a/src/main/java/org/bukkit/enchantments/EnchantmentTarget.java b/src/main/java/org/bukkit/enchantments/EnchantmentTarget.java
-index bd653ad99e8e75af1494b595640c3910f4d37e6e..13b903e785a9ef5e513cb9d6483482133cc5f25b 100644
---- a/src/main/java/org/bukkit/enchantments/EnchantmentTarget.java
-+++ b/src/main/java/org/bukkit/enchantments/EnchantmentTarget.java
-@@ -238,6 +238,16 @@ public enum EnchantmentTarget {
- public boolean includes(@NotNull Material item) {
- return item.equals(Material.BOW) || item.equals(Material.CROSSBOW);
- }
-+ },
-+
-+ /**
-+ * Allow the Enchantment to be placed on shears.
-+ */
-+ WEAPON_AND_SHEARS {
-+ @Override
-+ public boolean includes(@NotNull Material item) {
-+ return WEAPON.includes(item) || item.equals(Material.SHEARS);
-+ }
- // Purpur end
- };
-
diff --git a/patches/api/0033-Lobotomize-stuck-villagers.patch b/patches/api/0033-Lobotomize-stuck-villagers.patch
deleted file mode 100644
index 4370acca80..0000000000
--- a/patches/api/0033-Lobotomize-stuck-villagers.patch
+++ /dev/null
@@ -1,25 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: BillyGalbreath
-Date: Mon, 24 Jan 2022 20:42:22 -0600
-Subject: [PATCH] Lobotomize stuck villagers
-
-
-diff --git a/src/main/java/org/bukkit/entity/Villager.java b/src/main/java/org/bukkit/entity/Villager.java
-index 1db3742024e9cd1b70af2d52b4b756a544c019df..9c722a762c88a88bb5ef18c3b9eab8b371360dac 100644
---- a/src/main/java/org/bukkit/entity/Villager.java
-+++ b/src/main/java/org/bukkit/entity/Villager.java
-@@ -367,4 +367,14 @@ public interface Villager extends AbstractVillager {
- */
- public void clearReputations();
- // Paper end
-+
-+ // Purpur start
-+
-+ /**
-+ * Check if villager is currently lobotomized
-+ *
-+ * @return True if lobotomized
-+ */
-+ boolean isLobotomized();
-+ // Purpur end
- }
diff --git a/patches/api/0034-Add-local-difficulty-api.patch b/patches/api/0034-Add-local-difficulty-api.patch
deleted file mode 100644
index bb9a78b780..0000000000
--- a/patches/api/0034-Add-local-difficulty-api.patch
+++ /dev/null
@@ -1,27 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: BillyGalbreath
-Date: Sat, 9 Jul 2022 00:57:26 -0500
-Subject: [PATCH] Add local difficulty api
-
-
-diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java
-index bef54a6c8290e09cbaac20b03dde8dfb902c96b0..3d1e2a6804980c091fe2a66a6810564d317d339f 100644
---- a/src/main/java/org/bukkit/World.java
-+++ b/src/main/java/org/bukkit/World.java
-@@ -4246,6 +4246,16 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
- @Nullable
- public DragonBattle getEnderDragonBattle();
-
-+ // Purpur start
-+ /**
-+ * Gets the local difficulty (based on inhabited time) at a location
-+ *
-+ * @param location Location to check
-+ * @return The local difficulty
-+ */
-+ public float getLocalDifficultyAt(@NotNull Location location);
-+ // Purpur end
-+
- /**
- * Get all {@link FeatureFlag} enabled in this world.
- *
diff --git a/patches/api/0035-Remove-Timings.patch b/patches/api/0035-Remove-Timings.patch
deleted file mode 100644
index 5087679a82..0000000000
--- a/patches/api/0035-Remove-Timings.patch
+++ /dev/null
@@ -1,159 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: BillyGalbreath
-Date: Fri, 1 Jul 2022 04:03:26 -0500
-Subject: [PATCH] Remove Timings
-
-
-diff --git a/src/main/java/co/aikar/timings/TimedEventExecutor.java b/src/main/java/co/aikar/timings/TimedEventExecutor.java
-index 157617933a772451f6c073d97afaf305769b4d40..438a9c76381ea3f5b774e2232ff56c5dc6f82586 100644
---- a/src/main/java/co/aikar/timings/TimedEventExecutor.java
-+++ b/src/main/java/co/aikar/timings/TimedEventExecutor.java
-@@ -80,9 +80,9 @@ public class TimedEventExecutor implements EventExecutor {
- executor.execute(listener, event);
- return;
- }
-- try (Timing ignored = timings.startTiming()){
-+ //try (Timing ignored = timings.startTiming()){ // Purpur
- executor.execute(listener, event);
-- }
-+ //} // Purpur
- }
-
- @Override
-diff --git a/src/main/java/co/aikar/timings/Timing.java b/src/main/java/co/aikar/timings/Timing.java
-index 4195efcfe044618052bb03dea34a4fb2ca7c44f0..8709c955bac34bc546a8e022cfac808bc61ee793 100644
---- a/src/main/java/co/aikar/timings/Timing.java
-+++ b/src/main/java/co/aikar/timings/Timing.java
-@@ -39,6 +39,7 @@ public interface Timing extends AutoCloseable {
- * @return Timing
- */
- @NotNull
-+ @io.papermc.paper.annotation.DoNotUse // Purpur
- Timing startTiming();
-
- /**
-@@ -46,6 +47,7 @@ public interface Timing extends AutoCloseable {
- *
- * Will automatically be called when this Timing is used with try-with-resources
- */
-+ @io.papermc.paper.annotation.DoNotUse // Purpur
- void stopTiming();
-
- /**
-@@ -56,6 +58,7 @@ public interface Timing extends AutoCloseable {
- * @return Timing
- */
- @NotNull
-+ @io.papermc.paper.annotation.DoNotUse // Purpur
- Timing startTimingIfSync();
-
- /**
-@@ -65,12 +68,14 @@ public interface Timing extends AutoCloseable {
- *
- * But only if we are on the primary thread.
- */
-+ @io.papermc.paper.annotation.DoNotUse // Purpur
- void stopTimingIfSync();
-
- /**
- * @deprecated Doesn't do anything - Removed
- */
- @Deprecated
-+ @io.papermc.paper.annotation.DoNotUse // Purpur
- void abort();
-
- /**
-@@ -82,5 +87,6 @@ public interface Timing extends AutoCloseable {
- TimingHandler getTimingHandler();
-
- @Override
-+ @io.papermc.paper.annotation.DoNotUse // Purpur
- void close();
- }
-diff --git a/src/main/java/co/aikar/timings/Timings.java b/src/main/java/co/aikar/timings/Timings.java
-index 95b7cdf0677ef71e6885fa78aa5c75bb500f5f53..27a02f0c3261067d8e4ee6169c62cecbbfe50d42 100644
---- a/src/main/java/co/aikar/timings/Timings.java
-+++ b/src/main/java/co/aikar/timings/Timings.java
-@@ -124,7 +124,7 @@ public final class Timings {
- @NotNull
- public static Timing ofStart(@NotNull Plugin plugin, @NotNull String name, @Nullable Timing groupHandler) {
- Timing timing = of(plugin, name, groupHandler);
-- timing.startTiming();
-+ //timing.startTiming(); // Purpur
- return timing;
- }
-
-@@ -146,7 +146,7 @@ public final class Timings {
- */
- public static void setTimingsEnabled(boolean enabled) {
- if (enabled && !warnedAboutDeprecationOnEnable) {
-- Bukkit.getLogger().severe(PlainTextComponentSerializer.plainText().serialize(deprecationMessage()));
-+ //Bukkit.getLogger().severe(PlainTextComponentSerializer.plainText().serialize(deprecationMessage()));
- warnedAboutDeprecationOnEnable = true;
- }
- }
-diff --git a/src/main/java/co/aikar/timings/TimingsCommand.java b/src/main/java/co/aikar/timings/TimingsCommand.java
-index b83e5ff7ada8771fdf27ba9807c77ba6a4ce12da..f28eec202237461cb489a2b13289d813381a25bc 100644
---- a/src/main/java/co/aikar/timings/TimingsCommand.java
-+++ b/src/main/java/co/aikar/timings/TimingsCommand.java
-@@ -47,7 +47,7 @@ public class TimingsCommand extends BukkitCommand {
- public TimingsCommand(@NotNull String name) {
- super(name);
- this.description = "Manages Spigot Timings data to see performance of the server.";
-- this.usageMessage = "/timings ";
-+ this.usageMessage = "/timings";// "; // Purpur
- this.setPermission("bukkit.command.timings");
- }
-
-@@ -57,7 +57,10 @@ public class TimingsCommand extends BukkitCommand {
- return true;
- }
- if (true) {
-- sender.sendMessage(Timings.deprecationMessage());
-+ net.kyori.adventure.text.minimessage.MiniMessage mm = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage();
-+ sender.sendMessage(mm.deserialize("Purpur has removed timings to save your performance. Please use /spark instead"));
-+ sender.sendMessage(mm.deserialize("For more information, view its documentation at"));
-+ sender.sendMessage(mm.deserialize("https://spark.lucko.me/docs/Command-Usage")); // Purpur
- return true;
- }
- if (args.length < 1) {
-@@ -118,7 +121,7 @@ public class TimingsCommand extends BukkitCommand {
- Preconditions.checkNotNull(args, "Arguments cannot be null");
- Preconditions.checkNotNull(alias, "Alias cannot be null");
-
-- if (args.length == 1) {
-+ if (false && args.length == 1) { // Purpur
- return StringUtil.copyPartialMatches(args[0], TIMINGS_SUBCOMMANDS,
- new ArrayList(TIMINGS_SUBCOMMANDS.size()));
- }
-diff --git a/src/main/java/org/bukkit/command/SimpleCommandMap.java b/src/main/java/org/bukkit/command/SimpleCommandMap.java
-index a09b5458191eb5df4787859b72a37fa1fa2bffba..7740ad53796d08584bb0110f99af5639993e4d71 100644
---- a/src/main/java/org/bukkit/command/SimpleCommandMap.java
-+++ b/src/main/java/org/bukkit/command/SimpleCommandMap.java
-@@ -173,10 +173,10 @@ public class SimpleCommandMap implements CommandMap {
- // Paper end
-
- try {
-- try (co.aikar.timings.Timing ignored = target.timings.startTiming()) { // Paper - use try with resources
-+ //try (co.aikar.timings.Timing ignored = target.timings.startTiming()) { // Paper - use try with resources // Purpur
- // 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, parsedArgs); // Purpur
-- } // target.timings.stopTiming(); // Spigot // Paper
-+ //} // target.timings.stopTiming(); // Spigot // Paper // Purpur
- } catch (CommandException ex) {
- server.getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerCommandException(ex, target, sender, args))); // Paper
- //target.timings.stopTiming(); // Spigot // Paper
-diff --git a/src/main/java/org/spigotmc/CustomTimingsHandler.java b/src/main/java/org/spigotmc/CustomTimingsHandler.java
-index 12946bd55fcf7c40d39081779a7fa30049ee6165..9c2d605c50cbf9aefa56ec209df9f6cea1392e89 100644
---- a/src/main/java/org/spigotmc/CustomTimingsHandler.java
-+++ b/src/main/java/org/spigotmc/CustomTimingsHandler.java
-@@ -61,7 +61,7 @@ public final class CustomTimingsHandler {
- handler = timing;
- }
-
-- public void startTiming() { handler.startTiming(); }
-- public void stopTiming() { handler.stopTiming(); }
-+ public void startTiming() { /*handler.startTiming();*/ } // Purpur
-+ public void stopTiming() { /*handler.stopTiming();*/ } // Purpur
-
- }
diff --git a/patches/api/0036-Add-Bee-API.patch b/patches/api/0036-Add-Bee-API.patch
deleted file mode 100644
index 940b0126c9..0000000000
--- a/patches/api/0036-Add-Bee-API.patch
+++ /dev/null
@@ -1,178 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: SageSphinx63920
-Date: Mon, 25 Jul 2022 19:33:49 +0200
-Subject: [PATCH] Add Bee API
-
-
-diff --git a/src/main/java/org/purpurmc/purpur/event/entity/BeeFoundFlowerEvent.java b/src/main/java/org/purpurmc/purpur/event/entity/BeeFoundFlowerEvent.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..7f631a41abee4640a37339a7896ce96e61747735
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/entity/BeeFoundFlowerEvent.java
-@@ -0,0 +1,48 @@
-+package org.purpurmc.purpur.event.entity;
-+
-+import org.bukkit.Location;
-+import org.bukkit.entity.Bee;
-+import org.bukkit.event.HandlerList;
-+import org.bukkit.event.entity.EntityEvent;
-+import org.jetbrains.annotations.ApiStatus;
-+import org.jspecify.annotations.NullMarked;
-+import org.jspecify.annotations.Nullable;
-+
-+/**
-+ * Called when a bee targets a flower
-+ */
-+@NullMarked
-+public class BeeFoundFlowerEvent extends EntityEvent {
-+ private static final HandlerList handlers = new HandlerList();
-+ private final Location location;
-+
-+ @ApiStatus.Internal
-+ public BeeFoundFlowerEvent(Bee bee, @Nullable Location location) {
-+ super(bee);
-+ this.location = location;
-+ }
-+
-+ @Override
-+ public Bee getEntity() {
-+ return (Bee) super.getEntity();
-+ }
-+
-+ /**
-+ * Returns the location of the flower that the bee targets
-+ *
-+ * @return The location of the flower
-+ */
-+ @Nullable
-+ public Location getLocation() {
-+ return location;
-+ }
-+
-+ @Override
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
-diff --git a/src/main/java/org/purpurmc/purpur/event/entity/BeeStartedPollinatingEvent.java b/src/main/java/org/purpurmc/purpur/event/entity/BeeStartedPollinatingEvent.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..e260145d6dc556bbe9e3654296b965c4e393084d
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/entity/BeeStartedPollinatingEvent.java
-@@ -0,0 +1,46 @@
-+package org.purpurmc.purpur.event.entity;
-+
-+import org.bukkit.Location;
-+import org.bukkit.entity.Bee;
-+import org.bukkit.event.HandlerList;
-+import org.bukkit.event.entity.EntityEvent;
-+import org.jetbrains.annotations.ApiStatus;
-+import org.jspecify.annotations.NullMarked;
-+
-+/**
-+ * Called when a bee starts pollinating
-+ */
-+@NullMarked
-+public class BeeStartedPollinatingEvent extends EntityEvent {
-+ private static final HandlerList handlers = new HandlerList();
-+ private final Location location;
-+
-+ @ApiStatus.Internal
-+ public BeeStartedPollinatingEvent(Bee bee, Location location) {
-+ super(bee);
-+ this.location = location;
-+ }
-+
-+ @Override
-+ public Bee getEntity() {
-+ return (Bee) super.getEntity();
-+ }
-+
-+ /**
-+ * Returns the location of the flower that the bee pollinates
-+ *
-+ * @return The location of the flower
-+ */
-+ public Location getLocation() {
-+ return this.location;
-+ }
-+
-+ @Override
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
-diff --git a/src/main/java/org/purpurmc/purpur/event/entity/BeeStopPollinatingEvent.java b/src/main/java/org/purpurmc/purpur/event/entity/BeeStopPollinatingEvent.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..8b2b351d620c749cdf58d7e824b55cf55578fde6
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/entity/BeeStopPollinatingEvent.java
-@@ -0,0 +1,60 @@
-+package org.purpurmc.purpur.event.entity;
-+
-+import org.bukkit.Location;
-+import org.bukkit.entity.Bee;
-+import org.bukkit.event.HandlerList;
-+import org.bukkit.event.entity.EntityEvent;
-+import org.jetbrains.annotations.ApiStatus;
-+import org.jspecify.annotations.NullMarked;
-+import org.jspecify.annotations.Nullable;
-+
-+/**
-+ * Called when a bee stops pollinating
-+ */
-+@NullMarked
-+public class BeeStopPollinatingEvent extends EntityEvent {
-+ private static final HandlerList handlers = new HandlerList();
-+ private final Location location;
-+ private final boolean success;
-+
-+ @ApiStatus.Internal
-+ public BeeStopPollinatingEvent(Bee bee, @Nullable Location location, boolean success) {
-+ super(bee);
-+ this.location = location;
-+ this.success = success;
-+ }
-+
-+ @Override
-+ public Bee getEntity() {
-+ return (Bee) super.getEntity();
-+ }
-+
-+ /**
-+ * Returns the location of the flower that the bee stopped pollinating
-+ *
-+ * @return The location of the flower
-+ */
-+ @Nullable
-+ public Location getLocation() {
-+ return location;
-+ }
-+
-+ /**
-+ * Returns whether the bee successfully pollinated the flower
-+ *
-+ * @return True if the pollination was successful
-+ */
-+ public boolean wasSuccessful() {
-+ return success;
-+ }
-+
-+
-+ @Override
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
diff --git a/patches/api/0037-Debug-Marker-API.patch b/patches/api/0037-Debug-Marker-API.patch
deleted file mode 100644
index 470a7bff7c..0000000000
--- a/patches/api/0037-Debug-Marker-API.patch
+++ /dev/null
@@ -1,341 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: YouHaveTrouble
-Date: Sat, 23 Jul 2022 14:40:17 +0200
-Subject: [PATCH] Debug Marker API
-
-
-diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java
-index 79528ad4408eac53754ccc810445970e263a1a32..db453d04efb00baaeabb904a7bd1b99dd0a50735 100644
---- a/src/main/java/org/bukkit/Bukkit.java
-+++ b/src/main/java/org/bukkit/Bukkit.java
-@@ -3006,5 +3006,89 @@ public final class Bukkit {
- public static void removeFuel(@NotNull Material material) {
- server.removeFuel(material);
- }
-+
-+ /**
-+ * Creates debug block highlight on specified block location and show it to all players on the server.
-+ *
-+ * Clients may be inconsistent in displaying it.
-+ * @param location Location to highlight
-+ * @param duration Duration for highlight to show in milliseconds
-+ */
-+ public static void sendBlockHighlight(@NotNull Location location, int duration) {
-+ server.sendBlockHighlight(location, duration);
-+ }
-+
-+ /**
-+ * Creates debug block highlight on specified block location and show it to all players on the server.
-+ *
-+ * Clients may be inconsistent in displaying it.
-+ * @param location Location to highlight
-+ * @param duration Duration for highlight to show in milliseconds
-+ * @param argb Color of the highlight. ARGB int. Will be ignored on some versions of vanilla client
-+ */
-+ public static void sendBlockHighlight(@NotNull Location location, int duration, int argb) {
-+ server.sendBlockHighlight(location, duration, argb);
-+ }
-+
-+ /**
-+ * Creates debug block highlight on specified block location and show it to all players on the server.
-+ *
-+ * Clients may be inconsistent in displaying it.
-+ * @param location Location to highlight
-+ * @param duration Duration for highlight to show in milliseconds
-+ * @param text Text to show above the highlight
-+ */
-+ public static void sendBlockHighlight(@NotNull Location location, int duration, @NotNull String text) {
-+ server.sendBlockHighlight(location, duration, text);
-+ }
-+
-+ /**
-+ * Creates debug block highlight on specified block location and show it to all players on the server.
-+ *
-+ * Clients may be inconsistent in displaying it.
-+ * @param location Location to highlight
-+ * @param duration Duration for highlight to show in milliseconds
-+ * @param text Text to show above the highlight
-+ * @param argb Color of the highlight. ARGB int. Will be ignored on some versions of vanilla client
-+ */
-+ public static void sendBlockHighlight(@NotNull Location location, int duration, @NotNull String text, int argb) {
-+ server.sendBlockHighlight(location, duration, text, argb);
-+ }
-+
-+ /**
-+ * Creates debug block highlight on specified block location and show it to all players on the server.
-+ *
-+ * Clients may be inconsistent in displaying it.
-+ * @param location Location to highlight
-+ * @param duration Duration for highlight to show in milliseconds
-+ * @param color Color of the highlight. Will be ignored on some versions of vanilla client
-+ * @param transparency Transparency of the highlight
-+ * @throws IllegalArgumentException If transparency is outside 0-255 range
-+ */
-+ public static void sendBlockHighlight(@NotNull Location location, int duration, @NotNull org.bukkit.Color color, int transparency) {
-+ server.sendBlockHighlight(location, duration, color, transparency);
-+ }
-+
-+ /**
-+ * Creates debug block highlight on specified block location and show it to all players on the server.
-+ *
-+ * Clients may be inconsistent in displaying it.
-+ * @param location Location to highlight
-+ * @param duration Duration for highlight to show in milliseconds
-+ * @param text Text to show above the highlight
-+ * @param color Color of the highlight. Will be ignored on some versions of vanilla client
-+ * @param transparency Transparency of the highlight
-+ * @throws IllegalArgumentException If transparency is outside 0-255 range
-+ */
-+ public static void sendBlockHighlight(@NotNull Location location, int duration, @NotNull String text, @NotNull org.bukkit.Color color, int transparency) {
-+ server.sendBlockHighlight(location, duration, text, color, transparency);
-+ }
-+
-+ /**
-+ * Clears all debug block highlights for all players on the server.
-+ */
-+ public static void clearBlockHighlights() {
-+ server.clearBlockHighlights();
-+ }
- // Purpur end
- }
-diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java
-index c1523229138d5d07569c8e564cf27b8276cca153..fcfad39173ecf2573a1ba77236bce8d9f73e02bb 100644
---- a/src/main/java/org/bukkit/Server.java
-+++ b/src/main/java/org/bukkit/Server.java
-@@ -2649,5 +2649,75 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
- * @param material The material that will no longer be a fuel
- */
- public void removeFuel(@NotNull Material material);
-+
-+ /**
-+ * Creates debug block highlight on specified block location and show it to all players on the server.
-+ *
-+ * Clients may be inconsistent in displaying it.
-+ * @param location Location to highlight
-+ * @param duration Duration for highlight to show in milliseconds
-+ */
-+ void sendBlockHighlight(@NotNull Location location, int duration);
-+
-+ /**
-+ * Creates debug block highlight on specified block location and show it to all players on the server.
-+ *
-+ * Clients may be inconsistent in displaying it.
-+ * @param location Location to highlight
-+ * @param duration Duration for highlight to show in milliseconds
-+ * @param argb Color of the highlight. ARGB int. Will be ignored on some versions of vanilla client
-+ */
-+ void sendBlockHighlight(@NotNull Location location, int duration, int argb);
-+
-+ /**
-+ * Creates debug block highlight on specified block location and show it to all players on the server.
-+ *
-+ * Clients may be inconsistent in displaying it.
-+ * @param location Location to highlight
-+ * @param duration Duration for highlight to show in milliseconds
-+ * @param text Text to show above the highlight
-+ */
-+ void sendBlockHighlight(@NotNull Location location, int duration, @NotNull String text);
-+
-+ /**
-+ * Creates debug block highlight on specified block location and show it to all players on the server.
-+ *
-+ * Clients may be inconsistent in displaying it.
-+ * @param location Location to highlight
-+ * @param duration Duration for highlight to show in milliseconds
-+ * @param text Text to show above the highlight
-+ * @param argb Color of the highlight. ARGB int. Will be ignored on some versions of vanilla client
-+ */
-+ void sendBlockHighlight(@NotNull Location location, int duration, @NotNull String text, int argb);
-+
-+ /**
-+ * Creates debug block highlight on specified block location and show it to all players on the server.
-+ *
-+ * Clients may be inconsistent in displaying it.
-+ * @param location Location to highlight
-+ * @param duration Duration for highlight to show in milliseconds
-+ * @param color Color of the highlight. Will be ignored on some versions of vanilla client
-+ * @param transparency Transparency of the highlight
-+ * @throws IllegalArgumentException If transparency is outside 0-255 range
-+ */
-+ void sendBlockHighlight(@NotNull Location location, int duration, @NotNull org.bukkit.Color color, int transparency);
-+
-+ /**
-+ * Creates debug block highlight on specified block location and show it to all players on the server.
-+ *
-+ * Clients may be inconsistent in displaying it.
-+ * @param location Location to highlight
-+ * @param duration Duration for highlight to show in milliseconds
-+ * @param text Text to show above the highlight
-+ * @param color Color of the highlight. Will be ignored on some versions of vanilla client
-+ * @param transparency Transparency of the highlight
-+ * @throws IllegalArgumentException If transparency is outside 0-255 range
-+ */
-+ void sendBlockHighlight(@NotNull Location location, int duration, @NotNull String text, @NotNull org.bukkit.Color color, int transparency);
-+
-+ /**
-+ * Clears all debug block highlights for all players on the server.
-+ */
-+ void clearBlockHighlights();
- // Purpur end
- }
-diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java
-index 3d1e2a6804980c091fe2a66a6810564d317d339f..5f7de23e419175e55459df760c7190639ea39f18 100644
---- a/src/main/java/org/bukkit/World.java
-+++ b/src/main/java/org/bukkit/World.java
-@@ -4254,6 +4254,76 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
- * @return The local difficulty
- */
- public float getLocalDifficultyAt(@NotNull Location location);
-+
-+ /**
-+ * Creates debug block highlight on specified block location and show it to all players on this world.
-+ *
-+ * Clients may be inconsistent in displaying it.
-+ * @param location Location to highlight
-+ * @param duration Duration for highlight to show in milliseconds
-+ */
-+ void sendBlockHighlight(@NotNull Location location, int duration);
-+
-+ /**
-+ * Creates debug block highlight on specified block location and show it to all players on this world.
-+ *
-+ * Clients may be inconsistent in displaying it.
-+ * @param location Location to highlight
-+ * @param duration Duration for highlight to show in milliseconds
-+ * @param argb Color of the highlight. ARGB int. Will be ignored on some versions of vanilla client
-+ */
-+ void sendBlockHighlight(@NotNull Location location, int duration, int argb);
-+
-+ /**
-+ * Creates debug block highlight on specified block location and show it to all players on this world.
-+ *
-+ * Clients may be inconsistent in displaying it.
-+ * @param location Location to highlight
-+ * @param duration Duration for highlight to show in milliseconds
-+ * @param text Text to show above the highlight
-+ */
-+ void sendBlockHighlight(@NotNull Location location, int duration, @NotNull String text);
-+
-+ /**
-+ * Creates debug block highlight on specified block location and show it to all players on this world.
-+ *
-+ * Clients may be inconsistent in displaying it.
-+ * @param location Location to highlight
-+ * @param duration Duration for highlight to show in milliseconds
-+ * @param text Text to show above the highlight
-+ * @param argb Color of the highlight. ARGB int. Will be ignored on some versions of vanilla client
-+ */
-+ void sendBlockHighlight(@NotNull Location location, int duration, @NotNull String text, int argb);
-+
-+ /**
-+ * Creates debug block highlight on specified block location and show it to all players on this world.
-+ *
-+ * Clients may be inconsistent in displaying it.
-+ * @param location Location to highlight
-+ * @param duration Duration for highlight to show in milliseconds
-+ * @param color Color of the highlight. Will be ignored on some versions of vanilla client
-+ * @param transparency Transparency of the highlight
-+ * @throws IllegalArgumentException If transparency is outside 0-255 range
-+ */
-+ void sendBlockHighlight(@NotNull Location location, int duration, @NotNull org.bukkit.Color color, int transparency);
-+
-+ /**
-+ * Creates debug block highlight on specified block location and show it to all players on this world.
-+ *
-+ * Clients may be inconsistent in displaying it.
-+ * @param location Location to highlight
-+ * @param duration Duration for highlight to show in milliseconds
-+ * @param text Text to show above the highlight
-+ * @param color Color of the highlight. Will be ignored on some versions of vanilla client
-+ * @param transparency Transparency of the highlight
-+ * @throws IllegalArgumentException If transparency is outside 0-255 range
-+ */
-+ void sendBlockHighlight(@NotNull Location location, int duration, @NotNull String text, @NotNull org.bukkit.Color color, int transparency);
-+
-+ /**
-+ * Clears all debug block highlights for all players on this world.
-+ */
-+ void clearBlockHighlights();
- // Purpur end
-
- /**
-diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
-index ed0f892ed987419809fe1a0390b6278c99659919..8cedbba4e65b0a8d3a17c6960033e2a324c64bc9 100644
---- a/src/main/java/org/bukkit/entity/Player.java
-+++ b/src/main/java/org/bukkit/entity/Player.java
-@@ -3939,5 +3939,75 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
- * @deprecated Use {@link #resetIdleDuration()} instead
- */
- void resetIdleTimer();
-+
-+ /**
-+ * Creates debug block highlight on specified block location and show it to this player.
-+ *
-+ * Clients may be inconsistent in displaying it.
-+ * @param location Location to highlight
-+ * @param duration Duration for highlight to show in milliseconds
-+ */
-+ void sendBlockHighlight(@NotNull Location location, int duration);
-+
-+ /**
-+ * Creates debug block highlight on specified block location and show it to this player.
-+ *
-+ * Clients may be inconsistent in displaying it.
-+ * @param location Location to highlight
-+ * @param duration Duration for highlight to show in milliseconds
-+ * @param argb Color of the highlight. ARGB int. Will be ignored on some versions of vanilla client
-+ */
-+ void sendBlockHighlight(@NotNull Location location, int duration, int argb);
-+
-+ /**
-+ * Creates debug block highlight on specified block location and show it to this player.
-+ *
-+ * Clients may be inconsistent in displaying it.
-+ * @param location Location to highlight
-+ * @param duration Duration for highlight to show in milliseconds
-+ * @param text Text to show above the highlight
-+ */
-+ void sendBlockHighlight(@NotNull Location location, int duration, @NotNull String text);
-+
-+ /**
-+ * Creates debug block highlight on specified block location and show it to this player.
-+ *
-+ * Clients may be inconsistent in displaying it.
-+ * @param location Location to highlight
-+ * @param duration Duration for highlight to show in milliseconds
-+ * @param text Text to show above the highlight
-+ * @param argb Color of the highlight. ARGB int. Will be ignored on some versions of vanilla client
-+ */
-+ void sendBlockHighlight(@NotNull Location location, int duration, @NotNull String text, int argb);
-+
-+ /**
-+ * Creates debug block highlight on specified block location and show it to this player.
-+ *
-+ * Clients may be inconsistent in displaying it.
-+ * @param location Location to highlight
-+ * @param duration Duration for highlight to show in milliseconds
-+ * @param color Color of the highlight. Will be ignored on some versions of vanilla client
-+ * @param transparency Transparency of the highlight
-+ * @throws IllegalArgumentException If transparency is outside 0-255 range
-+ */
-+ void sendBlockHighlight(@NotNull Location location, int duration, @NotNull org.bukkit.Color color, int transparency);
-+
-+ /**
-+ * Creates debug block highlight on specified block location and show it to this player.
-+ *
-+ * Clients may be inconsistent in displaying it.
-+ * @param location Location to highlight
-+ * @param duration Duration for highlight to show in milliseconds
-+ * @param text Text to show above the highlight
-+ * @param color Color of the highlight. Will be ignored on some versions of vanilla client
-+ * @param transparency Transparency of the highlight
-+ * @throws IllegalArgumentException If transparency is outside 0-255 range
-+ */
-+ void sendBlockHighlight(@NotNull Location location, int duration, @NotNull String text, @NotNull org.bukkit.Color color, int transparency);
-+
-+ /**
-+ * Clears all debug block highlights
-+ */
-+ void clearBlockHighlights();
- // Purpur end
- }
diff --git a/patches/api/0038-Add-death-screen-API.patch b/patches/api/0038-Add-death-screen-API.patch
deleted file mode 100644
index 11afb29cd1..0000000000
--- a/patches/api/0038-Add-death-screen-API.patch
+++ /dev/null
@@ -1,36 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: MelnCat
-Date: Fri, 23 Sep 2022 18:35:28 -0700
-Subject: [PATCH] Add death screen API
-
-
-diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
-index 8cedbba4e65b0a8d3a17c6960033e2a324c64bc9..6f7f1fc3db0237021fa1bd0f11fe56b2d6d4f84a 100644
---- a/src/main/java/org/bukkit/entity/Player.java
-+++ b/src/main/java/org/bukkit/entity/Player.java
-@@ -4009,5 +4009,25 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
- * Clears all debug block highlights
- */
- void clearBlockHighlights();
-+
-+ /**
-+ * Sends a player the death screen with a specified death message.
-+ *
-+ * @param message The death message to show the player
-+ */
-+ void sendDeathScreen(@NotNull net.kyori.adventure.text.Component message);
-+
-+ /**
-+ * Sends a player the death screen with a specified death message,
-+ * along with the entity that caused the death.
-+ *
-+ * @param message The death message to show the player
-+ * @param killer The entity that killed the player
-+ * @deprecated Use {@link #sendDeathScreen(net.kyori.adventure.text.Component)} instead, as 1.20 removed the killer ID from the packet.
-+ */
-+ @Deprecated(since = "1.20")
-+ default void sendDeathScreen(@NotNull net.kyori.adventure.text.Component message, @Nullable Entity killer) {
-+ sendDeathScreen(message);
-+ }
- // Purpur end
- }
diff --git a/patches/api/0039-Language-API.patch b/patches/api/0039-Language-API.patch
deleted file mode 100644
index 5dfa484c1e..0000000000
--- a/patches/api/0039-Language-API.patch
+++ /dev/null
@@ -1,72 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: MelnCat
-Date: Sat, 1 Oct 2022 17:08:23 -0700
-Subject: [PATCH] Language API
-
-
-diff --git a/src/main/java/org/purpurmc/purpur/language/Language.java b/src/main/java/org/purpurmc/purpur/language/Language.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..cbdad4cf09c170064a45644efdf7aa0b28608301
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/language/Language.java
-@@ -0,0 +1,60 @@
-+package org.purpurmc.purpur.language;
-+
-+import net.kyori.adventure.translation.Translatable;
-+import org.jspecify.annotations.NullMarked;
-+import org.jspecify.annotations.Nullable;
-+
-+/**
-+ * Represents a language that can translate translation keys
-+ */
-+@NullMarked
-+public abstract class Language {
-+ private static @Nullable Language language;
-+
-+ /**
-+ * Returns the default language of the server
-+ */
-+ @Nullable
-+ public static Language getLanguage() {
-+ return language;
-+ }
-+
-+ public static void setLanguage(Language language) {
-+ if (Language.language != null) {
-+ throw new UnsupportedOperationException("Cannot redefine singleton Language");
-+ }
-+ Language.language = language;
-+ }
-+
-+ /**
-+ * Checks if a certain translation key is translatable with this language
-+ * @param key The translation key
-+ * @return Whether this language can translate the key
-+ */
-+ abstract public boolean has(String key);
-+
-+ /**
-+ * Checks if a certain translation key is translatable with this language
-+ * @param key The translation key
-+ * @return Whether this language can translate the key
-+ */
-+ public boolean has(Translatable key) {
-+ return has(key.translationKey());
-+ }
-+
-+ /**
-+ * Translates a translation key to this language
-+ * @param key The translation key
-+ * @return The translated key, or the translation key if it couldn't be translated
-+ */
-+ abstract public String getOrDefault(String key);
-+
-+ /**
-+ * Translates a translation key to this language
-+ * @param key The translation key
-+ * @return The translated key, or the translation key if it couldn't be translated
-+ */
-+ public String getOrDefault(Translatable key) {
-+ return getOrDefault(key.translationKey());
-+ }
-+}
diff --git a/patches/api/0040-Add-log-suppression-for-LibraryLoader.patch b/patches/api/0040-Add-log-suppression-for-LibraryLoader.patch
deleted file mode 100644
index f2b073e13f..0000000000
--- a/patches/api/0040-Add-log-suppression-for-LibraryLoader.patch
+++ /dev/null
@@ -1,46 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Krakenied
-Date: Fri, 14 Oct 2022 23:11:27 +0200
-Subject: [PATCH] Add log suppression for LibraryLoader
-
-
-diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
-index b412aaf08901d169ac9fc89b36f9d6ccb95c53d3..43a0de7394eb786e9c87611003cc820424979205 100644
---- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
-+++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
-@@ -55,6 +55,7 @@ public final class JavaPluginLoader implements PluginLoader {
- private final Pattern[] fileFilters = new Pattern[]{Pattern.compile("\\.jar$")};
- private final List loaders = new CopyOnWriteArrayList();
- private final LibraryLoader libraryLoader;
-+ public static boolean SuppressLibraryLoaderLogger = false; // Purpur
-
- /**
- * This class was not meant to be constructed explicitly
-diff --git a/src/main/java/org/bukkit/plugin/java/LibraryLoader.java b/src/main/java/org/bukkit/plugin/java/LibraryLoader.java
-index c66252802c51174bc26f266cb5cdecdd856ff220..97f580fccd06a8db5f592a53c8b95a7a6159adac 100644
---- a/src/main/java/org/bukkit/plugin/java/LibraryLoader.java
-+++ b/src/main/java/org/bukkit/plugin/java/LibraryLoader.java
-@@ -68,6 +68,7 @@ public class LibraryLoader
- @Override
- public void transferStarted(@NotNull TransferEvent event) throws TransferCancelledException
- {
-+ if (!JavaPluginLoader.SuppressLibraryLoaderLogger) // Purpur
- logger.log( Level.INFO, "Downloading {0}", event.getResource().getRepositoryUrl() + event.getResource().getResourceName() );
- }
- } );
-@@ -94,6 +95,7 @@ public class LibraryLoader
- {
- return null;
- }
-+ if (!JavaPluginLoader.SuppressLibraryLoaderLogger) // Purpur
- logger.log( Level.INFO, "[{0}] Loading {1} libraries... please wait", new Object[]
- {
- java.util.Objects.requireNonNullElseGet(desc.getPrefix(), desc::getName), desc.getLibraries().size() // Paper - use configured log prefix
-@@ -144,6 +146,7 @@ public class LibraryLoader
- }
-
- jarFiles.add( url );
-+ if (!JavaPluginLoader.SuppressLibraryLoaderLogger) // Purpur
- logger.log( Level.INFO, "[{0}] Loaded library {1}", new Object[]
- {
- java.util.Objects.requireNonNullElseGet(desc.getPrefix(), desc::getName), file // Paper - use configured log prefix
diff --git a/patches/api/0041-Fire-Immunity-API.patch b/patches/api/0041-Fire-Immunity-API.patch
deleted file mode 100644
index 5de466a439..0000000000
--- a/patches/api/0041-Fire-Immunity-API.patch
+++ /dev/null
@@ -1,29 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Racci <90304606+DaRacci@users.noreply.github.com>
-Date: Fri, 4 Feb 2022 16:09:47 +1100
-Subject: [PATCH] Fire Immunity API
-
-
-diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java
-index a4136da96f0759ca137a86f2518af58bccb6b632..076fe310d500ebb52e705a3a69e895061702f470 100644
---- a/src/main/java/org/bukkit/entity/Entity.java
-+++ b/src/main/java/org/bukkit/entity/Entity.java
-@@ -1209,5 +1209,18 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent
- * @return True if in daylight
- */
- boolean isInDaylight();
-+
-+ /**
-+ * Checks if the entity is fire immune
-+ *
-+ * @return True if fire immune
-+ */
-+ boolean isImmuneToFire();
-+
-+ /**
-+ * Sets if the entity is fire immune
-+ * Set this to null to restore the entity type default
-+ */
-+ void setImmuneToFire(@Nullable Boolean fireImmune);
- // Purpur end
- }
diff --git a/patches/api/0042-Added-goat-ram-event.patch b/patches/api/0042-Added-goat-ram-event.patch
deleted file mode 100644
index d3c3f06d3a..0000000000
--- a/patches/api/0042-Added-goat-ram-event.patch
+++ /dev/null
@@ -1,70 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: SageSphinx63920
-Date: Sat, 29 Oct 2022 00:06:05 +0200
-Subject: [PATCH] Added goat ram event
-
-
-diff --git a/src/main/java/org/purpurmc/purpur/event/entity/GoatRamEntityEvent.java b/src/main/java/org/purpurmc/purpur/event/entity/GoatRamEntityEvent.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..f0a7fe694db145294ff93d320382d1baecc68702
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/entity/GoatRamEntityEvent.java
-@@ -0,0 +1,58 @@
-+package org.purpurmc.purpur.event.entity;
-+
-+import org.bukkit.entity.Goat;
-+import org.bukkit.entity.LivingEntity;
-+import org.bukkit.event.Cancellable;
-+import org.bukkit.event.HandlerList;
-+import org.bukkit.event.entity.EntityEvent;
-+import org.jetbrains.annotations.ApiStatus;
-+import org.jspecify.annotations.NullMarked;
-+
-+/**
-+ * Called when a goat rams an entity
-+ */
-+@NullMarked
-+public class GoatRamEntityEvent extends EntityEvent implements Cancellable {
-+ private static final HandlerList handlers = new HandlerList();
-+ private final LivingEntity rammedEntity;
-+ private boolean cancelled;
-+
-+ @ApiStatus.Internal
-+ public GoatRamEntityEvent(Goat goat, LivingEntity rammedEntity) {
-+ super(goat);
-+ this.rammedEntity = rammedEntity;
-+ }
-+
-+ /**
-+ * Returns the entity that was rammed by the goat
-+ *
-+ * @return The rammed entity
-+ */
-+ public LivingEntity getRammedEntity() {
-+ return this.rammedEntity;
-+ }
-+
-+ @Override
-+ public Goat getEntity() {
-+ return (Goat) super.getEntity();
-+ }
-+
-+ @Override
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+
-+ @Override
-+ public boolean isCancelled() {
-+ return this.cancelled;
-+ }
-+
-+ @Override
-+ public void setCancelled(boolean cancel) {
-+ this.cancelled = cancel;
-+ }
-+}
diff --git a/patches/api/0043-Add-PreExplodeEvents.patch b/patches/api/0043-Add-PreExplodeEvents.patch
deleted file mode 100644
index 2699b3c6da..0000000000
--- a/patches/api/0043-Add-PreExplodeEvents.patch
+++ /dev/null
@@ -1,140 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: SageSphinx63920
-Date: Mon, 26 Dec 2022 23:40:13 +0100
-Subject: [PATCH] Add PreExplodeEvents
-
-
-diff --git a/src/main/java/org/purpurmc/purpur/event/PreBlockExplodeEvent.java b/src/main/java/org/purpurmc/purpur/event/PreBlockExplodeEvent.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..4b4d32c58224e1208f14024ca214078a37550bb5
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/PreBlockExplodeEvent.java
-@@ -0,0 +1,56 @@
-+package org.purpurmc.purpur.event;
-+
-+import org.bukkit.ExplosionResult;
-+import org.bukkit.block.Block;
-+import org.bukkit.block.BlockState;
-+import org.bukkit.event.Cancellable;
-+import org.bukkit.event.HandlerList;
-+import org.bukkit.event.block.BlockExplodeEvent;
-+import org.jetbrains.annotations.ApiStatus;
-+import java.util.Collections;
-+import org.jspecify.annotations.NullMarked;
-+
-+/**
-+ * Called before a block's explosion is processed
-+ */
-+@NullMarked
-+public class PreBlockExplodeEvent extends BlockExplodeEvent implements Cancellable {
-+ private static final HandlerList handlers = new HandlerList();
-+ private boolean cancelled;
-+ private final float yield;
-+
-+ @ApiStatus.Internal
-+ public PreBlockExplodeEvent(final Block what, final float yield, BlockState explodedBlockState, ExplosionResult result) {
-+ super(what, explodedBlockState, Collections.emptyList(), yield, result);
-+ this.yield = yield;
-+ this.cancelled = false;
-+ }
-+
-+ /**
-+ * Returns the percentage of blocks to drop from this explosion
-+ *
-+ * @return The yield.
-+ */
-+ public float getYield() {
-+ return yield;
-+ }
-+
-+ @Override
-+ public boolean isCancelled() {
-+ return this.cancelled;
-+ }
-+
-+ @Override
-+ public void setCancelled(boolean cancel) {
-+ this.cancelled = cancel;
-+ }
-+
-+ @Override
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
-diff --git a/src/main/java/org/purpurmc/purpur/event/entity/PreEntityExplodeEvent.java b/src/main/java/org/purpurmc/purpur/event/entity/PreEntityExplodeEvent.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..d56fb066455007cc710f7ba34ba722af6e89bc1d
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/entity/PreEntityExplodeEvent.java
-@@ -0,0 +1,66 @@
-+package org.purpurmc.purpur.event.entity;
-+
-+import org.bukkit.ExplosionResult;
-+import org.bukkit.Location;
-+import org.bukkit.event.Cancellable;
-+import org.bukkit.event.HandlerList;
-+import org.bukkit.event.entity.EntityExplodeEvent;
-+import org.jetbrains.annotations.ApiStatus;
-+import java.util.Collections;
-+import org.jspecify.annotations.NullMarked;
-+
-+/**
-+ * Called before an entity's explosion is processed
-+ */
-+@NullMarked
-+public class PreEntityExplodeEvent extends EntityExplodeEvent implements Cancellable {
-+ private static final HandlerList handlers = new HandlerList();
-+ private boolean cancelled;
-+ private final float yield;
-+ private final Location location;
-+
-+ @ApiStatus.Internal
-+ public PreEntityExplodeEvent(org.bukkit.entity.Entity what, final Location location, final float yield, ExplosionResult result) {
-+ super(what, location, Collections.emptyList(), yield, result);
-+ this.cancelled = false;
-+ this.yield = yield;
-+ this.location = location;
-+ }
-+
-+ /**
-+ * Returns the percentage of blocks to drop from this explosion
-+ *
-+ * @return The yield.
-+ */
-+ public float getYield() {
-+ return yield;
-+ }
-+
-+ /**
-+ * Returns the location where the explosion happened.
-+ *
-+ * @return The location of the explosion
-+ */
-+ public Location getLocation() {
-+ return location;
-+ }
-+
-+ @Override
-+ public boolean isCancelled() {
-+ return this.cancelled;
-+ }
-+
-+ @Override
-+ public void setCancelled(boolean cancel) {
-+ this.cancelled = cancel;
-+ }
-+
-+ @Override
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
diff --git a/patches/api/0044-Stored-Bee-API.patch b/patches/api/0044-Stored-Bee-API.patch
deleted file mode 100644
index e155a6a0fb..0000000000
--- a/patches/api/0044-Stored-Bee-API.patch
+++ /dev/null
@@ -1,93 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: EOT3000
-Date: Sat, 10 Jun 2023 20:27:14 -0400
-Subject: [PATCH] Stored Bee API
-
-
-diff --git a/src/main/java/org/bukkit/block/EntityBlockStorage.java b/src/main/java/org/bukkit/block/EntityBlockStorage.java
-index 739911cda33b373f99df627a3a378b37d7d461aa..51e78c22cd021722b963fe31d1d9175d141add1a 100644
---- a/src/main/java/org/bukkit/block/EntityBlockStorage.java
-+++ b/src/main/java/org/bukkit/block/EntityBlockStorage.java
-@@ -47,6 +47,24 @@ public interface EntityBlockStorage extends TileState {
- @NotNull
- List releaseEntities();
-
-+ // Purpur start
-+ /**
-+ * Releases a stored entity, and returns the entity in the world.
-+ *
-+ * @param entity Entity to release
-+ * @return The entity which was released, or null if the stored entity is not in the hive
-+ */
-+ @org.jetbrains.annotations.Nullable
-+ T releaseEntity(@NotNull org.purpurmc.purpur.entity.StoredEntity entity);
-+
-+ /**
-+ * Gets all the entities currently stored in the block.
-+ *
-+ * @return List of all entities which are stored in the block
-+ */
-+ @NotNull
-+ List> getEntities();
-+ //Purpur end
- /**
- * Add an entity to the block.
- *
-diff --git a/src/main/java/org/purpurmc/purpur/entity/StoredEntity.java b/src/main/java/org/purpurmc/purpur/entity/StoredEntity.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..29540d55532197d2381a52ea9222b5785d224ef8
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/entity/StoredEntity.java
-@@ -0,0 +1,52 @@
-+package org.purpurmc.purpur.entity;
-+
-+import org.bukkit.Nameable;
-+import org.bukkit.block.EntityBlockStorage;
-+import org.bukkit.entity.Entity;
-+import org.bukkit.entity.EntityType;
-+import org.bukkit.persistence.PersistentDataHolder;
-+import org.jetbrains.annotations.NotNull;
-+import org.jetbrains.annotations.Nullable;
-+
-+/**
-+ * Represents an entity stored in a block
-+ *
-+ * @see org.bukkit.block.EntityBlockStorage
-+ */
-+public interface StoredEntity extends PersistentDataHolder, Nameable {
-+ /**
-+ * Checks if this entity has been released yet
-+ *
-+ * @return if this entity has been released
-+ */
-+ boolean hasBeenReleased();
-+
-+ /**
-+ * Releases the entity from its stored block
-+ *
-+ * @return the released entity, or null if unsuccessful (including if this entity has already been released)
-+ */
-+ @Nullable
-+ T release();
-+
-+ /**
-+ * Returns the block in which this entity is stored
-+ *
-+ * @return the EntityBlockStorage in which this entity is stored, or null if it has been released
-+ */
-+ @Nullable
-+ EntityBlockStorage getBlockStorage();
-+
-+ /**
-+ * Gets the entity type of this stored entity
-+ *
-+ * @return the type of entity this stored entity represents
-+ */
-+ @NotNull
-+ EntityType getType();
-+
-+ /**
-+ * Writes data to the block entity snapshot. {@link EntityBlockStorage#update()} must be run in order to update the block in game.
-+ */
-+ void update();
-+}
diff --git a/patches/api/0045-Explorer-Map-API.patch b/patches/api/0045-Explorer-Map-API.patch
deleted file mode 100644
index 91241d8f14..0000000000
--- a/patches/api/0045-Explorer-Map-API.patch
+++ /dev/null
@@ -1,23 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: William Blake Galbreath
-Date: Wed, 5 Jul 2023 12:48:08 -0500
-Subject: [PATCH] Explorer Map API
-
-
-diff --git a/src/main/java/org/bukkit/map/MapRenderer.java b/src/main/java/org/bukkit/map/MapRenderer.java
-index cb7040876a99a5a7e49b81684ef0f3b79584c376..22d8f31b1b8a5dbb5ab3275068642937c097abfe 100644
---- a/src/main/java/org/bukkit/map/MapRenderer.java
-+++ b/src/main/java/org/bukkit/map/MapRenderer.java
-@@ -54,4 +54,12 @@ public abstract class MapRenderer {
- */
- public abstract void render(@NotNull MapView map, @NotNull MapCanvas canvas, @NotNull Player player);
-
-+ // Purpur - start
-+ /**
-+ * Check if this is an explorer (aka treasure) map.
-+ *
-+ * @return True if explorer map
-+ */
-+ public abstract boolean isExplorerMap();
-+ // Purpur - end
- }
diff --git a/patches/api/0046-Stonecutter-damage.patch b/patches/api/0046-Stonecutter-damage.patch
deleted file mode 100644
index 1ae8744ad5..0000000000
--- a/patches/api/0046-Stonecutter-damage.patch
+++ /dev/null
@@ -1,20 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: granny
-Date: Sun, 11 Feb 2024 23:07:47 -0800
-Subject: [PATCH] Stonecutter damage
-
-
-diff --git a/src/main/java/org/bukkit/event/entity/EntityDamageEvent.java b/src/main/java/org/bukkit/event/entity/EntityDamageEvent.java
-index d1a5424ff3b289f1c82449ef6d88eb52665df41b..f23b0c250f88926c147af0314b5c4d23c5f8dbae 100644
---- a/src/main/java/org/bukkit/event/entity/EntityDamageEvent.java
-+++ b/src/main/java/org/bukkit/event/entity/EntityDamageEvent.java
-@@ -308,7 +308,8 @@ public class EntityDamageEvent extends EntityEvent implements Cancellable {
- WORLD_BORDER,
- /**
- * Damage caused when an entity contacts a block such as a Cactus,
-- * Dripstone (Stalagmite) or Berry Bush.
-+ * Dripstone (Stalagmite) or Berry Bush. (Stonecutters too if you
-+ * have the Stonecutter damage Purpur feature enabled)
- *
- * Damage: variable
- */
diff --git a/patches/generated-api/0001-Ridables.patch b/patches/generated-api/0001-Ridables.patch
deleted file mode 100644
index 4552c2066c..0000000000
--- a/patches/generated-api/0001-Ridables.patch
+++ /dev/null
@@ -1,23 +0,0 @@
-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/com/destroystokyo/paper/entity/ai/VanillaGoal.java b/com/destroystokyo/paper/entity/ai/VanillaGoal.java
-index f15a7b4471cd31a487467ec7ecf7a186fa887a51..9b9eb256c4f17693e717e87e62a2e61b928f073a 100644
---- a/com/destroystokyo/paper/entity/ai/VanillaGoal.java
-+++ b/com/destroystokyo/paper/entity/ai/VanillaGoal.java
-@@ -441,6 +441,12 @@ public interface VanillaGoal extends Goal {
-
- GoalKey ZOMBIE_ATTACK_TURTLE_EGG = create("zombie_attack_turtle_egg", Zombie.class);
-
-+ // Purpur start
-+ GoalKey MOB_HAS_RIDER = GoalKey.of(Mob.class, NamespacedKey.minecraft("has_rider"));
-+ GoalKey HORSE_HAS_RIDER = GoalKey.of(AbstractHorse.class, NamespacedKey.minecraft("horse_has_rider"));
-+ GoalKey LLAMA_HAS_RIDER = GoalKey.of(Llama.class, NamespacedKey.minecraft("llama_has_rider"));
-+ // Purpur end
-+
- private static GoalKey create(final String key, final Class type) {
- return GoalKey.of(type, NamespacedKey.minecraft(key));
- }
diff --git a/patches/generated-api/0002-Phantoms-attracted-to-crystals-and-crystals-shoot-ph.patch b/patches/generated-api/0002-Phantoms-attracted-to-crystals-and-crystals-shoot-ph.patch
deleted file mode 100644
index 61152fef7d..0000000000
--- a/patches/generated-api/0002-Phantoms-attracted-to-crystals-and-crystals-shoot-ph.patch
+++ /dev/null
@@ -1,19 +0,0 @@
-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/com/destroystokyo/paper/entity/ai/VanillaGoal.java b/com/destroystokyo/paper/entity/ai/VanillaGoal.java
-index 9b9eb256c4f17693e717e87e62a2e61b928f073a..33eed1c791eeaeb4eba538d32ce8516c5d5fc6c0 100644
---- a/com/destroystokyo/paper/entity/ai/VanillaGoal.java
-+++ b/com/destroystokyo/paper/entity/ai/VanillaGoal.java
-@@ -445,6 +445,8 @@ public interface VanillaGoal extends Goal {
- GoalKey MOB_HAS_RIDER = GoalKey.of(Mob.class, NamespacedKey.minecraft("has_rider"));
- GoalKey HORSE_HAS_RIDER = GoalKey.of(AbstractHorse.class, NamespacedKey.minecraft("horse_has_rider"));
- GoalKey LLAMA_HAS_RIDER = GoalKey.of(Llama.class, NamespacedKey.minecraft("llama_has_rider"));
-+ GoalKey FIND_CRYSTAL = GoalKey.of(Phantom.class, NamespacedKey.minecraft("find_crystal"));
-+ GoalKey ORBIT_CRYSTAL = GoalKey.of(Phantom.class, NamespacedKey.minecraft("orbit_crystal"));
- // Purpur end
-
- private static GoalKey create(final String key, final Class type) {
diff --git a/patches/generated-api/0003-Add-option-to-disable-zombie-aggressiveness-towards-.patch b/patches/generated-api/0003-Add-option-to-disable-zombie-aggressiveness-towards-.patch
deleted file mode 100644
index b77789f975..0000000000
--- a/patches/generated-api/0003-Add-option-to-disable-zombie-aggressiveness-towards-.patch
+++ /dev/null
@@ -1,20 +0,0 @@
-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/com/destroystokyo/paper/entity/ai/VanillaGoal.java b/com/destroystokyo/paper/entity/ai/VanillaGoal.java
-index 33eed1c791eeaeb4eba538d32ce8516c5d5fc6c0..9ef2111b60ace1a088c8c3d4707b26c06f14037c 100644
---- a/com/destroystokyo/paper/entity/ai/VanillaGoal.java
-+++ b/com/destroystokyo/paper/entity/ai/VanillaGoal.java
-@@ -447,6 +447,8 @@ public interface VanillaGoal extends Goal {
- GoalKey LLAMA_HAS_RIDER = GoalKey.of(Llama.class, NamespacedKey.minecraft("llama_has_rider"));
- GoalKey FIND_CRYSTAL = GoalKey.of(Phantom.class, NamespacedKey.minecraft("find_crystal"));
- GoalKey ORBIT_CRYSTAL = GoalKey.of(Phantom.class, NamespacedKey.minecraft("orbit_crystal"));
-+ 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
-
- private static GoalKey create(final String key, final Class type) {
diff --git a/patches/generated-api/0004-Rabid-Wolf-API.patch b/patches/generated-api/0004-Rabid-Wolf-API.patch
deleted file mode 100644
index 5096e181af..0000000000
--- a/patches/generated-api/0004-Rabid-Wolf-API.patch
+++ /dev/null
@@ -1,18 +0,0 @@
-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/com/destroystokyo/paper/entity/ai/VanillaGoal.java b/com/destroystokyo/paper/entity/ai/VanillaGoal.java
-index 9ef2111b60ace1a088c8c3d4707b26c06f14037c..6eea8fbe575c4570c2b760c9828f8ec5b81c7202 100644
---- a/com/destroystokyo/paper/entity/ai/VanillaGoal.java
-+++ b/com/destroystokyo/paper/entity/ai/VanillaGoal.java
-@@ -449,6 +449,7 @@ public interface VanillaGoal extends Goal {
- GoalKey ORBIT_CRYSTAL = GoalKey.of(Phantom.class, NamespacedKey.minecraft("orbit_crystal"));
- 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_WOLF = GoalKey.of(Wolf.class, NamespacedKey.minecraft("avoid_rabid_wolf"));
- // Purpur end
-
- private static GoalKey create(final String key, final Class type) {
diff --git a/patches/generated-api/0005-Iron-golem-poppy-calms-anger.patch b/patches/generated-api/0005-Iron-golem-poppy-calms-anger.patch
deleted file mode 100644
index bae54e389e..0000000000
--- a/patches/generated-api/0005-Iron-golem-poppy-calms-anger.patch
+++ /dev/null
@@ -1,18 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: William Blake Galbreath
-Date: Thu, 13 May 2021 21:38:01 -0500
-Subject: [PATCH] Iron golem poppy calms anger
-
-
-diff --git a/com/destroystokyo/paper/entity/ai/VanillaGoal.java b/com/destroystokyo/paper/entity/ai/VanillaGoal.java
-index 6eea8fbe575c4570c2b760c9828f8ec5b81c7202..e7795889133063ac165d5b4783ddcbcbb442f189 100644
---- a/com/destroystokyo/paper/entity/ai/VanillaGoal.java
-+++ b/com/destroystokyo/paper/entity/ai/VanillaGoal.java
-@@ -450,6 +450,7 @@ 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_WOLF = GoalKey.of(Wolf.class, NamespacedKey.minecraft("avoid_rabid_wolf"));
-+ GoalKey RECEIVE_FLOWER = GoalKey.of(IronGolem.class, NamespacedKey.minecraft("receive_flower"));
- // Purpur end
-
- private static GoalKey create(final String key, final Class type) {
diff --git a/patches/server/0002-Purpur-config-files.patch b/patches/server/0002-Purpur-config-files.patch
deleted file mode 100644
index 1a0d459acd..0000000000
--- a/patches/server/0002-Purpur-config-files.patch
+++ /dev/null
@@ -1,532 +0,0 @@
-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 8f62879582195d8ae4f64bd23f752fa133b1c973..be1bb14dca9367b9685841985b6198376986c496 100644
---- a/src/main/java/com/destroystokyo/paper/Metrics.java
-+++ b/src/main/java/com/destroystokyo/paper/Metrics.java
-@@ -592,7 +592,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("Paper", serverUUID, logFailedRequests, Bukkit.getLogger());
-+ Metrics metrics = new Metrics("Purpur", serverUUID, logFailedRequests, Bukkit.getLogger()); // Pufferfish // Purpur - Purpur config files
-
- metrics.addCustomChart(new Metrics.SimplePie("minecraft_version", () -> {
- String minecraftVersion = Bukkit.getVersion();
-@@ -601,16 +601,8 @@ public class Metrics {
- }));
-
- metrics.addCustomChart(new Metrics.SingleLineChart("players", () -> Bukkit.getOnlinePlayers().size()));
-- metrics.addCustomChart(new Metrics.SimplePie("online_mode", () -> Bukkit.getOnlineMode() ? "online" : "offline"));
-- final String paperVersion;
-- final String implVersion = org.bukkit.craftbukkit.Main.class.getPackage().getImplementationVersion();
-- if (implVersion != null) {
-- final String buildOrHash = implVersion.substring(implVersion.lastIndexOf('-') + 1);
-- paperVersion = "git-Paper-%s-%s".formatted(Bukkit.getServer().getMinecraftVersion(), buildOrHash);
-- } else {
-- paperVersion = "unknown";
-- }
-- metrics.addCustomChart(new Metrics.SimplePie("paper_version", () -> paperVersion));
-+ metrics.addCustomChart(new Metrics.SimplePie("online_mode", () -> Bukkit.getOnlineMode() ? "online" : (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() ? "bungee" : "offline"))); // Purpur - Purpur config files
-+ metrics.addCustomChart(new Metrics.SimplePie("purpur_version", () -> (org.bukkit.craftbukkit.Main.class.getPackage().getImplementationVersion() != null) ? org.bukkit.craftbukkit.Main.class.getPackage().getImplementationVersion() : "unknown")); // Purpur - Purpur config files
-
- metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> {
- Map> map = new HashMap<>();
-diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java
-index 13bd145b1e8006a53c22f5dc0c78f29b540c7663..7b2daf47e411362a462019a1612a99c952170200 100644
---- a/src/main/java/net/minecraft/commands/CommandSourceStack.java
-+++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java
-@@ -312,6 +312,30 @@ public class CommandSourceStack implements ExecutionCommandSource io.papermc.paper.adventure.PaperAdventure.asVanilla(message), broadcastToOps);
-+ }
-+ // Purpur end - Purpur config files
-+
- public void sendSuccess(Supplier feedbackSupplier, boolean broadcastToOps) {
- boolean flag1 = this.source.acceptsSuccess() && !this.silent;
- boolean flag2 = broadcastToOps && this.source.shouldInformAdmins() && !this.silent;
-diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
-index 17a158ff6ce6520b69a5a0032ba4c05449dd0cf8..cf63c64b8c2ac148b83325209940713a91b91bad 100644
---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
-+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
-@@ -235,6 +235,15 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
- io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command
- this.server.spark.registerCommandBeforePlugins(this.server); // Paper - spark
- com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics
-+ // Purpur start - Purpur config files
-+ try {
-+ org.purpurmc.purpur.PurpurConfig.init((java.io.File) options.valueOf("purpur-settings"));
-+ } catch (Exception e) {
-+ DedicatedServer.LOGGER.error("Unable to load server configuration", e);
-+ return false;
-+ }
-+ org.purpurmc.purpur.PurpurConfig.registerCommands();
-+ // Purpur end - Purpur config files
- com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now
-
- this.setPvpAllowed(dedicatedserverproperties.pvp);
-diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
-index 27f9d167b5ae9ce5117798ea44324107df59425f..f3c5e076558cd8d7b9d9b3212872f8675670b2dd 100644
---- a/src/main/java/net/minecraft/world/level/Level.java
-+++ b/src/main/java/net/minecraft/world/level/Level.java
-@@ -175,6 +175,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
- // Paper end - add paper world config
-
- public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray
-+ public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur - Purpur config files
- public static BlockPos lastPhysicsProblem; // Spigot
- private org.spigotmc.TickLimiter entityLimiter;
- private org.spigotmc.TickLimiter tileLimiter;
-@@ -843,6 +844,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl
- // Paper end - getblock optimisations - cache world height/sections
- this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot
- this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config
-+ this.purpurConfig = new org.purpurmc.purpur.PurpurWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName(), env); // Purpur - Purpur config files
- this.generator = gen;
- this.world = new CraftWorld((ServerLevel) this, gen, biomeProvider, env);
-
-diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-index 97b5d6ba2b19a7c730730c74175a29157aed1840..cc2f23613644126c3f7506b26db8e6a865f78dde 100644
---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-@@ -1086,6 +1086,7 @@ public final class CraftServer implements Server {
-
- org.spigotmc.SpigotConfig.init((File) this.console.options.valueOf("spigot-settings")); // Spigot
- this.console.paperConfigurations.reloadConfigs(this.console);
-+ org.purpurmc.purpur.PurpurConfig.init((File) console.options.valueOf("purpur-settings")); // Purpur - Purpur config files
- for (ServerLevel world : this.console.getAllLevels()) {
- // world.serverLevelData.setDifficulty(config.difficulty); // Paper - per level difficulty
- world.setSpawnSettings(world.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && config.spawnMonsters); // Paper - per level difficulty (from MinecraftServer#setDifficulty(ServerLevel, Difficulty, boolean))
-@@ -1101,6 +1102,7 @@ public final class CraftServer implements Server {
- }
- }
- world.spigotConfig.init(); // Spigot
-+ world.purpurConfig.init(); // Purpur - Purpur config files
- }
-
- Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper
-@@ -1118,6 +1120,7 @@ public final class CraftServer implements Server {
- org.spigotmc.SpigotConfig.registerCommands(); // Spigot
- io.papermc.paper.command.PaperCommands.registerCommands(this.console); // Paper
- this.spark.registerCommandBeforePlugins(this); // Paper - spark
-+ org.purpurmc.purpur.PurpurConfig.registerCommands(); // Purpur - Purpur config files
- this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*");
- this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions");
-
-@@ -3031,6 +3034,18 @@ public final class CraftServer implements Server {
- return CraftServer.this.console.paperConfigurations.createLegacyObject(CraftServer.this.console);
- }
-
-+ // Purpur start - Purpur config files
-+ @Override
-+ public YamlConfiguration getPurpurConfig() {
-+ return org.purpurmc.purpur.PurpurConfig.config;
-+ }
-+
-+ @Override
-+ public java.util.Properties getServerProperties() {
-+ return getProperties().properties;
-+ }
-+ // Purpur end - Purpur config files
-+
- @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 1c2439ffc1e407ff69286817d22f127470ce07ba..d313f3a9b31d4ecc3b48f8fc2e44d3b445e8e2c5 100644
---- a/src/main/java/org/bukkit/craftbukkit/Main.java
-+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
-@@ -176,6 +176,13 @@ public class Main {
- .describedAs("Jar file");
- // Paper end
-
-+ // Purpur start - Purpur config files
-+ acceptsAll(asList("purpur", "purpur-settings"), "File for purpur settings")
-+ .withRequiredArg()
-+ .ofType(File.class)
-+ .defaultsTo(new File("purpur.yml"))
-+ .describedAs("Yml file");
-+ // Purpur end - Purpur config files
- // Paper start
- acceptsAll(asList("server-name"), "Name of the server")
- .withRequiredArg()
-diff --git a/src/main/java/org/purpurmc/purpur/PurpurConfig.java b/src/main/java/org/purpurmc/purpur/PurpurConfig.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..c2991c34fd4306fae79fca2c1349c826b3247c49
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/PurpurConfig.java
-@@ -0,0 +1,178 @@
-+package org.purpurmc.purpur;
-+
-+import com.google.common.base.Throwables;
-+import com.google.common.collect.ImmutableMap;
-+import com.mojang.datafixers.util.Pair;
-+import net.kyori.adventure.bossbar.BossBar;
-+import net.kyori.adventure.text.minimessage.MiniMessage;
-+import net.minecraft.core.Registry;
-+import net.minecraft.core.registries.BuiltInRegistries;
-+import net.minecraft.core.registries.Registries;
-+import net.minecraft.resources.ResourceLocation;
-+import net.minecraft.server.MinecraftServer;
-+import net.minecraft.world.effect.MobEffect;
-+import net.minecraft.world.effect.MobEffectInstance;
-+import net.minecraft.world.entity.EntityDimensions;
-+import net.minecraft.world.entity.EntityType;
-+import net.minecraft.world.food.FoodProperties;
-+import net.minecraft.world.food.Foods;
-+import net.minecraft.world.item.enchantment.Enchantment;
-+import net.minecraft.world.level.block.Block;
-+import net.minecraft.world.level.block.Blocks;
-+import net.minecraft.world.level.block.state.BlockBehaviour;
-+import org.bukkit.Bukkit;
-+import org.bukkit.command.Command;
-+import org.bukkit.configuration.ConfigurationSection;
-+import org.bukkit.configuration.InvalidConfigurationException;
-+import org.bukkit.configuration.file.YamlConfiguration;
-+import org.purpurmc.purpur.command.PurpurCommand;
-+import org.purpurmc.purpur.task.TPSBarTask;
-+
-+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.ArrayList;
-+import java.util.Collections;
-+import java.util.HashMap;
-+import java.util.HashSet;
-+import java.util.List;
-+import java.util.Map;
-+import java.util.Set;
-+import java.util.logging.Level;
-+
-+@SuppressWarnings("unused")
-+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://purpurmc.org \n"
-+ + "Docs: https://purpurmc.org/docs \n";
-+ private static File CONFIG_FILE;
-+ public static YamlConfiguration config;
-+
-+ private static Map commands;
-+
-+ public 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", 38);
-+ set("config-version", 38);
-+
-+ readConfig(PurpurConfig.class, null);
-+
-+ Block.BLOCK_STATE_REGISTRY.forEach(BlockBehaviour.BlockStateBase::initCache);
-+ }
-+
-+ 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 String getString(String path, String def) {
-+ config.addDefault(path, def);
-+ return config.getString(path, config.getString(path));
-+ }
-+
-+ 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));
-+ }
-+
-+ static Map getMap(String path, Map def) {
-+ if (def != null && config.getConfigurationSection(path) == null) {
-+ config.addDefault(path, def);
-+ return def;
-+ }
-+ return toMap(config.getConfigurationSection(path));
-+ }
-+
-+ private static Map toMap(ConfigurationSection section) {
-+ ImmutableMap.Builder builder = ImmutableMap.builder();
-+ if (section != null) {
-+ for (String key : section.getKeys(false)) {
-+ Object obj = section.get(key);
-+ if (obj != null) {
-+ builder.put(key, obj instanceof ConfigurationSection val ? toMap(val) : obj);
-+ }
-+ }
-+ }
-+ return builder.build();
-+ }
-+}
-diff --git a/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..42e502cfcb8d2e775cbf738773caf1a087d2f3f4
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java
-@@ -0,0 +1,93 @@
-+package org.purpurmc.purpur;
-+
-+import net.minecraft.core.registries.BuiltInRegistries;
-+import net.minecraft.resources.ResourceLocation;
-+import net.minecraft.util.Mth;
-+import net.minecraft.world.entity.Entity;
-+import net.minecraft.world.entity.EntityType;
-+import net.minecraft.world.entity.monster.Shulker;
-+import net.minecraft.world.item.DyeColor;
-+import net.minecraft.world.item.Item;
-+import net.minecraft.world.item.Items;
-+import net.minecraft.world.level.block.Block;
-+import net.minecraft.world.level.block.Blocks;
-+import net.minecraft.world.level.block.state.properties.Tilt;
-+import org.purpurmc.purpur.tool.Flattenable;
-+import org.purpurmc.purpur.tool.Strippable;
-+import org.purpurmc.purpur.tool.Tillable;
-+import org.purpurmc.purpur.tool.Waxable;
-+import org.purpurmc.purpur.tool.Weatherable;
-+import org.apache.commons.lang.BooleanUtils;
-+import org.bukkit.ChatColor;
-+import org.bukkit.World;
-+import org.bukkit.configuration.ConfigurationSection;
-+import java.util.ArrayList;
-+import java.util.HashMap;
-+import java.util.List;
-+import java.util.Map;
-+import java.util.function.Predicate;
-+import java.util.logging.Level;
-+import static org.purpurmc.purpur.PurpurConfig.log;
-+
-+@SuppressWarnings("unused")
-+public class PurpurWorldConfig {
-+
-+ private final String worldName;
-+ private final World.Environment environment;
-+
-+ public PurpurWorldConfig(String worldName, World.Environment environment) {
-+ this.worldName = worldName;
-+ this.environment = environment;
-+ 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 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));
-+ }
-+
-+ 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 Map getMap(String path, Map def) {
-+ final Map fallback = PurpurConfig.getMap("world-settings.default." + path, def);
-+ final Map value = PurpurConfig.getMap("world-settings." + worldName + "." + path, null);
-+ return value.isEmpty() ? fallback : value;
-+ }
-+}
-diff --git a/src/main/java/org/purpurmc/purpur/command/PurpurCommand.java b/src/main/java/org/purpurmc/purpur/command/PurpurCommand.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..afdf04f8b22ad0b7c0b41675e44687b49c2f86d6
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/command/PurpurCommand.java
-@@ -0,0 +1,65 @@
-+package org.purpurmc.purpur.command;
-+
-+import net.minecraft.server.MinecraftServer;
-+import net.minecraft.server.level.ServerLevel;
-+import org.purpurmc.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 (ServerLevel level : console.getAllLevels()) {
-+ level.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/patches/server/0003-Purpur-client-support.patch b/patches/server/0003-Purpur-client-support.patch
deleted file mode 100644
index 3f727f26ea..0000000000
--- a/patches/server/0003-Purpur-client-support.patch
+++ /dev/null
@@ -1,60 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: William Blake Galbreath
-Date: Fri, 30 Jul 2021 14:31:25 -0500
-Subject: [PATCH] Purpur client support
-
-
-diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-index fc7f7a34babd095a51b5321f600aef65a2a9d123..be20eed6e3bfab7a78228dfb42b50f80ad3d817c 100644
---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
-+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-@@ -327,6 +327,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple
- public com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent
- public @Nullable String clientBrandName = null; // Paper - Brand support
- public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - Add API for quit reason; there are a lot of changes to do if we change all methods leading to the event
-+ public boolean purpurClient = false; // Purpur - Purpur client support
-
- // Paper start - rewrite chunk system
- private ca.spottedleaf.moonrise.patches.chunk_system.player.RegionizedPlayerChunkLoader.PlayerChunkLoaderData chunkLoader;
-diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
-index b0bc66dc7248aae691dcab68b925b52a1695e63f..92749b57d3a2b2ffee79436319513248846296b6 100644
---- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
-+++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
-@@ -85,6 +85,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
- public final java.util.Map packCallbacks = new java.util.concurrent.ConcurrentHashMap<>(); // Paper - adventure resource pack callbacks
- private static final long KEEPALIVE_LIMIT = Long.getLong("paper.playerconnection.keepalive", 30) * 1000; // Paper - provide property to set keepalive limit
- protected static final ResourceLocation MINECRAFT_BRAND = ResourceLocation.withDefaultNamespace("brand"); // Paper - Brand support
-+ protected static final ResourceLocation PURPUR_CLIENT = ResourceLocation.fromNamespaceAndPath("purpur", "client"); // Purpur - Purpur client support
-
- public ServerCommonPacketListenerImpl(MinecraftServer minecraftserver, Connection networkmanager, CommonListenerCookie commonlistenercookie, ServerPlayer player) { // CraftBukkit
- this.server = minecraftserver;
-@@ -179,6 +180,13 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
- ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t register custom payload", ex);
- this.disconnect(Component.literal("Invalid payload REGISTER!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause
- }
-+ // Purpur start - Purpur client support
-+ } else if (identifier.equals(PURPUR_CLIENT)) {
-+ try {
-+ player.purpurClient = true;
-+ } catch (Exception ignore) {
-+ }
-+ // Purpur end - Purpur client support
- } else if (identifier.equals(ServerCommonPacketListenerImpl.CUSTOM_UNREGISTER)) {
- try {
- String channels = payload.toString(com.google.common.base.Charsets.UTF_8);
-diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
-index 6a647cab8b2e476987931486e290703b8726f2c7..c5bd2a45b32e8dff83c148379544db125684622a 100644
---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
-+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
-@@ -3565,4 +3565,11 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
- this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundEntityEventPacket(((CraftEntity) target).getHandle(), effect.getData()));
- }
- // Paper end - entity effect API
-+
-+ // Purpur start - Purpur client support
-+ @Override
-+ public boolean usesPurpurClient() {
-+ return getHandle().purpurClient;
-+ }
-+ // Purpur end - Purpur client support
- }
diff --git a/patches/server/0004-MC-Utils.patch b/patches/server/0004-MC-Utils.patch
deleted file mode 100644
index 022fd8ea51..0000000000
--- a/patches/server/0004-MC-Utils.patch
+++ /dev/null
@@ -1,164 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar
-Date: Tue, 9 Jun 2020 03:17:25 -0400
-Subject: [PATCH] MC Utils
-
-
-diff --git a/src/main/java/org/purpurmc/purpur/util/MinecraftInternalPlugin.java b/src/main/java/org/purpurmc/purpur/util/MinecraftInternalPlugin.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..129acb8ad139decc6b1c023cb10bc32dc91d64d1
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/util/MinecraftInternalPlugin.java
-@@ -0,0 +1,152 @@
-+package org.purpurmc.purpur.util;
-+
-+import org.bukkit.Server;
-+import org.bukkit.command.Command;
-+import org.bukkit.command.CommandSender;
-+import org.bukkit.configuration.file.FileConfiguration;
-+import org.bukkit.generator.BiomeProvider;
-+import org.bukkit.generator.ChunkGenerator;
-+import org.bukkit.plugin.PluginBase;
-+import org.bukkit.plugin.PluginDescriptionFile;
-+import org.bukkit.plugin.PluginLoader;
-+import org.bukkit.plugin.PluginLogger;
-+import org.jetbrains.annotations.NotNull;
-+import org.jetbrains.annotations.Nullable;
-+
-+import java.io.File;
-+import java.io.InputStream;
-+import java.util.List;
-+
-+public class MinecraftInternalPlugin extends PluginBase {
-+ private boolean enabled = true;
-+
-+ private final String pluginName;
-+ private PluginDescriptionFile pdf;
-+
-+ public MinecraftInternalPlugin() {
-+ this.pluginName = "Minecraft";
-+ pdf = new PluginDescriptionFile(pluginName, "1.0", "nms");
-+ }
-+
-+ public void setEnabled(boolean enabled) {
-+ this.enabled = enabled;
-+ }
-+
-+ @Override
-+ public File getDataFolder() {
-+ throw new UnsupportedOperationException("Not supported.");
-+ }
-+
-+ @Override
-+ public PluginDescriptionFile getDescription() {
-+ return pdf;
-+ }
-+ // Paper start
-+ @Override
-+ public io.papermc.paper.plugin.configuration.PluginMeta getPluginMeta() {
-+ return pdf;
-+ }
-+ // Paper end
-+
-+ @Override
-+ public FileConfiguration getConfig() {
-+ throw new UnsupportedOperationException("Not supported.");
-+ }
-+
-+ @Override
-+ public InputStream getResource(String filename) {
-+ throw new UnsupportedOperationException("Not supported.");
-+ }
-+
-+ @Override
-+ public void saveConfig() {
-+ throw new UnsupportedOperationException("Not supported.");
-+ }
-+
-+ @Override
-+ public void saveDefaultConfig() {
-+ throw new UnsupportedOperationException("Not supported.");
-+ }
-+
-+ @Override
-+ public void saveResource(String resourcePath, boolean replace) {
-+ throw new UnsupportedOperationException("Not supported.");
-+ }
-+
-+ @Override
-+ public void reloadConfig() {
-+ throw new UnsupportedOperationException("Not supported.");
-+ }
-+
-+ @Override
-+ public PluginLogger getLogger() {
-+ throw new UnsupportedOperationException("Not supported.");
-+ }
-+
-+ @Override
-+ public PluginLoader getPluginLoader() {
-+ throw new UnsupportedOperationException("Not supported.");
-+ }
-+
-+ @Override
-+ public Server getServer() {
-+ throw new UnsupportedOperationException("Not supported.");
-+ }
-+
-+ @Override
-+ public boolean isEnabled() {
-+ return enabled;
-+ }
-+
-+ @Override
-+ public void onDisable() {
-+ throw new UnsupportedOperationException("Not supported.");
-+ }
-+
-+ @Override
-+ public void onLoad() {
-+ throw new UnsupportedOperationException("Not supported.");
-+ }
-+
-+ @Override
-+ public void onEnable() {
-+ throw new UnsupportedOperationException("Not supported.");
-+ }
-+
-+ @Override
-+ public boolean isNaggable() {
-+ throw new UnsupportedOperationException("Not supported.");
-+ }
-+
-+ @Override
-+ public void setNaggable(boolean canNag) {
-+ throw new UnsupportedOperationException("Not supported.");
-+ }
-+
-+ @Override
-+ public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) {
-+ throw new UnsupportedOperationException("Not supported.");
-+ }
-+
-+ @Override
-+ public @Nullable BiomeProvider getDefaultBiomeProvider(@NotNull String worldName, @Nullable String id) {
-+ throw new UnsupportedOperationException("Not supported.");
-+ }
-+
-+ @Override
-+ public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
-+ throw new UnsupportedOperationException("Not supported.");
-+ }
-+
-+ @Override
-+ public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
-+ throw new UnsupportedOperationException("Not supported.");
-+ }
-+
-+ // Paper start - lifecycle events
-+ @Override
-+ public @NotNull io.papermc.paper.plugin.lifecycle.event.LifecycleEventManager getLifecycleManager() {
-+ throw new UnsupportedOperationException("Not supported.");
-+ }
-+ // Paper end - lifecycle events
-+}
diff --git a/patches/server/0005-Fix-decompile-errors.patch b/patches/server/0005-Fix-decompile-errors.patch
deleted file mode 100644
index f16d294f48..0000000000
--- a/patches/server/0005-Fix-decompile-errors.patch
+++ /dev/null
@@ -1,52 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: BillyGalbreath
-Date: Sun, 12 Jun 2022 06:20:21 -0500
-Subject: [PATCH] Fix decompile errors
-
-
-diff --git a/src/main/java/net/minecraft/commands/execution/tasks/BuildContexts.java b/src/main/java/net/minecraft/commands/execution/tasks/BuildContexts.java
-index b0d26b0eadb2a43924629424a6c13198aace8f69..9f5c3ec2eae9b30bdb8dbcb328d7f701cb7aeb9d 100644
---- a/src/main/java/net/minecraft/commands/execution/tasks/BuildContexts.java
-+++ b/src/main/java/net/minecraft/commands/execution/tasks/BuildContexts.java
-@@ -52,7 +52,7 @@ public class BuildContexts> {
- }
-
- RedirectModifier redirectModifier = commandContext.getRedirectModifier();
-- if (redirectModifier instanceof CustomModifierExecutor customModifierExecutor) {
-+ if (redirectModifier instanceof CustomModifierExecutor customModifierExecutor) { // Purpur - decompile error
- customModifierExecutor.apply(baseSource, list, contextChain, chainModifiers, ExecutionControl.create(context, frame));
- return;
- }
-@@ -92,11 +92,11 @@ public class BuildContexts> {
-
- if (list.isEmpty()) {
- if (chainModifiers.isReturn()) {
-- context.queueNext(new CommandQueueEntry<>(frame, FallthroughTask.instance()));
-+ context.queueNext(new CommandQueueEntry<>(frame, (EntryAction) FallthroughTask.instance())); // Purpur - decompile error
- }
- } else {
- CommandContext commandContext2 = contextChain.getTopContext();
-- if (commandContext2.getCommand() instanceof CustomCommandExecutor customCommandExecutor) {
-+ if (commandContext2.getCommand() instanceof CustomCommandExecutor customCommandExecutor) { // Purpur - decompile error
- ExecutionControl executionControl = ExecutionControl.create(context, frame);
-
- for (T executionCommandSource2 : list) {
-diff --git a/src/main/java/net/minecraft/commands/synchronization/ArgumentTypeInfos.java b/src/main/java/net/minecraft/commands/synchronization/ArgumentTypeInfos.java
-index 7b118a92a6eb779f800ae8f5d8f6e3c861fc4f6a..057a038e8dcacd7496a0b2373de2c20255a5c297 100644
---- a/src/main/java/net/minecraft/commands/synchronization/ArgumentTypeInfos.java
-+++ b/src/main/java/net/minecraft/commands/synchronization/ArgumentTypeInfos.java
-@@ -119,10 +119,10 @@ public class ArgumentTypeInfos {
- register(registry, "dimension", DimensionArgument.class, SingletonArgumentInfo.contextFree(DimensionArgument::dimension));
- register(registry, "gamemode", GameModeArgument.class, SingletonArgumentInfo.contextFree(GameModeArgument::gameMode));
- register(registry, "time", TimeArgument.class, new TimeArgument.Info());
-- register(registry, "resource_or_tag", fixClassType(ResourceOrTagArgument.class), new ResourceOrTagArgument.Info());
-- register(registry, "resource_or_tag_key", fixClassType(ResourceOrTagKeyArgument.class), new ResourceOrTagKeyArgument.Info());
-- register(registry, "resource", fixClassType(ResourceArgument.class), new ResourceArgument.Info());
-- register(registry, "resource_key", fixClassType(ResourceKeyArgument.class), new ResourceKeyArgument.Info());
-+ register(registry, "resource_or_tag", fixClassType(ResourceOrTagArgument.class), new ResourceOrTagArgument.Info<>()); // Purpur - decompile error
-+ register(registry, "resource_or_tag_key", fixClassType(ResourceOrTagKeyArgument.class), new ResourceOrTagKeyArgument.Info<>()); // Purpur - decompile error
-+ register(registry, "resource", fixClassType(ResourceArgument.class), new ResourceArgument.Info<>()); // Purpur - decompile error
-+ register(registry, "resource_key", fixClassType(ResourceKeyArgument.class), new ResourceKeyArgument.Info<>()); // Purpur - decompile error
- register(registry, "template_mirror", TemplateMirrorArgument.class, SingletonArgumentInfo.contextFree(TemplateMirrorArgument::templateMirror));
- register(registry, "template_rotation", TemplateRotationArgument.class, SingletonArgumentInfo.contextFree(TemplateRotationArgument::templateRotation));
- register(registry, "heightmap", HeightmapTypeArgument.class, SingletonArgumentInfo.contextFree(HeightmapTypeArgument::heightmap));
diff --git a/patches/server/0006-Component-related-conveniences.patch b/patches/server/0006-Component-related-conveniences.patch
deleted file mode 100644
index e463f84ccb..0000000000
--- a/patches/server/0006-Component-related-conveniences.patch
+++ /dev/null
@@ -1,103 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: William Blake Galbreath
-Date: Tue, 29 Jun 2021 21:37:40 -0500
-Subject: [PATCH] Component related conveniences
-
-
-diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-index be20eed6e3bfab7a78228dfb42b50f80ad3d817c..09a7809b89a4f302a1149850d4daeb471365d189 100644
---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
-+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-@@ -2236,6 +2236,26 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple
- this.lastSentExp = -1; // CraftBukkit - Added to reset
- }
-
-+ // Purpur start - Component related conveniences
-+ public void sendActionBarMessage(@Nullable String message) {
-+ if (message != null && !message.isEmpty()) {
-+ sendActionBarMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(message));
-+ }
-+ }
-+
-+ public void sendActionBarMessage(@Nullable net.kyori.adventure.text.Component message) {
-+ if (message != null) {
-+ sendActionBarMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message));
-+ }
-+ }
-+
-+ public void sendActionBarMessage(@Nullable Component message) {
-+ if (message != null) {
-+ displayClientMessage(message, true);
-+ }
-+ }
-+ // Purpur end - Component related conveniences
-+
- @Override
- public void displayClientMessage(Component message, boolean overlay) {
- this.sendSystemMessage(message, overlay);
-@@ -2457,6 +2477,20 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple
- return new CommandSourceStack(this.commandSource(), this.position(), this.getRotationVector(), this.serverLevel(), this.getPermissionLevel(), this.getName().getString(), this.getDisplayName(), this.server, this);
- }
-
-+ // Purpur start - Component related conveniences
-+ public void sendMiniMessage(@Nullable String message) {
-+ if (message != null && !message.isEmpty()) {
-+ this.sendMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(message));
-+ }
-+ }
-+
-+ public void sendMessage(@Nullable net.kyori.adventure.text.Component message) {
-+ if (message != null) {
-+ this.sendSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message));
-+ }
-+ }
-+ // Purpur end - Component related conveniences
-+
- public void sendSystemMessage(Component message) {
- this.sendSystemMessage(message, false);
- }
-diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
-index 9b71655a425356132afb786eff623f558e1e3498..98e803eaf5ce4c773f35fd752c21c7176707427c 100644
---- a/src/main/java/net/minecraft/server/players/PlayerList.java
-+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
-@@ -991,6 +991,20 @@ public abstract class PlayerList {
- }
- // CraftBukkit end
-
-+ // Purpur start - Component related conveniences
-+ public void broadcastMiniMessage(@Nullable String message, boolean overlay) {
-+ if (message != null && !message.isEmpty()) {
-+ this.broadcastMessage(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(message), overlay);
-+ }
-+ }
-+
-+ public void broadcastMessage(@Nullable net.kyori.adventure.text.Component message, boolean overlay) {
-+ if (message != null) {
-+ this.broadcastSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message), overlay);
-+ }
-+ }
-+ // Purpur end - Component related conveniences
-+
- public void broadcastAll(Packet> packet, ResourceKey dimension) {
- Iterator iterator = this.players.iterator();
-
-diff --git a/src/main/java/net/minecraft/world/damagesource/DamageSource.java b/src/main/java/net/minecraft/world/damagesource/DamageSource.java
-index bb1a60180e58c1333e7bb33e8acf1b0225eda8a8..ab0ba4406dcaa915435c3f53ac9ca06fb21c673b 100644
---- a/src/main/java/net/minecraft/world/damagesource/DamageSource.java
-+++ b/src/main/java/net/minecraft/world/damagesource/DamageSource.java
-@@ -198,6 +198,15 @@ public class DamageSource {
- }
- }
-
-+ // Purpur start - Component related conveniences
-+ public Component getLocalizedDeathMessage(String str, LivingEntity entity) {
-+ net.kyori.adventure.text.Component name = io.papermc.paper.adventure.PaperAdventure.asAdventure(entity.getDisplayName());
-+ net.kyori.adventure.text.minimessage.tag.resolver.TagResolver template = net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.component("player", name);
-+ net.kyori.adventure.text.Component component = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(str, template);
-+ return io.papermc.paper.adventure.PaperAdventure.asVanilla(component);
-+ }
-+ // Purpur end - Component related conveniences
-+
- public String getMsgId() {
- return this.type().msgId();
- }
diff --git a/patches/server/0007-Ridables.patch b/patches/server/0007-Ridables.patch
deleted file mode 100644
index 9ebf529d2a..0000000000
--- a/patches/server/0007-Ridables.patch
+++ /dev/null
@@ -1,6681 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: William Blake Galbreath
-Date: Sun, 5 Jul 2020 22:19:49 -0500
-Subject: [PATCH] Ridables
-
-
-diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java
-index faffd87c357511ef00646971a16acf1009362c59..6714b4a39180affd101f1cab0d587cf2d3e6886a 100644
---- a/src/main/java/net/minecraft/core/BlockPos.java
-+++ b/src/main/java/net/minecraft/core/BlockPos.java
-@@ -63,6 +63,12 @@ public class BlockPos extends Vec3i {
- public static final int MAX_HORIZONTAL_COORDINATE = 33554431;
- // Paper end - Optimize Bit Operations by inlining
-
-+ // Purpur start - Ridables
-+ public BlockPos(net.minecraft.world.entity.Entity entity) {
-+ super(entity.getBlockX(), entity.getBlockY(), entity.getBlockZ());
-+ }
-+ // Purpur end - Ridables
-+
- public BlockPos(int x, int y, int z) {
- super(x, y, z);
- }
-diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
-index ae4ebf509837e8d44255781c61d02873f8b74be8..ce4ce361061932162ace58070d44d1aa70189dbd 100644
---- a/src/main/java/net/minecraft/server/MinecraftServer.java
-+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
-@@ -1857,6 +1857,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - Add EntityMoveEvent
- net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper - Perf: Optimize Hoppers
- worldserver.updateLagCompensationTick(); // Paper - lag compensation
-+ worldserver.hasRidableMoveEvent = org.purpurmc.purpur.event.entity.RidableMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Purpur - Ridables
-
- gameprofilerfiller.push(() -> {
- String s = String.valueOf(worldserver);
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 1f898500d0e9b18a880645ceb0a8ff0fe75f4e48..4b9434b2d03cd24f5dac7098d2f1318fd12baddb 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -232,6 +232,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
- public boolean hasPhysicsEvent = true; // Paper - BlockPhysicsEvent
- public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent
- private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current)
-+ public boolean hasRidableMoveEvent = false; // Purpur - Ridables
-
- public LevelChunk getChunkIfLoaded(int x, int z) {
- return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately
-diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-index 09a7809b89a4f302a1149850d4daeb471365d189..8207208d6fb3f982e9909add9e74a0dda69e8120 100644
---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
-+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-@@ -1030,6 +1030,15 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple
- this.trackEnteredOrExitedLavaOnVehicle();
- this.updatePlayerAttributes();
- this.advancements.flushDirty(this);
-+
-+ // Purpur start - Ridables
-+ if (this.level().purpurConfig.useNightVisionWhenRiding && this.getVehicle() != null && this.getVehicle().getRider() == this && this.level().getGameTime() % 100 == 0) { // 5 seconds
-+ MobEffectInstance nightVision = this.getEffect(MobEffects.NIGHT_VISION);
-+ if (nightVision == null || nightVision.getDuration() <= 300) { // 15 seconds
-+ this.addEffect(new MobEffectInstance(MobEffects.NIGHT_VISION, 400, 0)); // 20 seconds
-+ }
-+ }
-+ // Purpur end - Ridables
- }
-
- private void updatePlayerAttributes() {
-diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-index 84fa24880d02dc7ba1ec8bda3575be38447fd4b2..fbc59503316d566e88b037851afd74e5469c281b 100644
---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-@@ -2887,6 +2887,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
-
- ServerGamePacketListenerImpl.this.cserver.getPluginManager().callEvent(event);
-
-+ player.processClick(enumhand); // Purpur - Ridables
-+
- // Entity in bucket - SPIGOT-4048 and SPIGOT-6859a
- if ((entity instanceof Bucketable && entity instanceof LivingEntity && origItem != null && origItem.asItem() == Items.WATER_BUCKET) && (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem)) {
- entity.resendPossiblyDesyncedEntityData(ServerGamePacketListenerImpl.this.player); // Paper - The entire mob gets deleted, so resend it
-diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
-index 7ac7d0729705cb02f22277be3c467aed4f69ec0e..5c14180d92e1baebe59b08311746418e7d9f6a24 100644
---- a/src/main/java/net/minecraft/world/entity/Entity.java
-+++ b/src/main/java/net/minecraft/world/entity/Entity.java
-@@ -340,7 +340,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
- private final Set tags;
- private final double[] pistonDeltas;
- private long pistonDeltasGameTime;
-- private EntityDimensions dimensions;
-+ protected EntityDimensions dimensions; // Purpur - private -> protected - Ridables
- private float eyeHeight;
- public boolean isInPowderSnow;
- public boolean wasInPowderSnow;
-@@ -3324,6 +3324,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
- this.passengers = ImmutableList.copyOf(list);
- }
-
-+ // Purpur start - Ridables
-+ if (isRidable() && this.passengers.get(0) == passenger && passenger instanceof Player player) {
-+ onMount(player);
-+ this.rider = player;
-+ }
-+ // Purpur end - Ridables
-+
- this.gameEvent(GameEvent.ENTITY_MOUNT, passenger);
- }
- }
-@@ -3363,6 +3370,14 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
- return false;
- }
- // CraftBukkit end
-+
-+ // Purpur start - Ridables
-+ if (this.rider != null && this.passengers.get(0) == this.rider) {
-+ onDismount(this.rider);
-+ this.rider = null;
-+ }
-+ // Purpur end - Ridables
-+
- if (this.passengers.size() == 1 && this.passengers.get(0) == entity) {
- this.passengers = ImmutableList.of();
- } else {
-@@ -5355,4 +5370,44 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
- return ((net.minecraft.server.level.ServerLevel) this.level).isPositionEntityTicking(this.blockPosition());
- }
- // Paper end - Expose entity id counter
-+ // Purpur start - Ridables
-+ @Nullable
-+ private Player rider = null;
-+
-+ @Nullable
-+ public Player getRider() {
-+ return rider;
-+ }
-+
-+ public boolean isRidable() {
-+ return false;
-+ }
-+
-+ public boolean isControllable() {
-+ return true;
-+ }
-+
-+ public void onMount(Player rider) {
-+ if (this instanceof Mob) {
-+ ((Mob) this).setTarget(null, null, false);
-+ ((Mob) this).getNavigation().stop();
-+ }
-+ rider.setJumping(false); // fixes jump on mount
-+ }
-+
-+ public void onDismount(Player player) {
-+ }
-+
-+ public boolean onSpacebar() {
-+ return false;
-+ }
-+
-+ public boolean onClick(InteractionHand hand) {
-+ return false;
-+ }
-+
-+ public boolean processClick(InteractionHand hand) {
-+ return false;
-+ }
-+ // Purpur end - Ridables
- }
-diff --git a/src/main/java/net/minecraft/world/entity/GlowSquid.java b/src/main/java/net/minecraft/world/entity/GlowSquid.java
-index 397765b1547ae47b64963b3807b206c50a6650e1..293ffe990de70f4f8872f063388a3a50c60b68e6 100644
---- a/src/main/java/net/minecraft/world/entity/GlowSquid.java
-+++ b/src/main/java/net/minecraft/world/entity/GlowSquid.java
-@@ -25,6 +25,19 @@ public class GlowSquid extends Squid {
- super(type, world);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.glowSquidRidable;
-+ }
-+
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.glowSquidControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected ParticleOptions getInkParticle() {
- return ParticleTypes.GLOW_SQUID_INK;
-diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
-index 96b4fbe4a4655777ff10b32e3257e2fac2aba12a..715b76bd0ccc0c29583a55f82a8ecd889ab49b56 100644
---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
-+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
-@@ -246,9 +246,9 @@ public abstract class LivingEntity extends Entity implements Attackable {
- protected float rotOffs;
- public float lastHurt;
- public boolean jumping;
-- public float xxa;
-- public float yya;
-- public float zza;
-+ public float xxa; public float getStrafeMot() { return xxa; } public void setStrafeMot(float strafe) { xxa = strafe; } // Purpur - OBFHELPER
-+ public float yya; public float getVerticalMot() { return yya; } public void setVerticalMot(float vertical) { yya = vertical; } // Purpur - OBFHELPER
-+ public float zza; public float getForwardMot() { return zza; } public void setForwardMot(float forward) { zza = forward; } // Purpur - OBFHELPER
- protected int lerpSteps;
- protected double lerpX;
- protected double lerpY;
-@@ -323,7 +323,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
- this.lastClimbablePos = Optional.empty();
- this.activeLocationDependentEnchantments = new EnumMap(EquipmentSlot.class);
- this.appliedScale = 1.0F;
-- this.attributes = new AttributeMap(DefaultAttributes.getSupplier(type));
-+ this.attributes = new AttributeMap(DefaultAttributes.getSupplier(type), this); // Purpur - Ridables
- this.craftAttributes = new CraftAttributeMap(this.attributes); // CraftBukkit
- // CraftBukkit - setHealth(getMaxHealth()) inlined and simplified to skip the instanceof check for EntityPlayer, as getBukkitEntity() is not initialized in constructor
- this.entityData.set(LivingEntity.DATA_HEALTH_ID, (float) this.getAttribute(Attributes.MAX_HEALTH).getValue());
-@@ -373,6 +373,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
- public static AttributeSupplier.Builder createLivingAttributes() {
- return AttributeSupplier.builder().add(Attributes.MAX_HEALTH).add(Attributes.KNOCKBACK_RESISTANCE).add(Attributes.MOVEMENT_SPEED).add(Attributes.ARMOR).add(Attributes.ARMOR_TOUGHNESS).add(Attributes.MAX_ABSORPTION).add(Attributes.STEP_HEIGHT).add(Attributes.SCALE).add(Attributes.GRAVITY).add(Attributes.SAFE_FALL_DISTANCE).add(Attributes.FALL_DAMAGE_MULTIPLIER).add(Attributes.JUMP_STRENGTH).add(Attributes.OXYGEN_BONUS).add(Attributes.BURNING_TIME).add(Attributes.EXPLOSION_KNOCKBACK_RESISTANCE).add(Attributes.WATER_MOVEMENT_EFFICIENCY).add(Attributes.MOVEMENT_EFFICIENCY).add(Attributes.ATTACK_KNOCKBACK);
- }
-+ public boolean shouldSendAttribute(Attribute attribute) { return true; } // Purpur - Ridables
-
- @Override
- protected void checkFallDamage(double heightDifference, boolean onGround, BlockState state, BlockPos landedPosition) {
-@@ -3703,8 +3704,10 @@ public abstract class LivingEntity extends Entity implements Attackable {
- this.pushEntities();
- gameprofilerfiller.pop();
- // Paper start - Add EntityMoveEvent
-- if (((ServerLevel) this.level()).hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) {
-- if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) {
-+ // Purpur start - Ridables
-+ if (this.xo != this.getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) {
-+ if (((ServerLevel) this.level()).hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) {
-+ // Purpur end - Ridables
- Location from = new Location(this.level().getWorld(), this.xo, this.yo, this.zo, this.yRotO, this.xRotO);
- Location to = new Location(this.level().getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot());
- io.papermc.paper.event.entity.EntityMoveEvent event = new io.papermc.paper.event.entity.EntityMoveEvent(this.getBukkitLivingEntity(), from, to.clone());
-@@ -3714,6 +3717,21 @@ public abstract class LivingEntity extends Entity implements Attackable {
- this.absMoveTo(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ(), event.getTo().getYaw(), event.getTo().getPitch());
- }
- }
-+ // Purpur start - Ridables
-+ if (getRider() != null) {
-+ getRider().resetLastActionTime();
-+ if (((ServerLevel) level()).hasRidableMoveEvent && this instanceof Mob) {
-+ Location from = new Location(level().getWorld(), xo, yo, zo, this.yRotO, this.xRotO);
-+ Location to = new Location(level().getWorld(), getX(), getY(), getZ(), this.getYRot(), this.getXRot());
-+ org.purpurmc.purpur.event.entity.RidableMoveEvent event = new org.purpurmc.purpur.event.entity.RidableMoveEvent((org.bukkit.entity.Mob) getBukkitLivingEntity(), (Player) getRider().getBukkitEntity(), from, to.clone());
-+ if (!event.callEvent()) {
-+ absMoveTo(from.getX(), from.getY(), from.getZ(), from.getYaw(), from.getPitch());
-+ } else if (!to.equals(event.getTo())) {
-+ absMoveTo(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch());
-+ }
-+ }
-+ }
-+ // Purpur end - Ridables
- }
- // Paper end - Add EntityMoveEvent
- world = this.level();
-diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java
-index 5a0b51342f4a646101f4588697bcae7d1ca8a010..261288f51ed47b0eac80cc965c76683f3e13687f 100644
---- a/src/main/java/net/minecraft/world/entity/Mob.java
-+++ b/src/main/java/net/minecraft/world/entity/Mob.java
-@@ -159,8 +159,8 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab
- this.restrictRadius = -1.0F;
- this.goalSelector = new GoalSelector();
- this.targetSelector = new GoalSelector();
-- this.lookControl = new LookControl(this);
-- this.moveControl = new MoveControl(this);
-+ this.lookControl = new org.purpurmc.purpur.controller.LookControllerWASD(this); // Purpur - Ridables
-+ this.moveControl = new org.purpurmc.purpur.controller.MoveControllerWASD(this); // Purpur - Ridables
- this.jumpControl = new JumpControl(this);
- this.bodyRotationControl = this.createBodyControl();
- this.navigation = this.createNavigation(world);
-@@ -1496,7 +1496,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab
- protected void onOffspringSpawnedFromEgg(Player player, Mob child) {}
-
- protected InteractionResult mobInteract(Player player, InteractionHand hand) {
-- return InteractionResult.PASS;
-+ return tryRide(player, hand); // Purpur - Ridables
- }
-
- public boolean isWithinRestriction() {
-@@ -1816,4 +1816,58 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab
- public float[] getArmorDropChances() {
- return this.armorDropChances;
- }
-+
-+ // Purpur start - Ridables
-+ public double getMaxY() {
-+ return level().getHeight();
-+ }
-+
-+ public InteractionResult tryRide(Player player, InteractionHand hand) {
-+ return tryRide(player, hand, InteractionResult.PASS);
-+ }
-+
-+ public InteractionResult tryRide(Player player, InteractionHand hand, InteractionResult result) {
-+ if (!isRidable()) {
-+ return result;
-+ }
-+ if (hand != InteractionHand.MAIN_HAND) {
-+ return InteractionResult.PASS;
-+ }
-+ if (player.isShiftKeyDown()) {
-+ return InteractionResult.PASS;
-+ }
-+ if (!player.getItemInHand(hand).isEmpty()) {
-+ return InteractionResult.PASS;
-+ }
-+ if (!passengers.isEmpty() || player.isPassenger()) {
-+ return InteractionResult.PASS;
-+ }
-+ if (this instanceof TamableAnimal tamable) {
-+ if (tamable.isTame() && !tamable.isOwnedBy(player)) {
-+ return InteractionResult.PASS;
-+ }
-+ if (!tamable.isTame() && !level().purpurConfig.untamedTamablesAreRidable) {
-+ return InteractionResult.PASS;
-+ }
-+ }
-+ if (this instanceof AgeableMob ageable) {
-+ if (ageable.isBaby() && !level().purpurConfig.babiesAreRidable) {
-+ return InteractionResult.PASS;
-+ }
-+ }
-+ if (!player.getBukkitEntity().hasPermission("allow.ride." + net.minecraft.core.registries.BuiltInRegistries.ENTITY_TYPE.getKey(getType()).getPath())) {
-+ if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer) {
-+ serverPlayer.sendMiniMessage(org.purpurmc.purpur.PurpurConfig.cannotRideMob);
-+ }
-+ return InteractionResult.PASS;
-+ }
-+ player.setYRot(this.getYRot());
-+ player.setXRot(this.getXRot());
-+ if (player.startRiding(this)) {
-+ return InteractionResult.SUCCESS;
-+ } else {
-+ return InteractionResult.PASS;
-+ }
-+ }
-+ // Purpur end - Ridables
- }
-diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java
-index fb967ac7b3e7828301f08a7fe9b039441cf7da30..d6c98612ca15e506657d85f6872c1278e0b73652 100644
---- a/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/attributes/AttributeMap.java
-@@ -23,14 +23,21 @@ public class AttributeMap {
- private final Set attributesToSync = new ObjectOpenHashSet<>();
- private final Set attributesToUpdate = new ObjectOpenHashSet<>();
- private final AttributeSupplier supplier;
-+ private final net.minecraft.world.entity.LivingEntity entity; // Purpur - Ridables
-
- public AttributeMap(AttributeSupplier defaultAttributes) {
-+ // Purpur start - Ridables
-+ this(defaultAttributes, null);
-+ }
-+ public AttributeMap(AttributeSupplier defaultAttributes, net.minecraft.world.entity.LivingEntity entity) {
-+ this.entity = entity;
-+ // Purpur end - Ridables
- this.supplier = defaultAttributes;
- }
-
- private void onAttributeModified(AttributeInstance instance) {
- this.attributesToUpdate.add(instance);
-- if (instance.getAttribute().value().isClientSyncable()) {
-+ if (instance.getAttribute().value().isClientSyncable() && (entity == null || entity.shouldSendAttribute(instance.getAttribute().value()))) { // Purpur - Ridables
- this.attributesToSync.add(instance);
- }
- }
-@@ -44,7 +51,7 @@ public class AttributeMap {
- }
-
- public Collection getSyncableAttributes() {
-- return this.attributes.values().stream().filter(attribute -> attribute.getAttribute().value().isClientSyncable()).collect(Collectors.toList());
-+ return this.attributes.values().stream().filter(attribute -> attribute.getAttribute().value().isClientSyncable() && (entity == null || entity.shouldSendAttribute(attribute.getAttribute().value()))).collect(Collectors.toList()); // Purpur - Ridables
- }
-
- @Nullable
-diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java b/src/main/java/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java
-index c76438d5ce2330eca16dc0b381f97e9506f84aef..8ccbf0386aa453e82fc0f82d2aefd1e08b6c3345 100644
---- a/src/main/java/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/attributes/DefaultAttributes.java
-@@ -131,7 +131,7 @@ public class DefaultAttributes {
- .put(EntityType.OCELOT, Ocelot.createAttributes().build())
- .put(EntityType.PANDA, Panda.createAttributes().build())
- .put(EntityType.PARROT, Parrot.createAttributes().build())
-- .put(EntityType.PHANTOM, Monster.createMonsterAttributes().build())
-+ .put(EntityType.PHANTOM, net.minecraft.world.entity.monster.Phantom.createAttributes().build()) // Purpur - Ridables
- .put(EntityType.PIG, Pig.createAttributes().build())
- .put(EntityType.PIGLIN, Piglin.createAttributes().build())
- .put(EntityType.PIGLIN_BRUTE, PiglinBrute.createAttributes().build())
-diff --git a/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java b/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java
-index c8fd5696de7c3623cdb4f498190a5c2708cf843e..2a6e5a9b35102ef540b561ec7ef5a5f119c564fe 100644
---- a/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/control/MoveControl.java
-@@ -29,6 +29,20 @@ public class MoveControl implements Control {
- this.mob = entity;
- }
-
-+ // Purpur start - Ridables
-+ public void setSpeedModifier(double speed) {
-+ this.speedModifier = speed;
-+ }
-+
-+ public void setForward(float forward) {
-+ this.strafeForwards = forward;
-+ }
-+
-+ public void setStrafe(float strafe) {
-+ this.strafeRight = strafe;
-+ }
-+ // Purpur end - Ridables
-+
- public boolean hasWanted() {
- return this.operation == MoveControl.Operation.MOVE_TO;
- }
-diff --git a/src/main/java/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java b/src/main/java/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java
-index fbfc2f2515ad709b2c1212aef9521e795547d66b..ebe941aeb959fc34372bfc59bc3a13421167b4cf 100644
---- a/src/main/java/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/control/SmoothSwimmingLookControl.java
-@@ -3,7 +3,7 @@ package net.minecraft.world.entity.ai.control;
- import net.minecraft.util.Mth;
- import net.minecraft.world.entity.Mob;
-
--public class SmoothSwimmingLookControl extends LookControl {
-+public class SmoothSwimmingLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - Ridables
- private final int maxYRotFromCenter;
- private static final int HEAD_TILT_X = 10;
- private static final int HEAD_TILT_Y = 20;
-@@ -14,7 +14,7 @@ public class SmoothSwimmingLookControl extends LookControl {
- }
-
- @Override
-- public void tick() {
-+ public void vanillaTick() { // Purpur - Ridables
- if (this.lookAtCooldown > 0) {
- this.lookAtCooldown--;
- this.getYRotD().ifPresent(yaw -> this.mob.yHeadRot = this.rotateTowards(this.mob.yHeadRot, yaw + 20.0F, this.yMaxRotSpeed));
-diff --git a/src/main/java/net/minecraft/world/entity/ambient/Bat.java b/src/main/java/net/minecraft/world/entity/ambient/Bat.java
-index 60c2868f255d372226e0c1389caaa5477bbef41e..add1c146cd7428547d9ef8810841b4cf39a6a05e 100644
---- a/src/main/java/net/minecraft/world/entity/ambient/Bat.java
-+++ b/src/main/java/net/minecraft/world/entity/ambient/Bat.java
-@@ -47,12 +47,59 @@ public class Bat extends AmbientCreature {
-
- public Bat(EntityType extends Bat> type, Level world) {
- super(type, world);
-+ this.moveControl = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.075F); // Purpur - Ridables
- if (!world.isClientSide) {
- this.setResting(true);
- }
-
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean shouldSendAttribute(net.minecraft.world.entity.ai.attributes.Attribute attribute) { return attribute != Attributes.FLYING_SPEED.value(); } // Fixes log spam on clients
-+
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.batRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.batRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.batControllable;
-+ }
-+
-+ @Override
-+ public double getMaxY() {
-+ return level().purpurConfig.batMaxY;
-+ }
-+
-+ @Override
-+ public void onMount(Player rider) {
-+ super.onMount(rider);
-+ if (isResting()) {
-+ setResting(false);
-+ level().levelEvent(null, 1025, new BlockPos(this).above(), 0);
-+ }
-+ }
-+
-+ @Override
-+ public void travel(Vec3 vec3) {
-+ super.travel(vec3);
-+ if (getRider() != null && this.isControllable() && !onGround) {
-+ float speed = (float) getAttributeValue(Attributes.FLYING_SPEED) * 2;
-+ setSpeed(speed);
-+ Vec3 mot = getDeltaMovement();
-+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 0.25, speed));
-+ setDeltaMovement(mot.scale(0.9D));
-+ }
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- public boolean isFlapping() {
- return !this.isResting() && (float) this.tickCount % 10.0F == 0.0F;
-@@ -102,7 +149,7 @@ public class Bat extends AmbientCreature {
- protected void pushEntities() {}
-
- public static AttributeSupplier.Builder createAttributes() {
-- return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 6.0D);
-+ return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 6.0D).add(Attributes.FLYING_SPEED, 0.6D); // Purpur - Ridables
- }
-
- public boolean isResting() {
-@@ -135,6 +182,14 @@ public class Bat extends AmbientCreature {
-
- @Override
- protected void customServerAiStep(ServerLevel world) {
-+ // Purpur start - Ridables
-+ if (getRider() != null && this.isControllable()) {
-+ Vec3 mot = getDeltaMovement();
-+ setDeltaMovement(mot.x(), mot.y() + (getVerticalMot() > 0 ? 0.07D : 0.0D), mot.z());
-+ return;
-+ }
-+ // Purpur end - Ridables
-+
- super.customServerAiStep(world);
- BlockPos blockposition = this.blockPosition();
- BlockPos blockposition1 = blockposition.above();
-diff --git a/src/main/java/net/minecraft/world/entity/animal/AbstractFish.java b/src/main/java/net/minecraft/world/entity/animal/AbstractFish.java
-index 9aedc62b1766f6a7db4da7eba55167d21d698791..d1fa6b6a18bd7a44e398eed17f2ff127b09f222a 100644
---- a/src/main/java/net/minecraft/world/entity/animal/AbstractFish.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/AbstractFish.java
-@@ -87,6 +87,7 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable {
- @Override
- protected void registerGoals() {
- super.registerGoals();
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(0, new PanicGoal(this, 1.25));
- this.goalSelector.addGoal(2, new AvoidEntityGoal<>(this, Player.class, 8.0F, 1.6, 1.4, EntitySelector.NO_SPECTATORS::test));
- this.goalSelector.addGoal(4, new AbstractFish.FishSwimGoal(this));
-@@ -100,7 +101,7 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable {
- @Override
- public void travel(Vec3 movementInput) {
- if (this.isControlledByLocalInstance() && this.isInWater()) {
-- this.moveRelative(0.01F, movementInput);
-+ this.moveRelative(getRider() != null ? getSpeed() : 0.01F, movementInput); // Purpur - Ridables
- this.move(MoverType.SELF, this.getDeltaMovement());
- this.setDeltaMovement(this.getDeltaMovement().scale(0.9));
- if (this.getTarget() == null) {
-@@ -161,7 +162,7 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable {
- protected void playStepSound(BlockPos pos, BlockState state) {
- }
-
-- static class FishMoveControl extends MoveControl {
-+ static class FishMoveControl extends org.purpurmc.purpur.controller.WaterMoveControllerWASD { // Purpur - Ridables
- private final AbstractFish fish;
-
- FishMoveControl(AbstractFish owner) {
-@@ -169,14 +170,22 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable {
- this.fish = owner;
- }
-
-+ // Purpur start - Ridables
- @Override
-- public void tick() {
-+ public void purpurTick(Player rider) {
-+ super.purpurTick(rider);
-+ fish.setDeltaMovement(fish.getDeltaMovement().add(0.0D, 0.005D, 0.0D));
-+ }
-+ // Purpur end - Ridables
-+
-+ @Override
-+ public void vanillaTick() { // Purpur - Ridables
- if (this.fish.isEyeInFluid(FluidTags.WATER)) {
- this.fish.setDeltaMovement(this.fish.getDeltaMovement().add(0.0, 0.005, 0.0));
- }
-
- if (this.operation == MoveControl.Operation.MOVE_TO && !this.fish.getNavigation().isDone()) {
-- float f = (float)(this.speedModifier * this.fish.getAttributeValue(Attributes.MOVEMENT_SPEED));
-+ float f = (float)(this.getSpeedModifier() * this.fish.getAttributeValue(Attributes.MOVEMENT_SPEED)); // Purpur - Ridables
- this.fish.setSpeed(Mth.lerp(0.125F, this.fish.getSpeed(), f));
- double d = this.wantedX - this.fish.getX();
- double e = this.wantedY - this.fish.getY();
-diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java
-index 0bafe14342c1acce131ad34717c18aed3718deed..13f6e4c83e1775daadb13e3532d7dfe6eef15aac 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Bee.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java
-@@ -154,6 +154,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
- public Bee(EntityType extends Bee> type, Level world) {
- super(type, world);
- this.remainingCooldownBeforeLocatingNewFlower = Mth.nextInt(this.random, 20, 60);
-+ final org.purpurmc.purpur.controller.FlyingMoveControllerWASD flyingController = new org.purpurmc.purpur.controller.FlyingMoveControllerWASD(this, 0.25F, 1.0F, false); // Purpur - Ridables
- // Paper start - Fix MC-167279
- class BeeFlyingMoveControl extends FlyingMoveControl {
- public BeeFlyingMoveControl(final Mob entity, final int maxPitchChange, final boolean noGravity) {
-@@ -162,11 +163,24 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
-
- @Override
- public void tick() {
-+ // Purpur start - Ridables
-+ if (mob.getRider() != null && mob.isControllable()) {
-+ flyingController.purpurTick(mob.getRider());
-+ return;
-+ }
-+ // Purpur end - Ridables
- if (this.mob.getY() <= Bee.this.level().getMinY()) {
- this.mob.setNoGravity(false);
- }
- super.tick();
- }
-+
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean hasWanted() {
-+ return mob.getRider() != null || !mob.isControllable() || super.hasWanted();
-+ }
-+ // Purpur end - Ridables
- }
- this.moveControl = new BeeFlyingMoveControl(this, 20, true);
- // Paper end - Fix MC-167279
-@@ -178,6 +192,40 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
- this.setPathfindingMalus(PathType.FENCE, -1.0F);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.beeRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.beeRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.beeControllable;
-+ }
-+
-+ @Override
-+ public double getMaxY() {
-+ return level().purpurConfig.beeMaxY;
-+ }
-+
-+ @Override
-+ public void travel(Vec3 vec3) {
-+ super.travel(vec3);
-+ if (getRider() != null && this.isControllable() && !onGround) {
-+ float speed = (float) getAttributeValue(Attributes.FLYING_SPEED) * 2;
-+ setSpeed(speed);
-+ Vec3 mot = getDeltaMovement();
-+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, speed, speed));
-+ setDeltaMovement(mot.scale(0.9D));
-+ }
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void defineSynchedData(SynchedEntityData.Builder builder) {
- super.defineSynchedData(builder);
-@@ -192,6 +240,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
-
- @Override
- protected void registerGoals() {
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(0, new Bee.BeeAttackGoal(this, 1.399999976158142D, true));
- this.goalSelector.addGoal(1, new Bee.BeeEnterHiveGoal());
- this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D));
-@@ -211,6 +260,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
- this.goalSelector.addGoal(7, new Bee.BeeGrowCropGoal());
- this.goalSelector.addGoal(8, new Bee.BeeWanderGoal());
- this.goalSelector.addGoal(9, new FloatGoal(this));
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.targetSelector.addGoal(1, (new Bee.BeeHurtByOtherGoal(this)).setAlertOthers(new Class[0]));
- this.targetSelector.addGoal(2, new Bee.BeeBecomeAngryTargetGoal(this));
- this.targetSelector.addGoal(3, new ResetUniversalAngerTargetGoal<>(this, true));
-@@ -738,16 +788,16 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
- return state.is(BlockTags.BEE_ATTRACTIVE) ? ((Boolean) state.getValueOrElse(BlockStateProperties.WATERLOGGED, false) ? false : (state.is(Blocks.SUNFLOWER) ? state.getValue(DoublePlantBlock.HALF) == DoubleBlockHalf.UPPER : true)) : false;
- }
-
-- private class BeeLookControl extends LookControl {
-+ private class BeeLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - Ridables
-
- BeeLookControl(final Mob entity) {
- super(entity);
- }
-
- @Override
-- public void tick() {
-+ public void vanillaTick() { // Purpur - Ridables
- if (!Bee.this.isAngry()) {
-- super.tick();
-+ super.vanillaTick(); // Purpur - Ridables
- }
- }
-
-diff --git a/src/main/java/net/minecraft/world/entity/animal/Cat.java b/src/main/java/net/minecraft/world/entity/animal/Cat.java
-index 989b7be74eaeba7f40eac87c7ee7f252cb0c05c9..472bbf4c3f932e2b1c7d7fa3c74b41f5be11431f 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Cat.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Cat.java
-@@ -100,12 +100,38 @@ public class Cat extends TamableAnimal implements VariantHolder {
- return itemstack.is(ItemTags.CAT_FOOD);
- }, true);
- this.goalSelector.addGoal(1, new FloatGoal(this));
-+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(1, new TamableAnimal.TamableAnimalPanicGoal(1.5D));
- this.goalSelector.addGoal(2, new SitWhenOrderedToGoal(this));
- this.goalSelector.addGoal(3, new Cat.CatRelaxOnOwnerGoal(this));
-@@ -118,6 +144,7 @@ public class Cat extends TamableAnimal implements VariantHolder(this, Rabbit.class, false, (TargetingConditions.Selector) null));
- this.targetSelector.addGoal(1, new NonTameRandomTargetGoal<>(this, Turtle.class, false, Turtle.BABY_ON_LAND_SELECTOR));
- }
-@@ -375,6 +402,7 @@ public class Cat extends TamableAnimal implements VariantHolder {
-diff --git a/src/main/java/net/minecraft/world/entity/animal/Cod.java b/src/main/java/net/minecraft/world/entity/animal/Cod.java
-index 824e5e4fe7619ae46061c3c978c9a044db8c84ab..fcf7073dd2d79f1483bdc6e7fdc37c8c260ae418 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Cod.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Cod.java
-@@ -13,6 +13,18 @@ public class Cod extends AbstractSchoolingFish {
- super(type, world);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.codRidable;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.codControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- public ItemStack getBucketItemStack() {
- return new ItemStack(Items.COD_BUCKET);
-diff --git a/src/main/java/net/minecraft/world/entity/animal/Cow.java b/src/main/java/net/minecraft/world/entity/animal/Cow.java
-index 3e00bbff266fc71b07014e7e047d77b7f809239f..dc7ccfe90a82892d65098a325fd71fbbc734da86 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Cow.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Cow.java
-@@ -44,9 +44,27 @@ public class Cow extends Animal {
- super(type, world);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.cowRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.cowRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.cowControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void registerGoals() {
- this.goalSelector.addGoal(0, new FloatGoal(this));
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(1, new PanicGoal(this, 2.0D));
- this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D));
- this.goalSelector.addGoal(3, new TemptGoal(this, 1.25D, (itemstack) -> {
-@@ -94,6 +112,7 @@ public class Cow extends Animal {
-
- @Override
- public InteractionResult mobInteract(Player player, InteractionHand hand) {
-+ if (getRider() != null) return InteractionResult.PASS; // Purpur - Ridables
- ItemStack itemstack = player.getItemInHand(hand);
-
- if (itemstack.is(Items.BUCKET) && !this.isBaby()) {
-@@ -102,7 +121,7 @@ public class Cow extends Animal {
-
- if (event.isCancelled()) {
- player.containerMenu.sendAllDataToRemote(); // Paper - Fix inventory desync
-- return InteractionResult.PASS;
-+ return tryRide(player, hand); // Purpur - Ridables
- }
- // CraftBukkit end
-
-diff --git a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java
-index 5af4d590a9b0f17ba53c6959d9c18bd1269878a4..af677b6581514a07e6455977ffc591538d43bbc6 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java
-@@ -85,14 +85,82 @@ public class Dolphin extends AgeableWaterCreature {
- return !entityitem.hasPickUpDelay() && entityitem.isAlive() && entityitem.isInWater();
- };
- public static final float BABY_SCALE = 0.65F;
-+ private int spitCooldown; // Purpur - Ridables
-
- public Dolphin(EntityType extends Dolphin> type, Level world) {
- super(type, world);
-- this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true);
-+ // Purpur start - Ridables
-+ class DolphinMoveControl extends SmoothSwimmingMoveControl {
-+ private final org.purpurmc.purpur.controller.WaterMoveControllerWASD waterMoveControllerWASD;
-+ private final Dolphin dolphin;
-+
-+ public DolphinMoveControl(Dolphin dolphin, int pitchChange, int yawChange, float speedInWater, float speedInAir, boolean buoyant) {
-+ super(dolphin, pitchChange, yawChange, speedInWater, speedInAir, buoyant);
-+ this.dolphin = dolphin;
-+ this.waterMoveControllerWASD = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(dolphin);
-+ }
-+
-+ @Override
-+ public void tick() {
-+ if (dolphin.getRider() != null && dolphin.isControllable()) {
-+ purpurTick(dolphin.getRider());
-+ } else {
-+ super.tick();
-+ }
-+ }
-+
-+ public void purpurTick(Player rider) {
-+ if (dolphin.getAirSupply() < 150) {
-+ // if drowning override player WASD controls to find air
-+ super.tick();
-+ } else {
-+ waterMoveControllerWASD.purpurTick(rider);
-+ dolphin.setDeltaMovement(dolphin.getDeltaMovement().add(0.0D, 0.005D, 0.0D));
-+ }
-+ }
-+ };
-+ this.moveControl = new DolphinMoveControl(this, 85, 10, 0.02F, 0.1F, true);
-+ // Purpur end - Ridables
- this.lookControl = new SmoothSwimmingLookControl(this, 10);
- this.setCanPickUpLoot(true);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.dolphinRidable;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.dolphinControllable;
-+ }
-+
-+ @Override
-+ public boolean onSpacebar() {
-+ if (spitCooldown == 0 && getRider() != null) {
-+ spitCooldown = level().purpurConfig.dolphinSpitCooldown;
-+
-+ org.bukkit.craftbukkit.entity.CraftPlayer player = (org.bukkit.craftbukkit.entity.CraftPlayer) getRider().getBukkitEntity();
-+ if (!player.hasPermission("allow.special.dolphin")) {
-+ return false;
-+ }
-+
-+ org.bukkit.Location loc = player.getEyeLocation();
-+ loc.setPitch(loc.getPitch() - 10);
-+ org.bukkit.util.Vector target = loc.getDirection().normalize().multiply(10).add(loc.toVector());
-+
-+ org.purpurmc.purpur.entity.DolphinSpit spit = new org.purpurmc.purpur.entity.DolphinSpit(level(), this);
-+ spit.shoot(target.getX() - getX(), target.getY() - getY(), target.getZ() - getZ(), level().purpurConfig.dolphinSpitSpeed, 5.0F);
-+
-+ level().addFreshEntity(spit);
-+ playSound(SoundEvents.DOLPHIN_ATTACK, 1.0F, 1.0F + (random.nextFloat() - random.nextFloat()) * 0.2F);
-+ return true;
-+ }
-+ return false;
-+ }
-+ // Purpur end - Ridables
-+
- @Nullable
- @Override
- public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData entityData) {
-@@ -177,6 +245,7 @@ public class Dolphin extends AgeableWaterCreature {
- protected void registerGoals() {
- this.goalSelector.addGoal(0, new BreathAirGoal(this));
- this.goalSelector.addGoal(0, new TryFindWaterGoal(this));
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(1, new Dolphin.DolphinSwimToTreasureGoal(this));
- this.goalSelector.addGoal(2, new Dolphin.DolphinSwimWithPlayerGoal(this, 4.0D));
- this.goalSelector.addGoal(4, new RandomSwimmingGoal(this, 1.0D, 10));
-@@ -187,6 +256,7 @@ public class Dolphin extends AgeableWaterCreature {
- this.goalSelector.addGoal(8, new Dolphin.PlayWithItemsGoal());
- this.goalSelector.addGoal(8, new FollowBoatGoal(this));
- this.goalSelector.addGoal(9, new AvoidEntityGoal<>(this, Guardian.class, 8.0F, 1.0D, 1.0D));
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Guardian.class})).setAlertOthers());
- }
-
-@@ -231,7 +301,7 @@ public class Dolphin extends AgeableWaterCreature {
-
- @Override
- protected boolean canRide(Entity entity) {
-- return true;
-+ return boardingCooldown <= 0; // Purpur - make dolphin honor ride cooldown like all other non-boss mobs;
- }
-
- @Override
-@@ -264,6 +334,11 @@ public class Dolphin extends AgeableWaterCreature {
- @Override
- public void tick() {
- super.tick();
-+ // Purpur start - Ridables
-+ if (spitCooldown > 0) {
-+ spitCooldown--;
-+ }
-+ // Purpur end - Ridables
- if (this.isNoAi()) {
- this.setAirSupply(this.getMaxAirSupply());
- } else {
-diff --git a/src/main/java/net/minecraft/world/entity/animal/Fox.java b/src/main/java/net/minecraft/world/entity/animal/Fox.java
-index d48c2bdb004c86e9e08680138fe51dc3b2975a64..ce5ac300582f61d0f3eeb1e94340cfefbdff1ba9 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Fox.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java
-@@ -144,6 +144,44 @@ public class Fox extends Animal implements VariantHolder {
- this.getNavigation().setRequiredPathLength(32.0F);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.foxRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.foxRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.foxControllable;
-+ }
-+
-+ @Override
-+ public float getJumpPower() {
-+ return getRider() != null && this.isControllable() ? 0.5F : super.getJumpPower();
-+ }
-+
-+ @Override
-+ public void onMount(Player rider) {
-+ super.onMount(rider);
-+ setCanPickUpLoot(false);
-+ clearStates();
-+ setIsPouncing(false);
-+ spitOutItem(getItemBySlot(EquipmentSlot.MAINHAND));
-+ setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY);
-+ }
-+
-+ @Override
-+ public void onDismount(Player rider) {
-+ super.onDismount(rider);
-+ setCanPickUpLoot(true);
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void defineSynchedData(SynchedEntityData.Builder builder) {
- super.defineSynchedData(builder);
-@@ -163,6 +201,7 @@ public class Fox extends Animal implements VariantHolder {
- return entityliving instanceof AbstractSchoolingFish;
- });
- this.goalSelector.addGoal(0, new Fox.FoxFloatGoal());
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(0, new ClimbOnTopOfPowderSnowGoal(this, this.level()));
- this.goalSelector.addGoal(1, new Fox.FaceplantGoal());
- this.goalSelector.addGoal(2, new Fox.FoxPanicGoal(2.2D));
-@@ -189,6 +228,7 @@ public class Fox extends Animal implements VariantHolder {
- this.goalSelector.addGoal(11, new Fox.FoxSearchForItemsGoal());
- this.goalSelector.addGoal(12, new Fox.FoxLookAtPlayerGoal(this, Player.class, 24.0F));
- this.goalSelector.addGoal(13, new Fox.PerchAndSearchGoal());
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.targetSelector.addGoal(3, new Fox.DefendTrustedTargetGoal(LivingEntity.class, false, false, (entityliving, worldserver) -> {
- return Fox.TRUSTED_TARGET_SELECTOR.test(entityliving) && !this.trusts(entityliving.getUUID());
- }));
-@@ -754,16 +794,16 @@ public class Fox extends Animal implements VariantHolder {
- return new Vec3(0.0D, (double) (0.55F * this.getEyeHeight()), (double) (this.getBbWidth() * 0.4F));
- }
-
-- public class FoxLookControl extends LookControl {
-+ public class FoxLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - Ridables
-
- public FoxLookControl() {
- super(Fox.this);
- }
-
- @Override
-- public void tick() {
-+ public void vanillaTick() { // Purpur - Ridables
- if (!Fox.this.isSleeping()) {
-- super.tick();
-+ super.vanillaTick(); // Purpur - Ridables
- }
-
- }
-@@ -774,16 +814,16 @@ public class Fox extends Animal implements VariantHolder {
- }
- }
-
-- private class FoxMoveControl extends MoveControl {
-+ private class FoxMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables
-
- public FoxMoveControl() {
- super(Fox.this);
- }
-
- @Override
-- public void tick() {
-+ public void vanillaTick() { // Purpur - Ridables
- if (Fox.this.canMove()) {
-- super.tick();
-+ super.vanillaTick(); // Purpur - Ridables
- }
-
- }
-diff --git a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java
-index e07b79ef172095c1800c88342b3ac8dc7703aea2..938a0c6f7cfbb6cd459d5a2ec46f912d45fd2226 100644
---- a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java
-@@ -62,8 +62,27 @@ public class IronGolem extends AbstractGolem implements NeutralMob {
- super(type, world);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.ironGolemRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.ironGolemRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.ironGolemControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void registerGoals() {
-+ if (level().purpurConfig.ironGolemCanSwim) this.goalSelector.addGoal(0, new net.minecraft.world.entity.ai.goal.FloatGoal(this)); // Purpur - Ridables
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.0D, true));
- this.goalSelector.addGoal(2, new MoveTowardsTargetGoal(this, 0.9D, 32.0F));
- this.goalSelector.addGoal(2, new MoveBackToVillageGoal(this, 0.6D, false));
-@@ -71,6 +90,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob {
- this.goalSelector.addGoal(5, new OfferFlowerGoal(this));
- this.goalSelector.addGoal(7, new LookAtPlayerGoal(this, Player.class, 6.0F));
- this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.targetSelector.addGoal(1, new DefendVillageTargetGoal(this));
- this.targetSelector.addGoal(2, new HurtByTargetGoal(this, new Class[0]));
- this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt));
-@@ -267,13 +287,13 @@ public class IronGolem extends AbstractGolem implements NeutralMob {
- ItemStack itemstack = player.getItemInHand(hand);
-
- if (!itemstack.is(Items.IRON_INGOT)) {
-- return InteractionResult.PASS;
-+ return tryRide(player, hand); // Purpur - Ridables
- } else {
- float f = this.getHealth();
-
- this.heal(25.0F);
- if (this.getHealth() == f) {
-- return InteractionResult.PASS;
-+ return tryRide(player, hand); // Purpur - Ridables
- } else {
- float f1 = 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F;
-
-diff --git a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java
-index b04532aa04aec6ebbff74d64abb73189c2e12016..f37c8efa34efcb289bbeed06ea2d3860ff2662ac 100644
---- a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java
-@@ -64,6 +64,23 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder(this, Chicken.class, false));
- this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, false, false, Turtle.BABY_ON_LAND_SELECTOR));
- }
-diff --git a/src/main/java/net/minecraft/world/entity/animal/Panda.java b/src/main/java/net/minecraft/world/entity/animal/Panda.java
-index be753557d7ebd6f1e82b1bdb6d60ecc450f72eec..20d18f7bd8e9c1b3e3a6d06a11c9072456cd742f 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Panda.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java
-@@ -112,6 +112,32 @@ public class Panda extends Animal {
-
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.pandaRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.pandaRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.pandaControllable;
-+ }
-+
-+ @Override
-+ public void onMount(Player rider) {
-+ super.onMount(rider);
-+ setForwardMot(0.0F);
-+ sit(false);
-+ eat(false);
-+ setOnBack(false);
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected boolean canDispenserEquipIntoSlot(EquipmentSlot slot) {
- return slot == EquipmentSlot.MAINHAND && this.canPickUpLoot();
-@@ -271,6 +297,7 @@ public class Panda extends Animal {
- @Override
- protected void registerGoals() {
- this.goalSelector.addGoal(0, new FloatGoal(this));
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(2, new Panda.PandaPanicGoal(this, 2.0D));
- this.goalSelector.addGoal(2, new Panda.PandaBreedGoal(this, 1.0D));
- this.goalSelector.addGoal(3, new Panda.PandaAttackGoal(this, 1.2000000476837158D, true));
-@@ -288,6 +315,7 @@ public class Panda extends Animal {
- this.goalSelector.addGoal(12, new Panda.PandaRollGoal(this));
- this.goalSelector.addGoal(13, new FollowParentGoal(this, 1.25D));
- this.goalSelector.addGoal(14, new WaterAvoidingRandomStrollGoal(this, 1.0D));
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.targetSelector.addGoal(1, (new Panda.PandaHurtByTargetGoal(this, new Class[0])).setAlertOthers(new Class[0]));
- }
-
-@@ -640,7 +668,7 @@ public class Panda extends Animal {
- ItemStack itemstack = player.getItemInHand(hand);
-
- if (this.isScared()) {
-- return InteractionResult.PASS;
-+ return tryRide(player, hand); // Purpur - Ridables
- } else if (this.isOnBack()) {
- this.setOnBack(false);
- return InteractionResult.SUCCESS;
-@@ -679,12 +707,12 @@ public class Panda extends Animal {
- }
- }
-
-- return InteractionResult.PASS;
-+ return tryRide(player, hand); // Purpur - Ridables
- }
-
- return InteractionResult.SUCCESS_SERVER;
- } else {
-- return InteractionResult.PASS;
-+ return tryRide(player, hand); // Purpur - Ridables
- }
- }
-
-@@ -729,7 +757,7 @@ public class Panda extends Animal {
- return itemEntity.getItem().is(ItemTags.PANDA_EATS_FROM_GROUND) && itemEntity.isAlive() && !itemEntity.hasPickUpDelay();
- }
-
-- private static class PandaMoveControl extends MoveControl {
-+ private static class PandaMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables
-
- private final Panda panda;
-
-@@ -739,9 +767,9 @@ public class Panda extends Animal {
- }
-
- @Override
-- public void tick() {
-+ public void vanillaTick() { // Purpur - Ridables
- if (this.panda.canPerformAction()) {
-- super.tick();
-+ super.vanillaTick(); // Purpur - Ridables
- }
- }
- }
-diff --git a/src/main/java/net/minecraft/world/entity/animal/Parrot.java b/src/main/java/net/minecraft/world/entity/animal/Parrot.java
-index a2f0b79599799ad2aa85aff821d8ac76a8e650bd..872f2406531bf71f378325441b7215c085f1a70d 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Parrot.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Parrot.java
-@@ -125,12 +125,68 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder type, Level world) {
- super(type, world);
-- this.moveControl = new FlyingMoveControl(this, 10, false);
-+ // Purpur start - Ridables
-+ final org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD flyingController = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.3F);
-+ class ParrotMoveControl extends FlyingMoveControl {
-+ public ParrotMoveControl(Mob entity, int maxPitchChange, boolean noGravity) {
-+ super(entity, maxPitchChange, noGravity);
-+ }
-+
-+ @Override
-+ public void tick() {
-+ if (mob.getRider() != null && mob.isControllable()) {
-+ flyingController.purpurTick(mob.getRider());
-+ } else {
-+ super.tick();
-+ }
-+ }
-+
-+ @Override
-+ public boolean hasWanted() {
-+ return mob.getRider() != null && mob.isControllable() ? getForwardMot() != 0 || getStrafeMot() != 0 : super.hasWanted();
-+ }
-+ }
-+ this.moveControl = new ParrotMoveControl(this, 10, false);
-+ // Purpur end - Ridables
- this.setPathfindingMalus(PathType.DANGER_FIRE, -1.0F);
- this.setPathfindingMalus(PathType.DAMAGE_FIRE, -1.0F);
- this.setPathfindingMalus(PathType.COCOA, -1.0F);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.parrotRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.parrotRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.parrotControllable;
-+ }
-+
-+ @Override
-+ public double getMaxY() {
-+ return level().purpurConfig.parrotMaxY;
-+ }
-+
-+ @Override
-+ public void travel(Vec3 vec3) {
-+ super.travel(vec3);
-+ if (getRider() != null && this.isControllable() && !onGround) {
-+ float speed = (float) getAttributeValue(Attributes.FLYING_SPEED) * 2;
-+ setSpeed(speed);
-+ Vec3 mot = getDeltaMovement();
-+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 0.25, speed));
-+ setDeltaMovement(mot.scale(0.9D));
-+ }
-+ }
-+ // Purpur end - Ridables
-+
- @Nullable
- @Override
- public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData entityData) {
-@@ -149,8 +205,10 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder {
-diff --git a/src/main/java/net/minecraft/world/entity/animal/PolarBear.java b/src/main/java/net/minecraft/world/entity/animal/PolarBear.java
-index cd72d8f766069796ce1fe4a83b8646692005ff8c..6f30cdc26054a4ed7c577cd4e9aa29ade1d2ede5 100644
---- a/src/main/java/net/minecraft/world/entity/animal/PolarBear.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/PolarBear.java
-@@ -59,11 +59,40 @@ public class PolarBear extends Animal implements NeutralMob {
- private int remainingPersistentAngerTime;
- @Nullable
- private UUID persistentAngerTarget;
-+ private int standTimer = 0; // Purpur - Ridables
-
- public PolarBear(EntityType extends PolarBear> type, Level world) {
- super(type, world);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.polarBearRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.polarBearRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.polarBearControllable;
-+ }
-+
-+ @Override
-+ public boolean onSpacebar() {
-+ if (!isStanding()) {
-+ if (getRider() != null && getRider().getForwardMot() == 0 && getRider().getStrafeMot() == 0) {
-+ setStanding(true);
-+ playSound(SoundEvents.POLAR_BEAR_WARNING, 1.0F, 1.0F);
-+ }
-+ }
-+ return false;
-+ }
-+ // Purpur end - Ridables
-+
- @Nullable
- @Override
- public AgeableMob getBreedOffspring(ServerLevel world, AgeableMob entity) {
-@@ -79,6 +108,7 @@ public class PolarBear extends Animal implements NeutralMob {
- protected void registerGoals() {
- super.registerGoals();
- this.goalSelector.addGoal(0, new FloatGoal(this));
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(1, new PolarBear.PolarBearMeleeAttackGoal());
- this.goalSelector
- .addGoal(1, new PanicGoal(this, 2.0, polarBear -> polarBear.isBaby() ? DamageTypeTags.PANIC_CAUSES : DamageTypeTags.PANIC_ENVIRONMENTAL_CAUSES));
-@@ -86,6 +116,7 @@ public class PolarBear extends Animal implements NeutralMob {
- this.goalSelector.addGoal(5, new RandomStrollGoal(this, 1.0));
- this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F));
- this.goalSelector.addGoal(7, new RandomLookAroundGoal(this));
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.targetSelector.addGoal(1, new PolarBear.PolarBearHurtByTargetGoal());
- this.targetSelector.addGoal(2, new PolarBear.PolarBearAttackPlayersGoal());
- this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt));
-@@ -204,6 +235,12 @@ public class PolarBear extends Animal implements NeutralMob {
- if (!this.level().isClientSide) {
- this.updatePersistentAnger((ServerLevel)this.level(), true);
- }
-+
-+ // Purpur start - Ridables
-+ if (isStanding() && --standTimer <= 0) {
-+ setStanding(false);
-+ }
-+ // Purpur end - Ridables
- }
-
- @Override
-@@ -223,6 +260,7 @@ public class PolarBear extends Animal implements NeutralMob {
-
- public void setStanding(boolean warning) {
- this.entityData.set(DATA_STANDING_ID, warning);
-+ standTimer = warning ? 20 : -1; // Purpur - Ridables
- }
-
- public float getStandingAnimationScale(float tickDelta) {
-diff --git a/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java b/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java
-index cdb74f86ee92ee143af29962a85d45ca585cee44..93c1b945672a769ef6ee285efdcebd3717caf9f1 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java
-@@ -52,6 +52,18 @@ public class Pufferfish extends AbstractFish {
- this.refreshDimensions();
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.pufferfishRidable;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.pufferfishControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void defineSynchedData(SynchedEntityData.Builder builder) {
- super.defineSynchedData(builder);
-diff --git a/src/main/java/net/minecraft/world/entity/animal/Rabbit.java b/src/main/java/net/minecraft/world/entity/animal/Rabbit.java
-index 53d60d62686f9b6bc98b6b25e4315b848600a99d..b5601b99401f2c3cf4ce0fef4497b09667083412 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Rabbit.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Rabbit.java
-@@ -88,6 +88,7 @@ public class Rabbit extends Animal implements VariantHolder {
- private boolean wasOnGround;
- private int jumpDelayTicks;
- public int moreCarrotTicks;
-+ private boolean actualJump; // Purpur - Ridables
-
- public Rabbit(EntityType extends Rabbit> type, Level world) {
- super(type, world);
-@@ -95,9 +96,55 @@ public class Rabbit extends Animal implements VariantHolder {
- this.moveControl = new Rabbit.RabbitMoveControl(this);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.rabbitRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.rabbitRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.rabbitControllable;
-+ }
-+
-+ @Override
-+ public boolean onSpacebar() {
-+ if (onGround) {
-+ actualJump = true;
-+ jumpFromGround();
-+ actualJump = false;
-+ }
-+ return true;
-+ }
-+
-+ private void handleJumping() {
-+ if (onGround) {
-+ RabbitJumpControl jumpController = (RabbitJumpControl) jumpControl;
-+ if (!wasOnGround) {
-+ setJumping(false);
-+ jumpController.setCanJump(false);
-+ }
-+ if (!jumpController.wantJump()) {
-+ if (moveControl.hasWanted()) {
-+ startJumping();
-+ }
-+ } else if (!jumpController.canJump()) {
-+ jumpController.setCanJump(true);
-+ }
-+ }
-+ wasOnGround = onGround;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- public void registerGoals() {
- this.goalSelector.addGoal(1, new FloatGoal(this));
-+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(1, new ClimbOnTopOfPowderSnowGoal(this, this.level()));
- this.goalSelector.addGoal(1, new Rabbit.RabbitPanicGoal(this, 2.2D));
- this.goalSelector.addGoal(2, new BreedGoal(this, 0.8D));
-@@ -114,6 +161,14 @@ public class Rabbit extends Animal implements VariantHolder {
-
- @Override
- protected float getJumpPower() {
-+ // Purpur start - Ridables
-+ if (getRider() != null && this.isControllable()) {
-+ if (getForwardMot() < 0) {
-+ setSpeed(getForwardMot() * 2F);
-+ }
-+ return actualJump ? 0.5F : 0.3F;
-+ }
-+ // Purpur end - Ridables
- float f = 0.3F;
-
- if (this.moveControl.getSpeedModifier() <= 0.6D) {
-@@ -188,6 +243,12 @@ public class Rabbit extends Animal implements VariantHolder {
-
- @Override
- public void customServerAiStep(ServerLevel world) {
-+ // Purpur start - Ridables
-+ if (getRider() != null && this.isControllable()) {
-+ handleJumping();
-+ return;
-+ }
-+ // Purpur end - Ridables
- if (this.jumpDelayTicks > 0) {
- --this.jumpDelayTicks;
- }
-@@ -469,7 +530,7 @@ public class Rabbit extends Animal implements VariantHolder {
- }
- }
-
-- private static class RabbitMoveControl extends MoveControl {
-+ private static class RabbitMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables
-
- private final Rabbit rabbit;
- private double nextJumpSpeed;
-@@ -480,14 +541,14 @@ public class Rabbit extends Animal implements VariantHolder {
- }
-
- @Override
-- public void tick() {
-+ public void vanillaTick() { // Purpur - Ridables
- if (this.rabbit.onGround() && !this.rabbit.jumping && !((Rabbit.RabbitJumpControl) this.rabbit.jumpControl).wantJump()) {
- this.rabbit.setSpeedModifier(0.0D);
- } else if (this.hasWanted() || this.operation == MoveControl.Operation.JUMPING) {
- this.rabbit.setSpeedModifier(this.nextJumpSpeed);
- }
-
-- super.tick();
-+ super.vanillaTick(); // Purpur - Ridables
- }
-
- @Override
-diff --git a/src/main/java/net/minecraft/world/entity/animal/Salmon.java b/src/main/java/net/minecraft/world/entity/animal/Salmon.java
-index 500259e6f297276fb3d6943c2bf88c844d4ec7e4..9bc58ca4556baf6f6bc494ae249c11c5c627f86c 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Salmon.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Salmon.java
-@@ -35,6 +35,18 @@ public class Salmon extends AbstractSchoolingFish implements VariantHolder {
-diff --git a/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java b/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java
-index fd9f6c17448a4d87f940eb8f544ecb9669068582..0ba9aa45902cbad16ee0356cb3051b12923cfe10 100644
---- a/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java
-@@ -55,12 +55,31 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM
- super(type, world);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.snowGolemRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.snowGolemRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.snowGolemControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void registerGoals() {
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(1, new RangedAttackGoal(this, 1.25D, 20, 10.0F));
- this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 1.0D, 1.0000001E-5F));
- this.goalSelector.addGoal(3, new LookAtPlayerGoal(this, Player.class, 6.0F));
- this.goalSelector.addGoal(4, new RandomLookAroundGoal(this));
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Mob.class, 10, true, false, (entityliving, worldserver) -> {
- return entityliving instanceof Enemy;
- }));
-@@ -110,6 +129,7 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM
- return;
- }
-
-+ if (getRider() != null && this.isControllable() && !level().purpurConfig.snowGolemLeaveTrailWhenRidden) return; // Purpur - don't leave snow trail when being ridden
- BlockState iblockdata = Blocks.SNOW.defaultBlockState();
-
- for (int i = 0; i < 4; ++i) {
-@@ -166,7 +186,7 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM
- org.bukkit.event.player.PlayerShearEntityEvent event = CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand, drops);
- if (event != null) {
- if (event.isCancelled()) {
-- return InteractionResult.PASS;
-+ return tryRide(player, hand); // Purpur - Ridables
- }
- drops = org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getDrops());
- // Paper end - custom shear drops
-@@ -179,7 +199,7 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM
-
- return InteractionResult.SUCCESS;
- } else {
-- return InteractionResult.PASS;
-+ return tryRide(player, hand); // Purpur - Ridables
- }
- }
-
-diff --git a/src/main/java/net/minecraft/world/entity/animal/Squid.java b/src/main/java/net/minecraft/world/entity/animal/Squid.java
-index 97a3f0ab3dfca24991051395229dd4c601a66fa0..2d0ecae9625e08ecf5028ec62f71a5432c462712 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Squid.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Squid.java
-@@ -50,9 +50,32 @@ public class Squid extends AgeableWaterCreature {
- this.tentacleSpeed = 1.0F / (this.random.nextFloat() + 1.0F) * 0.2F;
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.squidRidable;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.squidControllable;
-+ }
-+
-+ protected static void rotateVectorAroundY(org.bukkit.util.Vector vector, double degrees) {
-+ double rad = Math.toRadians(degrees);
-+ double cos = Math.cos(rad);
-+ double sine = Math.sin(rad);
-+ double x = vector.getX();
-+ double z = vector.getZ();
-+ vector.setX(cos * x - sine * z);
-+ vector.setZ(sine * x + cos * z);
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void registerGoals() {
- this.goalSelector.addGoal(0, new Squid.SquidRandomMovementGoal(this));
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(1, new Squid.SquidFleeGoal());
- }
-
-@@ -305,6 +328,37 @@ public class Squid extends AgeableWaterCreature {
-
- @Override
- public void tick() {
-+ // Purpur start - Ridables
-+ net.minecraft.world.entity.player.Player rider = squid.getRider();
-+ if (rider != null && squid.isControllable()) {
-+ if (rider.jumping) {
-+ squid.onSpacebar();
-+ }
-+ float forward = rider.getForwardMot();
-+ float strafe = rider.getStrafeMot();
-+ float speed = (float) squid.getAttributeValue(Attributes.MOVEMENT_SPEED) * 10F;
-+ if (forward < 0.0F) {
-+ speed *= -0.5;
-+ }
-+ org.bukkit.util.Vector dir = rider.getBukkitEntity().getEyeLocation().getDirection().normalize().multiply(speed / 20.0F);
-+ if (strafe != 0.0F) {
-+ if (forward == 0.0F) {
-+ dir.setY(0);
-+ rotateVectorAroundY(dir, strafe > 0.0F ? -90 : 90);
-+ } else if (forward < 0.0F) {
-+ rotateVectorAroundY(dir, strafe > 0.0F ? 45 : -45);
-+ } else {
-+ rotateVectorAroundY(dir, strafe > 0.0F ? -45 : 45);
-+ }
-+ }
-+ if (forward != 0.0F || strafe != 0.0F) {
-+ squid.movementVector = new Vec3((float) dir.getX(), (float) dir.getY(), (float) dir.getZ());
-+ } else {
-+ squid.movementVector = Vec3.ZERO;
-+ }
-+ return;
-+ }
-+ // Purpur end - Ridables
- int i = this.squid.getNoActionTime();
- if (i > 100) {
- this.squid.movementVector = Vec3.ZERO;
-diff --git a/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java b/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java
-index 8d59d606bdaaea7c64389572b2810b65414a1533..df9d2d4d285f51d6e8e5bc781699c20fe1c2d00d 100644
---- a/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java
-@@ -67,6 +67,18 @@ public class TropicalFish extends AbstractSchoolingFish implements VariantHolder
- super(type, world);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.tropicalFishRidable;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.tropicalFishControllable;
-+ }
-+ // Purpur end - Ridables
-+
- public static String getPredefinedName(int variant) {
- return "entity.minecraft.tropical_fish.predefined." + variant;
- }
-diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java
-index d6605c15111dbdb6ee61a24822bc0a9aed7198d6..0fc805540305dd8d34b903d5b7769816578c19c3 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java
-@@ -86,6 +86,23 @@ public class Turtle extends Animal {
- this.moveControl = new Turtle.TurtleMoveControl(this);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.turtleRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.turtleRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.turtleControllable;
-+ }
-+ // Purpur end - Ridables
-+
- public void setHomePos(BlockPos pos) {
- this.entityData.set(Turtle.HOME_POS, pos);
- }
-@@ -188,6 +205,7 @@ public class Turtle extends Animal {
-
- @Override
- protected void registerGoals() {
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(0, new Turtle.TurtlePanicGoal(this, 1.2D));
- this.goalSelector.addGoal(1, new Turtle.TurtleBreedGoal(this, 1.0D));
- this.goalSelector.addGoal(1, new Turtle.TurtleLayEggGoal(this, 1.0D));
-@@ -349,13 +367,15 @@ public class Turtle extends Animal {
- return this.isBaby() ? Turtle.BABY_DIMENSIONS : super.getDefaultDimensions(pose);
- }
-
-- private static class TurtleMoveControl extends MoveControl {
-+ private static class TurtleMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables
-
- private final Turtle turtle;
-+ private final org.purpurmc.purpur.controller.WaterMoveControllerWASD waterController; // Purpur - Ridables
-
- TurtleMoveControl(Turtle turtle) {
- super(turtle);
- this.turtle = turtle;
-+ waterController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(turtle, 0.25D); // Purpur - Ridables
- }
-
- private void updateSpeed() {
-@@ -375,7 +395,7 @@ public class Turtle extends Animal {
- }
-
- @Override
-- public void tick() {
-+ public void vanillaTick() { // Purpur - Ridables
- this.updateSpeed();
- if (this.operation == MoveControl.Operation.MOVE_TO && !this.turtle.getNavigation().isDone()) {
- double d0 = this.wantedX - this.turtle.getX();
-@@ -391,7 +411,7 @@ public class Turtle extends Animal {
-
- this.turtle.setYRot(this.rotlerp(this.turtle.getYRot(), f, 90.0F));
- this.turtle.yBodyRot = this.turtle.getYRot();
-- float f1 = (float) (this.speedModifier * this.turtle.getAttributeValue(Attributes.MOVEMENT_SPEED));
-+ float f1 = (float) (this.getSpeedModifier() * this.turtle.getAttributeValue(Attributes.MOVEMENT_SPEED));
-
- this.turtle.setSpeed(Mth.lerp(0.125F, this.turtle.getSpeed(), f1));
- this.turtle.setDeltaMovement(this.turtle.getDeltaMovement().add(0.0D, (double) this.turtle.getSpeed() * d1 * 0.1D, 0.0D));
-diff --git a/src/main/java/net/minecraft/world/entity/animal/Wolf.java b/src/main/java/net/minecraft/world/entity/animal/Wolf.java
-index c57fac6b5a17f39699298a58d9d25c12da929e64..5ac51a56c451a8fde4b98cec165143b2128fd9eb 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Wolf.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Wolf.java
-@@ -124,9 +124,32 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder(this, Llama.class, 24.0F, 1.5D, 1.5D));
-@@ -138,6 +161,7 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder type, Level world) {
- super(type, world);
-- this.moveControl = new FlyingMoveControl(this, 20, true);
-+ // Purpur start - Ridables
-+ this.purpurController = new org.purpurmc.purpur.controller.FlyingMoveControllerWASD(this, 0.1F, 0.5F);
-+ this.moveControl = new FlyingMoveControl(this, 20, true) {
-+ @Override
-+ public void tick() {
-+ if (mob.getRider() != null && mob.isControllable()) {
-+ purpurController.purpurTick(mob.getRider());
-+ } else {
-+ super.tick();
-+ }
-+ }
-+ };
-+ // Purpur end - Ridables
- this.setCanPickUpLoot(this.canPickUpLoot());
- this.vibrationUser = new Allay.VibrationUser();
- this.vibrationData = new VibrationSystem.Data();
-@@ -121,6 +134,28 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS
- }
- // CraftBukkit end
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.allayRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.allayRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.allayControllable;
-+ }
-+
-+ @Override
-+ protected void registerGoals() {
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected Brain.Provider brainProvider() {
- return Brain.provider(Allay.MEMORY_TYPES, Allay.SENSOR_TYPES);
-@@ -228,6 +263,7 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS
- ProfilerFiller gameprofilerfiller = Profiler.get();
-
- gameprofilerfiller.push("allayBrain");
-+ //if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider
- this.getBrain().tick(world, this);
- gameprofilerfiller.pop();
- gameprofilerfiller.push("allayActivityUpdate");
-diff --git a/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java b/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java
-index c1ef714096159608752d744b98f615cd45fe459a..4c9771725f9567790841094dae72c2bbf0d5ba62 100644
---- a/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java
-@@ -82,6 +82,23 @@ public class Armadillo extends Animal {
- return Animal.createAnimalAttributes().add(Attributes.MAX_HEALTH, 12.0D).add(Attributes.MOVEMENT_SPEED, 0.14D);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.armadilloRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.armadilloRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.armadilloControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void defineSynchedData(SynchedEntityData.Builder builder) {
- super.defineSynchedData(builder);
-diff --git a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java
-index 31b10cd404b672d7ce21c2107d8f83e32de26ef4..b414edf515890066bd970f65c073964839851840 100644
---- a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java
-@@ -100,6 +100,23 @@ public class Axolotl extends Animal implements VariantHolder, B
- this.lookControl = new Axolotl.AxolotlLookControl(this, 20);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.axolotlRidable;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.axolotlControllable;
-+ }
-+
-+ @Override
-+ protected void registerGoals() {
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- public float getWalkTargetValue(BlockPos pos, LevelReader world) {
- return 0.0F;
-@@ -297,6 +314,7 @@ public class Axolotl extends Animal implements VariantHolder, B
- ProfilerFiller gameprofilerfiller = Profiler.get();
-
- gameprofilerfiller.push("axolotlBrain");
-+ //if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider
- this.getBrain().tick(world, this);
- gameprofilerfiller.pop();
- gameprofilerfiller.push("axolotlActivityUpdate");
-@@ -520,14 +538,22 @@ public class Axolotl extends Animal implements VariantHolder, B
- private static class AxolotlMoveControl extends SmoothSwimmingMoveControl {
-
- private final Axolotl axolotl;
-+ private final org.purpurmc.purpur.controller.WaterMoveControllerWASD waterController; // Purpur - Ridables
-
- public AxolotlMoveControl(Axolotl axolotl) {
- super(axolotl, 85, 10, 0.1F, 0.5F, false);
- this.axolotl = axolotl;
-+ waterController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(axolotl, 0.5D); // Purpur - Ridables
- }
-
- @Override
- public void tick() {
-+ // Purpur start - Ridables
-+ if (axolotl.getRider() != null && axolotl.isControllable()) {
-+ waterController.purpurTick(axolotl.getRider());
-+ return;
-+ }
-+ // Purpur end - Ridables
- if (!this.axolotl.isPlayingDead()) {
- super.tick();
- }
-@@ -542,9 +568,9 @@ public class Axolotl extends Animal implements VariantHolder, B
- }
-
- @Override
-- public void tick() {
-+ public void vanillaTick() { // Purpur - Ridables
- if (!Axolotl.this.isPlayingDead()) {
-- super.tick();
-+ super.vanillaTick(); // Purpur - Ridables
- }
-
- }
-diff --git a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java
-index f3c884ab9c09f04dd01cabf2ee9de3b5b620563d..f794ac7af227d413ed030457cbe4cd68e6eca969 100644
---- a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java
-@@ -87,6 +87,13 @@ public class Camel extends AbstractHorse {
- navigation.setCanWalkOverFences(true);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.camelRidableInWater;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- public void addAdditionalSaveData(CompoundTag nbt) {
- super.addAdditionalSaveData(nbt);
-diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java
-index ca04e5d829331551a2c2f44e223ff05c6ce04e76..b42e1e1749c9f18b3bf842517522e90ba60330ff 100644
---- a/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/frog/Frog.java
-@@ -106,6 +106,8 @@ public class Frog extends Animal implements VariantHolder> {
- public final AnimationState croakAnimationState = new AnimationState();
- public final AnimationState tongueAnimationState = new AnimationState();
- public final AnimationState swimIdleAnimationState = new AnimationState();
-+ private org.purpurmc.purpur.controller.MoveControllerWASD purpurLandController; // Purpur - Ridables
-+ private org.purpurmc.purpur.controller.WaterMoveControllerWASD purpurWaterController; // Purpur - Ridables
-
- public Frog(EntityType extends Animal> type, Level world) {
- super(type, world);
-@@ -113,7 +115,55 @@ public class Frog extends Animal implements VariantHolder> {
- this.setPathfindingMalus(PathType.WATER, 4.0F);
- this.setPathfindingMalus(PathType.TRAPDOOR, -1.0F);
- this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true);
-+ // Purpur start - Ridables
-+ this.purpurLandController = new org.purpurmc.purpur.controller.MoveControllerWASD(this, 0.2F);
-+ this.purpurWaterController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(this, 0.5F);
-+ this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true) {
-+ @Override
-+ public void tick() {
-+ net.minecraft.world.entity.player.Player rider = mob.getRider();
-+ if (rider != null && mob.isControllable()) {
-+ if (mob.isInWater()) {
-+ purpurWaterController.purpurTick(rider);
-+ mob.setDeltaMovement(mob.getDeltaMovement().add(0.0D, -0.005D, 0.0D));
-+ } else {
-+ purpurLandController.purpurTick(rider);
-+ }
-+ } else {
-+ super.tick();
-+ }
-+ }
-+ };
-+ // Purpur end - Ridables
-+ }
-+
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.frogRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.frogRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.frogControllable;
-+ }
-+
-+ @Override
-+ protected void registerGoals() {
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
-+ }
-+
-+ @Override
-+ public float getJumpPower() {
-+ return (getRider() != null && isControllable()) ? level().purpurConfig.frogRidableJumpHeight * this.getBlockJumpFactor() : super.getJumpPower();
- }
-+ // Purpur end - Ridables
-
- @Override
- protected Brain.Provider brainProvider() {
-@@ -188,6 +238,7 @@ public class Frog extends Animal implements VariantHolder> {
- protected void customServerAiStep(ServerLevel world) {
- ProfilerFiller profilerFiller = Profiler.get();
- profilerFiller.push("frogBrain");
-+ //if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider
- this.getBrain().tick(world, this);
- profilerFiller.pop();
- profilerFiller.push("frogActivityUpdate");
-@@ -384,7 +435,7 @@ public class Frog extends Animal implements VariantHolder> {
- return world.getBlockState(pos.below()).is(BlockTags.FROGS_SPAWNABLE_ON) && isBrightEnoughToSpawn(world, pos);
- }
-
-- class FrogLookControl extends LookControl {
-+ class FrogLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - Ridables
- FrogLookControl(final Mob entity) {
- super(entity);
- }
-diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java
-index 48ac8c3f6e00c3c2dc67b6c994be7c0ac6dfcf81..87a2825d4e198e152a601f20105596a793695885 100644
---- a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java
-@@ -51,13 +51,50 @@ public class Tadpole extends AbstractFish {
- protected static final ImmutableList>> SENSOR_TYPES = ImmutableList.of(SensorType.NEAREST_LIVING_ENTITIES, SensorType.NEAREST_PLAYERS, SensorType.HURT_BY, SensorType.FROG_TEMPTATIONS);
- protected static final ImmutableList> MEMORY_TYPES = ImmutableList.of(MemoryModuleType.LOOK_TARGET, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.NEAREST_VISIBLE_ADULT, MemoryModuleType.TEMPTATION_COOLDOWN_TICKS, MemoryModuleType.IS_TEMPTED, MemoryModuleType.TEMPTING_PLAYER, MemoryModuleType.BREED_TARGET, MemoryModuleType.IS_PANICKING);
- public boolean ageLocked; // Paper
-+ private org.purpurmc.purpur.controller.WaterMoveControllerWASD purpurController; // Purpur - Ridables
-
- public Tadpole(EntityType extends AbstractFish> type, Level world) {
- super(type, world);
-- this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true);
-+ // Purpur start - Ridables
-+ this.purpurController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(this, 0.5F);
-+ this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true) {
-+ @Override
-+ public void tick() {
-+ Player rider = mob.getRider();
-+ if (rider != null && mob.isControllable()) {
-+ purpurController.purpurTick(rider);
-+ mob.setDeltaMovement(mob.getDeltaMovement().add(0.0D, 0.002D, 0.0D));
-+ } else {
-+ super.tick();
-+ }
-+ }
-+ };
-+ // Purpur end - Ridables
- this.lookControl = new SmoothSwimmingLookControl(this, 10);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.tadpoleRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.tadpoleRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.tadpoleControllable;
-+ }
-+
-+ @Override
-+ protected void registerGoals() {
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected PathNavigation createNavigation(Level world) {
- return new WaterBoundPathNavigation(this, world);
-@@ -88,6 +125,7 @@ public class Tadpole extends AbstractFish {
- ProfilerFiller gameprofilerfiller = Profiler.get();
-
- gameprofilerfiller.push("tadpoleBrain");
-+ //if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider
- this.getBrain().tick(world, this);
- gameprofilerfiller.pop();
- gameprofilerfiller.push("tadpoleActivityUpdate");
-diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java
-index 76aca47d8638d5c37c57d3a59fa7f8ceaa5a53b4..307d11e5ad6d0b47af36b1469a73ae1caa100232 100644
---- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java
-@@ -93,6 +93,23 @@ public class Goat extends Animal {
- });
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.goatRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.goatRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.goatControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected Brain.Provider brainProvider() {
- return Brain.provider(Goat.MEMORY_TYPES, Goat.SENSOR_TYPES);
-@@ -197,6 +214,7 @@ public class Goat extends Animal {
- ProfilerFiller gameprofilerfiller = Profiler.get();
-
- gameprofilerfiller.push("goatBrain");
-+ //if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider
- this.getBrain().tick(world, this);
- gameprofilerfiller.pop();
- gameprofilerfiller.push("goatActivityUpdate");
-diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java
-index 8aed30cdbbfdd42c20dcd4c8773c8a0ee21a980d..d9e4eb76209abffd0079ccdbbd2fc3f29bd67052 100644
---- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java
-@@ -228,11 +228,21 @@ public abstract class AbstractHorse extends Animal implements ContainerListener,
-
- protected AbstractHorse(EntityType extends AbstractHorse> type, Level world) {
- super(type, world);
-+ this.moveControl = new net.minecraft.world.entity.ai.control.MoveControl(this); // Purpur - use vanilla controller
-+ this.lookControl = new net.minecraft.world.entity.ai.control.LookControl(this); // Purpur - use vanilla controller
- this.createInventory();
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return false; // vanilla handles
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void registerGoals() {
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HorseHasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(1, new PanicGoal(this, 1.2D));
- this.goalSelector.addGoal(1, new RunAroundLikeCrazyGoal(this, 1.2D));
- this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D, AbstractHorse.class));
-@@ -243,6 +253,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener,
- if (this.canPerformRearing()) {
- this.goalSelector.addGoal(9, new RandomStandGoal(this));
- }
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HorseHasRider(this)); // Purpur - Ridables
-
- this.addBehaviourGoals();
- }
-diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java b/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java
-index 5cafdde956d7a5b00cd5aec5c44849639307363d..f4ef46c6cf40993b878ee965a0af397894231ba6 100644
---- a/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java
-@@ -16,6 +16,13 @@ public class Donkey extends AbstractChestedHorse {
- super(type, world);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.donkeyRidableInWater;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected SoundEvent getAmbientSound() {
- return SoundEvents.DONKEY_AMBIENT;
-diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java b/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java
-index b5ec7c8ad0e482930d1a54b590b26093f4e477ea..4505bcc6ab70ee2bc969ecfaecf7cff072f48ca1 100644
---- a/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java
-@@ -43,6 +43,13 @@ public class Horse extends AbstractHorse implements VariantHolder {
- super(type, world);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.horseRidableInWater;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void randomizeAttributes(RandomSource random) {
- this.getAttribute(Attributes.MAX_HEALTH).setBaseValue((double)generateMaxHealth(random::nextInt));
-diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java
-index 18bd483fe46de3d9dc129bffbccfba9d4cab9550..0e15cb99cab5ed664dc265f3754b9da7fef8958f 100644
---- a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java
-@@ -77,7 +77,51 @@ public class Llama extends AbstractChestedHorse implements VariantHolder entitytypes, Level world) {
- super(EntityType.ENDER_DRAGON, world);
-@@ -129,6 +130,37 @@ public class EnderDragon extends Mob implements Enemy {
- this.noPhysics = true;
- this.phaseManager = new EnderDragonPhaseManager(this);
- this.explosionSource = new ServerExplosion(world.getMinecraftWorld(), this, null, null, new Vec3(Double.NaN, Double.NaN, Double.NaN), Float.NaN, true, Explosion.BlockInteraction.DESTROY); // CraftBukkit
-+
-+ // Purpur start - Ridables
-+ this.moveControl = new org.purpurmc.purpur.controller.FlyingMoveControllerWASD(this) {
-+ @Override
-+ public void vanillaTick() {
-+ // dragon doesn't use the controller. do nothing
-+ }
-+ };
-+ this.lookControl = new org.purpurmc.purpur.controller.LookControllerWASD(this) {
-+ @Override
-+ public void vanillaTick() {
-+ // dragon doesn't use the controller. do nothing
-+ }
-+
-+ @Override
-+ public void purpurTick(Player rider) {
-+ setYawPitch(rider.getYRot() - 180F, rider.xRotO * 0.5F);
-+ }
-+ };
-+ // Purpur end - Ridables
-+ }
-+
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.enderDragonRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.enderDragonRidableInWater;
- }
-
- public void setDragonFight(EndDragonFight fight) {
-@@ -143,6 +175,17 @@ public class EnderDragon extends Mob implements Enemy {
- return this.fightOrigin;
- }
-
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.enderDragonControllable;
-+ }
-+
-+ @Override
-+ public double getMaxY() {
-+ return level().purpurConfig.enderDragonMaxY;
-+ }
-+ // Purpur end - Ridables
-+
- public static AttributeSupplier.Builder createAttributes() {
- return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 200.0D);
- }
-@@ -184,6 +227,37 @@ public class EnderDragon extends Mob implements Enemy {
-
- @Override
- public void aiStep() {
-+ // Purpur start - Ridables
-+ boolean hasRider = getRider() != null && this.isControllable();
-+ if (hasRider) {
-+ if (!hadRider) {
-+ hadRider = true;
-+ noPhysics = false;
-+ this.dimensions = net.minecraft.world.entity.EntityDimensions.scalable(4.0F, 2.0F);
-+ }
-+
-+ // dragon doesn't use controllers, so must tick manually
-+ moveControl.tick();
-+ lookControl.tick();
-+
-+ moveRelative((float) getAttributeValue(Attributes.MOVEMENT_SPEED) * 0.1F, new Vec3(-getStrafeMot(), getVerticalMot(), -getForwardMot()));
-+ Vec3 mot = getDeltaMovement();
-+ setDeltaMovement(mot);
-+ move(MoverType.PLAYER, mot);
-+
-+ mot = mot.multiply(0.9F, 0.9F, 0.9F);
-+ setDeltaMovement(mot);
-+
-+ // control wing flap speed on client
-+ phaseManager.setPhase(mot.x() * mot.x() + mot.z() * mot.z() < 0.005F ? EnderDragonPhase.HOVERING : EnderDragonPhase.HOLDING_PATTERN);
-+ } else if (hadRider) {
-+ hadRider = false;
-+ noPhysics = true;
-+ this.dimensions = net.minecraft.world.entity.EntityDimensions.scalable(16.0F, 8.0F);
-+ phaseManager.setPhase(EnderDragonPhase.HOLDING_PATTERN); // HoldingPattern
-+ }
-+ // Purpur end - Ridables
-+
- this.processFlappingMovement();
- if (this.level().isClientSide) {
- this.setHealth(this.getHealth());
-@@ -210,6 +284,8 @@ public class EnderDragon extends Mob implements Enemy {
- float f;
-
- if (this.isDeadOrDying()) {
-+ if (hasRider) ejectPassengers(); // Purpur - Ridables
-+
- float f1 = (this.random.nextFloat() - 0.5F) * 8.0F;
-
- f = (this.random.nextFloat() - 0.5F) * 4.0F;
-@@ -222,9 +298,9 @@ public class EnderDragon extends Mob implements Enemy {
-
- f = 0.2F / ((float) vec3d.horizontalDistance() * 10.0F + 1.0F);
- f *= (float) Math.pow(2.0D, vec3d.y);
-- if (this.phaseManager.getCurrentPhase().isSitting()) {
-+ if (!hasRider && this.phaseManager.getCurrentPhase().isSitting()) { // Purpur - Ridables
- this.flapTime += 0.1F;
-- } else if (this.inWall) {
-+ } else if (!hasRider && this.inWall) { // Purpur - Ridables
- this.flapTime += f * 0.5F;
- } else {
- this.flapTime += f;
-@@ -240,7 +316,7 @@ public class EnderDragon extends Mob implements Enemy {
- float f4;
- float f5;
-
-- if (world1 instanceof ServerLevel) {
-+ if (world1 instanceof ServerLevel && !hasRider) { // Purpur - Ridables
- ServerLevel worldserver1 = (ServerLevel) world1;
- DragonPhaseInstance idragoncontroller = this.phaseManager.getCurrentPhase();
-
-@@ -326,7 +402,7 @@ public class EnderDragon extends Mob implements Enemy {
- if (world2 instanceof ServerLevel) {
- ServerLevel worldserver2 = (ServerLevel) world2;
-
-- if (this.hurtTime == 0) {
-+ if (!hasRider && this.hurtTime == 0) { // Purpur - Ridables
- this.knockBack(worldserver2, worldserver2.getEntities((Entity) this, this.wing1.getBoundingBox().inflate(4.0D, 2.0D, 4.0D).move(0.0D, -2.0D, 0.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR));
- this.knockBack(worldserver2, worldserver2.getEntities((Entity) this, this.wing2.getBoundingBox().inflate(4.0D, 2.0D, 4.0D).move(0.0D, -2.0D, 0.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR));
- this.hurt(worldserver2, worldserver2.getEntities((Entity) this, this.head.getBoundingBox().inflate(1.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR));
-@@ -374,7 +450,7 @@ public class EnderDragon extends Mob implements Enemy {
- if (world3 instanceof ServerLevel) {
- ServerLevel worldserver3 = (ServerLevel) world3;
-
-- this.inWall = this.checkWalls(worldserver3, this.head.getBoundingBox()) | this.checkWalls(worldserver3, this.neck.getBoundingBox()) | this.checkWalls(worldserver3, this.body.getBoundingBox());
-+ this.inWall = !hasRider && this.checkWalls(worldserver3, this.head.getBoundingBox()) | this.checkWalls(worldserver3, this.neck.getBoundingBox()) | this.checkWalls(worldserver3, this.body.getBoundingBox()); // Purpur - Ridables
- if (this.dragonFight != null) {
- this.dragonFight.updateDragon(this);
- }
-diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java
-index bd9e10f79eaf0d23908229b3ebc2227946a14843..236e8ea79119dcf21c066b7c250fe20e6752df9e 100644
---- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java
-+++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java
-@@ -86,16 +86,30 @@ public class WitherBoss extends Monster implements RangedAttackMob {
- return !entityliving.getType().is(EntityTypeTags.WITHER_FRIENDS) && entityliving.attackable();
- };
- private static final TargetingConditions TARGETING_CONDITIONS = TargetingConditions.forCombat().range(20.0D).selector(WitherBoss.LIVING_ENTITY_SELECTOR);
-+ private int shootCooldown = 0; // Purpur - Ridables
- // Paper start
- private boolean canPortal = false;
-
- public void setCanTravelThroughPortals(boolean canPortal) { this.canPortal = canPortal; }
- // Paper end
-+ private org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD purpurController; // Purpur - Ridables
-
- public WitherBoss(EntityType extends WitherBoss> type, Level world) {
- super(type, world);
- this.bossEvent = (ServerBossEvent) (new ServerBossEvent(this.getDisplayName(), BossEvent.BossBarColor.PURPLE, BossEvent.BossBarOverlay.PROGRESS)).setDarkenScreen(true);
-- this.moveControl = new FlyingMoveControl(this, 10, false);
-+ // Purpur start - Ridables
-+ this.purpurController = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.1F);
-+ this.moveControl = new FlyingMoveControl(this, 10, false) {
-+ @Override
-+ public void tick() {
-+ if (mob.getRider() != null && mob.isControllable()) {
-+ purpurController.purpurTick(mob.getRider());
-+ } else {
-+ super.tick();
-+ }
-+ }
-+ };
-+ // Purpur end - Ridables
- this.setHealth(this.getMaxHealth());
- this.xpReward = 50;
- }
-@@ -109,13 +123,114 @@ public class WitherBoss extends Monster implements RangedAttackMob {
- return navigationflying;
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.witherRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.witherRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.witherControllable;
-+ }
-+
-+ @Override
-+ public double getMaxY() {
-+ return level().purpurConfig.witherMaxY;
-+ }
-+
-+ @Override
-+ public void travel(Vec3 vec3) {
-+ super.travel(vec3);
-+ if (getRider() != null && this.isControllable() && !onGround) {
-+ float speed = (float) getAttributeValue(Attributes.FLYING_SPEED) * 5F;
-+ setSpeed(speed);
-+ Vec3 mot = getDeltaMovement();
-+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 0.5, speed));
-+ setDeltaMovement(mot.scale(0.9D));
-+ }
-+ }
-+
-+ @Override
-+ public void onMount(Player rider) {
-+ super.onMount(rider);
-+ this.entityData.set(DATA_TARGETS.get(0), 0);
-+ this.entityData.set(DATA_TARGETS.get(1), 0);
-+ this.entityData.set(DATA_TARGETS.get(2), 0);
-+ getNavigation().stop();
-+ shootCooldown = 20;
-+ }
-+
-+ @Override
-+ public boolean onClick(net.minecraft.world.InteractionHand hand) {
-+ return shoot(getRider(), hand == net.minecraft.world.InteractionHand.MAIN_HAND ? new int[]{1} : new int[]{2});
-+ }
-+
-+ public boolean shoot(@Nullable Player rider, int[] heads) {
-+ if (shootCooldown > 0) {
-+ return false;
-+ }
-+
-+ shootCooldown = 20;
-+ if (rider == null) {
-+ return false;
-+ }
-+
-+ org.bukkit.craftbukkit.entity.CraftHumanEntity player = rider.getBukkitEntity();
-+ if (!player.hasPermission("allow.special.wither")) {
-+ return false;
-+ }
-+
-+ net.minecraft.world.phys.HitResult rayTrace = getRayTrace(120, net.minecraft.world.level.ClipContext.Fluid.NONE);
-+ if (rayTrace == null) {
-+ return false;
-+ }
-+
-+ Vec3 loc;
-+ if (rayTrace.getType() == net.minecraft.world.phys.HitResult.Type.BLOCK) {
-+ BlockPos pos = ((net.minecraft.world.phys.BlockHitResult) rayTrace).getBlockPos();
-+ loc = new Vec3(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D);
-+ } else if (rayTrace.getType() == net.minecraft.world.phys.HitResult.Type.ENTITY) {
-+ Entity target = ((net.minecraft.world.phys.EntityHitResult) rayTrace).getEntity();
-+ loc = new Vec3(target.getX(), target.getY() + (target.getEyeHeight() / 2), target.getZ());
-+ } else {
-+ org.bukkit.block.Block block = player.getTargetBlock(null, 120);
-+ loc = new Vec3(block.getX() + 0.5D, block.getY() + 0.5D, block.getZ() + 0.5D);
-+ }
-+
-+ for (int head : heads) {
-+ shoot(head, loc.x(), loc.y(), loc.z(), rider);
-+ }
-+
-+ return true; // handled
-+ }
-+
-+ public void shoot(int head, double x, double y, double z, Player rider) {
-+ level().levelEvent(null, 1024, blockPosition(), 0);
-+ double headX = getHeadX(head);
-+ double headY = getHeadY(head);
-+ double headZ = getHeadZ(head);
-+ Vec3 vec3d = new Vec3(x - headX, y - headY, z - headZ);
-+ WitherSkull skull = new WitherSkull(level(), this, vec3d.normalize());
-+ skull.setPosRaw(headX, headY, headZ);
-+ level().addFreshEntity(skull);
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void registerGoals() {
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(0, new WitherBoss.WitherDoNothingGoal());
- this.goalSelector.addGoal(2, new RangedAttackGoal(this, 1.0D, 40, 20.0F));
- this.goalSelector.addGoal(5, new WaterAvoidingRandomFlyingGoal(this, 1.0D));
- this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F));
- this.goalSelector.addGoal(7, new RandomLookAroundGoal(this));
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[0]));
- this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, LivingEntity.class, 0, false, false, WitherBoss.LIVING_ENTITY_SELECTOR));
- }
-@@ -260,6 +375,15 @@ public class WitherBoss extends Monster implements RangedAttackMob {
-
- @Override
- protected void customServerAiStep(ServerLevel world) {
-+ // Purpur start - Ridables
-+ if (getRider() != null && this.isControllable()) {
-+ Vec3 mot = getDeltaMovement();
-+ setDeltaMovement(mot.x(), mot.y() + (getVerticalMot() > 0 ? 0.07D : 0.0D), mot.z());
-+ }
-+ if (shootCooldown > 0) {
-+ shootCooldown--;
-+ }
-+ // Purpur end - Ridables
- int i;
-
- if (this.getInvulnerableTicks() > 0) {
-@@ -578,11 +702,11 @@ public class WitherBoss extends Monster implements RangedAttackMob {
- }
-
- public int getAlternativeTarget(int headIndex) {
-- return (Integer) this.entityData.get((EntityDataAccessor) WitherBoss.DATA_TARGETS.get(headIndex));
-+ return getRider() != null && this.isControllable() ? 0 : this.entityData.get(WitherBoss.DATA_TARGETS.get(headIndex)); // Purpur - Ridables
- }
-
- public void setAlternativeTarget(int headIndex, int id) {
-- this.entityData.set((EntityDataAccessor) WitherBoss.DATA_TARGETS.get(headIndex), id);
-+ if (getRider() == null || !this.isControllable()) this.entityData.set(WitherBoss.DATA_TARGETS.get(headIndex), id); // Purpur - Ridables
- }
-
- public boolean isPowered() {
-diff --git a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java
-index 90b6ed81dcfd4021c7e9509da5e8725034fa07e5..22b003a23b519bedc50bbcad0706aa2d7d7f4b3a 100644
---- a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java
-@@ -74,12 +74,14 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo
-
- @Override
- protected void registerGoals() {
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(2, new RestrictSunGoal(this));
- this.goalSelector.addGoal(3, new FleeSunGoal(this, 1.0D));
- this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Wolf.class, 6.0F, 1.0D, 1.2D));
- this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0D));
- this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F));
- this.goalSelector.addGoal(6, new RandomLookAroundGoal(this));
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[0]));
- this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
- this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true));
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Blaze.java b/src/main/java/net/minecraft/world/entity/monster/Blaze.java
-index e33fa82ca1332b95bb067fd621212d3026eee1b7..968af87ceeb1862738c5270c2aad85c7974cfdb3 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Blaze.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Blaze.java
-@@ -33,6 +33,7 @@ public class Blaze extends Monster {
-
- public Blaze(EntityType extends Blaze> type, Level world) {
- super(type, world);
-+ this.moveControl = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.3F); // Purpur - Ridables
- this.setPathfindingMalus(PathType.WATER, -1.0F);
- this.setPathfindingMalus(PathType.LAVA, 8.0F);
- this.setPathfindingMalus(PathType.DANGER_FIRE, 0.0F);
-@@ -40,19 +41,55 @@ public class Blaze extends Monster {
- this.xpReward = 10;
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.blazeRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.blazeRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.blazeControllable;
-+ }
-+
-+ @Override
-+ public double getMaxY() {
-+ return level().purpurConfig.blazeMaxY;
-+ }
-+
-+ @Override
-+ public void travel(Vec3 vec3) {
-+ super.travel(vec3);
-+ if (getRider() != null && this.isControllable() && !onGround) {
-+ float speed = (float) getAttributeValue(Attributes.FLYING_SPEED);
-+ setSpeed(speed);
-+ Vec3 mot = getDeltaMovement();
-+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 1.0, speed));
-+ setDeltaMovement(mot.scale(0.9D));
-+ }
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void registerGoals() {
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(4, new Blaze.BlazeAttackGoal(this));
- this.goalSelector.addGoal(5, new MoveTowardsRestrictionGoal(this, 1.0));
- this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0, 0.0F));
- this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F));
- this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.targetSelector.addGoal(1, new HurtByTargetGoal(this).setAlertOthers());
- this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
- }
-
- public static AttributeSupplier.Builder createAttributes() {
-- return Monster.createMonsterAttributes().add(Attributes.ATTACK_DAMAGE, 6.0).add(Attributes.MOVEMENT_SPEED, 0.23F).add(Attributes.FOLLOW_RANGE, 48.0);
-+ return Monster.createMonsterAttributes().add(Attributes.ATTACK_DAMAGE, 6.0).add(Attributes.MOVEMENT_SPEED, 0.23F).add(Attributes.FOLLOW_RANGE, 48.0).add(Attributes.FLYING_SPEED, 0.6D); // Purpur - Ridables
- }
-
- @Override
-@@ -117,6 +154,13 @@ public class Blaze extends Monster {
-
- @Override
- protected void customServerAiStep(ServerLevel world) {
-+ // Purpur start - Ridables
-+ if (getRider() != null && this.isControllable()) {
-+ Vec3 mot = getDeltaMovement();
-+ setDeltaMovement(mot.x(), getVerticalMot() > 0 ? 0.07D : -0.07D, mot.z());
-+ return;
-+ }
-+ // Purpur end - Ridables
- this.nextHeightOffsetChangeTick--;
- if (this.nextHeightOffsetChangeTick <= 0) {
- this.nextHeightOffsetChangeTick = 100;
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Bogged.java b/src/main/java/net/minecraft/world/entity/monster/Bogged.java
-index 975477663b6d76a69c006a89e440e21471b39b89..05848ad509461f3fd40d820098cf7cbfe6866b0c 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Bogged.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Bogged.java
-@@ -44,6 +44,23 @@ public class Bogged extends AbstractSkeleton implements Shearable {
- super(type, world);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.boggedRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.boggedRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.boggedControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void defineSynchedData(SynchedEntityData.Builder builder) {
- super.defineSynchedData(builder);
-diff --git a/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java b/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java
-index 4e621a7f36b3d718695434a2a4e3060283667bb2..a21a18e249dbfa0f1d3d6995b6810961ed5e084b 100644
---- a/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java
-@@ -27,6 +27,23 @@ public class CaveSpider extends Spider {
- return Spider.createAttributes().add(Attributes.MAX_HEALTH, 12.0D);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.caveSpiderRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.caveSpiderRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.caveSpiderControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- public boolean doHurtTarget(ServerLevel world, Entity target) {
- if (super.doHurtTarget(world, target)) {
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Creeper.java b/src/main/java/net/minecraft/world/entity/monster/Creeper.java
-index 1906dfc22af208d6e801ad4a8f2f9e9702432691..ff7ee3a07f0c84aaa36be3a324274b4ccb04fb0b 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Creeper.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Creeper.java
-@@ -60,21 +60,98 @@ public class Creeper extends Monster {
- public int explosionRadius = 3;
- private int droppedSkulls;
- public Entity entityIgniter; // CraftBukkit
-+ // Purpur start - Ridables
-+ private int spacebarCharge = 0;
-+ private int prevSpacebarCharge = 0;
-+ private int powerToggleDelay = 0;
-+ // Purpur end - Ridables
-
- public Creeper(EntityType extends Creeper> type, Level world) {
- super(type, world);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.creeperRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.creeperRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.creeperControllable;
-+ }
-+
-+ @Override
-+ protected void customServerAiStep(ServerLevel world) {
-+ if (powerToggleDelay > 0) {
-+ powerToggleDelay--;
-+ }
-+ if (getRider() != null && this.isControllable()) {
-+ if (getRider().getForwardMot() != 0 || getRider().getStrafeMot() != 0) {
-+ spacebarCharge = 0;
-+ setIgnited(false);
-+ setSwellDir(-1);
-+ }
-+ if (spacebarCharge == prevSpacebarCharge) {
-+ spacebarCharge = 0;
-+ }
-+ prevSpacebarCharge = spacebarCharge;
-+ }
-+ super.customServerAiStep(world);
-+ }
-+
-+ @Override
-+ public void onMount(Player rider) {
-+ super.onMount(rider);
-+ setIgnited(false);
-+ setSwellDir(-1);
-+ }
-+
-+ @Override
-+ public boolean onSpacebar() {
-+ if (powerToggleDelay > 0) {
-+ return true; // just toggled power, do not jump or ignite
-+ }
-+ spacebarCharge++;
-+ if (spacebarCharge > maxSwell - 2) {
-+ spacebarCharge = 0;
-+ if (getRider() != null && getRider().getBukkitEntity().hasPermission("allow.powered.creeper")) {
-+ powerToggleDelay = 20;
-+ setPowered(!isPowered());
-+ setIgnited(false);
-+ setSwellDir(-1);
-+ return true;
-+ }
-+ }
-+ if (!isIgnited()) {
-+ if (getRider() != null && getRider().getForwardMot() == 0 && getRider().getStrafeMot() == 0 &&
-+ getRider().getBukkitEntity().hasPermission("allow.special.creeper")) {
-+ setIgnited(true);
-+ setSwellDir(1);
-+ return true;
-+ }
-+ }
-+ return getForwardMot() == 0 && getStrafeMot() == 0; // do not jump if standing still
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void registerGoals() {
- this.goalSelector.addGoal(1, new FloatGoal(this));
- this.goalSelector.addGoal(2, new SwellGoal(this));
-+ this.goalSelector.addGoal(3, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Ocelot.class, 6.0F, 1.0D, 1.2D));
- this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Cat.class, 6.0F, 1.0D, 1.2D));
- this.goalSelector.addGoal(4, new MeleeAttackGoal(this, 1.0D, false));
- this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.8D));
- this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F));
- this.goalSelector.addGoal(6, new RandomLookAroundGoal(this));
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, true));
- this.targetSelector.addGoal(2, new HurtByTargetGoal(this, new Class[0]));
- }
-@@ -324,6 +401,7 @@ public class Creeper extends Monster {
- com.destroystokyo.paper.event.entity.CreeperIgniteEvent event = new com.destroystokyo.paper.event.entity.CreeperIgniteEvent((org.bukkit.entity.Creeper) getBukkitEntity(), ignited);
- if (event.callEvent()) {
- this.entityData.set(Creeper.DATA_IS_IGNITED, event.isIgnited());
-+ if (!event.isIgnited()) setSwellDir(-1); // Purpur - Ridables
- }
- }
- // Paper end - CreeperIgniteEvent
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Drowned.java b/src/main/java/net/minecraft/world/entity/monster/Drowned.java
-index c6c86913c0a48501a9109a3838a3e56685d16d79..025133dff77df06e147defb70f920ee140b9881f 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Drowned.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Drowned.java
-@@ -75,6 +75,23 @@ public class Drowned extends Zombie implements RangedAttackMob {
- return Zombie.createAttributes().add(Attributes.STEP_HEIGHT, 1.0);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.drownedRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.drownedRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.drownedControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void addBehaviourGoals() {
- this.goalSelector.addGoal(1, new Drowned.DrownedGoToWaterGoal(this, 1.0));
-@@ -398,7 +415,7 @@ public class Drowned extends Zombie implements RangedAttackMob {
- }
- }
-
-- static class DrownedMoveControl extends MoveControl {
-+ static class DrownedMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables
- private final Drowned drowned;
-
- public DrownedMoveControl(Drowned drowned) {
-@@ -407,7 +424,7 @@ public class Drowned extends Zombie implements RangedAttackMob {
- }
-
- @Override
-- public void tick() {
-+ public void vanillaTick() { // Purpur - Ridables
- LivingEntity livingEntity = this.drowned.getTarget();
- if (this.drowned.wantsToSwim() && this.drowned.isInWater()) {
- if (livingEntity != null && livingEntity.getY() > this.drowned.getY() || this.drowned.searchingForLand) {
-@@ -427,7 +444,7 @@ public class Drowned extends Zombie implements RangedAttackMob {
- float h = (float)(Mth.atan2(f, d) * 180.0F / (float)Math.PI) - 90.0F;
- this.drowned.setYRot(this.rotlerp(this.drowned.getYRot(), h, 90.0F));
- this.drowned.yBodyRot = this.drowned.getYRot();
-- float i = (float)(this.speedModifier * this.drowned.getAttributeValue(Attributes.MOVEMENT_SPEED));
-+ float i = (float)(this.getSpeedModifier() * this.drowned.getAttributeValue(Attributes.MOVEMENT_SPEED)); // Purpur - Ridables
- float j = Mth.lerp(0.125F, this.drowned.getSpeed(), i);
- this.drowned.setSpeed(j);
- this.drowned.setDeltaMovement(this.drowned.getDeltaMovement().add((double)j * d * 0.005, (double)j * e * 0.1, (double)j * f * 0.005));
-@@ -436,7 +453,7 @@ public class Drowned extends Zombie implements RangedAttackMob {
- this.drowned.setDeltaMovement(this.drowned.getDeltaMovement().add(0.0, -0.008, 0.0));
- }
-
-- super.tick();
-+ super.vanillaTick(); // Purpur - Ridables
- }
- }
- }
-diff --git a/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java b/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java
-index 378694a38115c012978e1fea59d049d1ebd04110..bff963a1a0d89b57a686ed06aa630e789a602d82 100644
---- a/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java
-@@ -33,6 +33,18 @@ public class ElderGuardian extends Guardian {
-
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.elderGuardianRidable;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.elderGuardianControllable;
-+ }
-+ // Purpur end - Ridables
-+
- public static AttributeSupplier.Builder createAttributes() {
- return Guardian.createAttributes().add(Attributes.MOVEMENT_SPEED, 0.30000001192092896D).add(Attributes.ATTACK_DAMAGE, 8.0D).add(Attributes.MAX_HEALTH, 80.0D);
- }
-diff --git a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java
-index 2a394381a4ad46359359ba402b65c62b331480b4..f3013664d21f70c2650306f2406e93b21de8f9e0 100644
---- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java
-@@ -94,9 +94,27 @@ public class EnderMan extends Monster implements NeutralMob {
- this.setPathfindingMalus(PathType.WATER, -1.0F);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.endermanRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.endermanRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.endermanControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void registerGoals() {
- this.goalSelector.addGoal(0, new FloatGoal(this));
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(1, new EnderMan.EndermanFreezeWhenLookedAt(this));
- this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.0D, false));
- this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D, 0.0F));
-@@ -104,6 +122,7 @@ public class EnderMan extends Monster implements NeutralMob {
- this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
- this.goalSelector.addGoal(10, new EnderMan.EndermanLeaveBlockGoal(this));
- this.goalSelector.addGoal(11, new EnderMan.EndermanTakeBlockGoal(this));
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.targetSelector.addGoal(1, new EnderMan.EndermanLookForPlayerGoal(this, this::isAngryAt));
- this.targetSelector.addGoal(2, new HurtByTargetGoal(this, new Class[0]));
- this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Endermite.class, true, false));
-@@ -267,7 +286,7 @@ public class EnderMan extends Monster implements NeutralMob {
-
- @Override
- protected void customServerAiStep(ServerLevel world) {
-- if (world.isDay() && this.tickCount >= this.targetChangeTime + 600) {
-+ if ((getRider() == null || !this.isControllable()) && world.isDay() && this.tickCount >= this.targetChangeTime + 600) { // Purpur - no random teleporting
- float f = this.getLightLevelDependentMagicValue();
-
- if (f > 0.5F && world.canSeeSky(this.blockPosition()) && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && this.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.RUNAWAY)) { // Paper - EndermanEscapeEvent
-@@ -382,6 +401,7 @@ public class EnderMan extends Monster implements NeutralMob {
- public boolean hurtServer(ServerLevel world, DamageSource source, float amount) {
- if (this.isInvulnerableTo(world, source)) {
- return false;
-+ } else if (getRider() != null && this.isControllable()) { return super.hurtServer(world, source, amount); // Purpur - no teleporting on damage
- } else {
- boolean flag = source.getDirectEntity() instanceof ThrownPotion;
- boolean flag1;
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Endermite.java b/src/main/java/net/minecraft/world/entity/monster/Endermite.java
-index 534e98dd7291e09dee1d0f77cbf221b15708590f..ac9116735d8ee4713fab8d90ef12e781b77b26c8 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Endermite.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Endermite.java
-@@ -38,14 +38,33 @@ public class Endermite extends Monster {
- this.xpReward = 3;
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.endermiteRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.endermiteRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.endermiteControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void registerGoals() {
- this.goalSelector.addGoal(1, new FloatGoal(this));
-+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(1, new ClimbOnTopOfPowderSnowGoal(this, this.level()));
- this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.0D, false));
- this.goalSelector.addGoal(3, new WaterAvoidingRandomStrollGoal(this, 1.0D));
- this.goalSelector.addGoal(7, new LookAtPlayerGoal(this, Player.class, 8.0F));
- this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[0])).setAlertOthers());
- this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
- }
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Evoker.java b/src/main/java/net/minecraft/world/entity/monster/Evoker.java
-index 14f23c9a248760a57b3d6fe4f2824a4a456a6d37..69a37f88c7f05084007cc2f895c31650da6566f8 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Evoker.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Evoker.java
-@@ -53,10 +53,28 @@ public class Evoker extends SpellcasterIllager {
- this.xpReward = 10;
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.evokerRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.evokerRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.evokerControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void registerGoals() {
- super.registerGoals();
- this.goalSelector.addGoal(0, new FloatGoal(this));
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(1, new Evoker.EvokerCastingSpellGoal());
- this.goalSelector.addGoal(2, new AvoidEntityGoal<>(this, Player.class, 8.0F, 0.6D, 1.0D));
- this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Creaking.class, 8.0F, 0.6D, 1.0D));
-@@ -66,6 +84,7 @@ public class Evoker extends SpellcasterIllager {
- this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6D));
- this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 3.0F, 1.0F));
- this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F));
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers());
- this.targetSelector.addGoal(2, (new NearestAttackableTargetGoal<>(this, Player.class, true)).setUnseenMemoryTicks(300));
- this.targetSelector.addGoal(3, (new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)).setUnseenMemoryTicks(300));
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Ghast.java b/src/main/java/net/minecraft/world/entity/monster/Ghast.java
-index a8c8c03e972aa6352843cf4c3e4aebfb8f493125..8cb7bdd96f9780ee5e2fa0876bc80d092e06b5f5 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Ghast.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Ghast.java
-@@ -44,11 +44,47 @@ public class Ghast extends FlyingMob implements Enemy {
- this.moveControl = new Ghast.GhastMoveControl(this);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.ghastRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.ghastRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.ghastControllable;
-+ }
-+
-+ @Override
-+ public double getMaxY() {
-+ return level().purpurConfig.ghastMaxY;
-+ }
-+
-+ @Override
-+ public void travel(Vec3 vec3) {
-+ super.travel(vec3);
-+ if (getRider() != null && this.isControllable() && !onGround) {
-+ float speed = (float) getAttributeValue(Attributes.FLYING_SPEED);
-+ setSpeed(speed);
-+ Vec3 mot = getDeltaMovement();
-+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 1.0, speed));
-+ setDeltaMovement(mot.scale(0.9D));
-+ }
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void registerGoals() {
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(5, new Ghast.RandomFloatAroundGoal(this));
- this.goalSelector.addGoal(7, new Ghast.GhastLookGoal(this));
- this.goalSelector.addGoal(7, new Ghast.GhastShootFireballGoal(this));
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (entityliving, worldserver) -> {
- return Math.abs(entityliving.getY() - this.getY()) <= 4.0D;
- }));
-@@ -103,7 +139,7 @@ public class Ghast extends FlyingMob implements Enemy {
- }
-
- public static AttributeSupplier.Builder createAttributes() {
-- return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.FOLLOW_RANGE, 100.0D);
-+ return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.FOLLOW_RANGE, 100.0D).add(Attributes.FLYING_SPEED, 0.6D); // Purpur - Ridables
- }
-
- @Override
-@@ -155,7 +191,7 @@ public class Ghast extends FlyingMob implements Enemy {
-
- }
-
-- private static class GhastMoveControl extends MoveControl {
-+ private static class GhastMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur - Ridables
-
- private final Ghast ghast;
- private int floatDuration;
-@@ -166,7 +202,7 @@ public class Ghast extends FlyingMob implements Enemy {
- }
-
- @Override
-- public void tick() {
-+ public void vanillaTick() { // Purpur - Ridables
- if (this.operation == MoveControl.Operation.MOVE_TO) {
- if (this.floatDuration-- <= 0) {
- this.floatDuration += this.ghast.getRandom().nextInt(5) + 2;
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Giant.java b/src/main/java/net/minecraft/world/entity/monster/Giant.java
-index 118521ae54254b0a73bb7cba7b2871c9c26f89fc..64e4f78fd626c59e2957140af22993aa8e5c4f15 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Giant.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Giant.java
-@@ -12,6 +12,29 @@ public class Giant extends Monster {
- super(type, world);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.giantRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.giantRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.giantControllable;
-+ }
-+
-+ @Override
-+ protected void registerGoals() {
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this));
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this));
-+ }
-+ // Purpur end - Ridables
-+
- public static AttributeSupplier.Builder createAttributes() {
- return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 100.0).add(Attributes.MOVEMENT_SPEED, 0.5).add(Attributes.ATTACK_DAMAGE, 50.0);
- }
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Guardian.java b/src/main/java/net/minecraft/world/entity/monster/Guardian.java
-index 951f46684623582980901c1ebc1870aa5bcf25a1..bde5d88dcdb4f2305e2786842551c36da5ed9778 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Guardian.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Guardian.java
-@@ -67,15 +67,36 @@ public class Guardian extends Monster {
- this.xpReward = 10;
- this.setPathfindingMalus(PathType.WATER, 0.0F);
- this.moveControl = new Guardian.GuardianMoveControl(this);
-+ // Purpur start - Ridables
-+ this.lookControl = new org.purpurmc.purpur.controller.LookControllerWASD(this) {
-+ @Override
-+ public void setYawPitch(float yaw, float pitch) {
-+ super.setYawPitch(yaw, pitch * 0.35F);
-+ }
-+ };
-+ // Purpur end - Ridables
- this.clientSideTailAnimation = this.random.nextFloat();
- this.clientSideTailAnimationO = this.clientSideTailAnimation;
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.guardianRidable;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.guardianControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void registerGoals() {
- MoveTowardsRestrictionGoal pathfindergoalmovetowardsrestriction = new MoveTowardsRestrictionGoal(this, 1.0D);
-
- this.randomStrollGoal = new RandomStrollGoal(this, 1.0D, 80);
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(4, this.guardianAttackGoal = new Guardian.GuardianAttackGoal(this)); // CraftBukkit - assign field
- this.goalSelector.addGoal(5, pathfindergoalmovetowardsrestriction);
- this.goalSelector.addGoal(7, this.randomStrollGoal);
-@@ -84,6 +105,7 @@ public class Guardian extends Monster {
- this.goalSelector.addGoal(9, new RandomLookAroundGoal(this));
- this.randomStrollGoal.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.LOOK));
- pathfindergoalmovetowardsrestriction.setFlags(EnumSet.of(Goal.Flag.MOVE, Goal.Flag.LOOK));
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, LivingEntity.class, 10, true, false, new Guardian.GuardianAttackSelector(this)));
- }
-
-@@ -330,7 +352,7 @@ public class Guardian extends Monster {
- @Override
- public void travel(Vec3 movementInput) {
- if (this.isControlledByLocalInstance() && this.isInWater()) {
-- this.moveRelative(0.1F, movementInput);
-+ this.moveRelative(getRider() != null && this.isControllable() ? getSpeed() : 0.1F, movementInput); // Purpur - Ridables
- this.move(MoverType.SELF, this.getDeltaMovement());
- this.setDeltaMovement(this.getDeltaMovement().scale(0.9D));
- if (!this.isMoving() && this.getTarget() == null) {
-@@ -342,7 +364,7 @@ public class Guardian extends Monster {
-
- }
-
-- private static class GuardianMoveControl extends MoveControl {
-+ private static class GuardianMoveControl extends org.purpurmc.purpur.controller.WaterMoveControllerWASD { // Purpur - Ridables
-
- private final Guardian guardian;
-
-@@ -351,8 +373,17 @@ public class Guardian extends Monster {
- this.guardian = guardian;
- }
-
-+ // Purpur start - Ridables
- @Override
-- public void tick() {
-+ public void purpurTick(Player rider) {
-+ super.purpurTick(rider);
-+ guardian.setDeltaMovement(guardian.getDeltaMovement().add(0.0D, 0.005D, 0.0D));
-+ guardian.setMoving(guardian.getForwardMot() > 0.0F); // control tail speed
-+ }
-+ // Purpur end - Ridables
-+
-+ @Override
-+ public void vanillaTick() { // Purpur - Ridables
- if (this.operation == MoveControl.Operation.MOVE_TO && !this.guardian.getNavigation().isDone()) {
- Vec3 vec3d = new Vec3(this.wantedX - this.guardian.getX(), this.wantedY - this.guardian.getY(), this.wantedZ - this.guardian.getZ());
- double d0 = vec3d.length();
-@@ -363,7 +394,7 @@ public class Guardian extends Monster {
-
- this.guardian.setYRot(this.rotlerp(this.guardian.getYRot(), f, 90.0F));
- this.guardian.yBodyRot = this.guardian.getYRot();
-- float f1 = (float) (this.speedModifier * this.guardian.getAttributeValue(Attributes.MOVEMENT_SPEED));
-+ float f1 = (float) (this.getSpeedModifier() * this.guardian.getAttributeValue(Attributes.MOVEMENT_SPEED)); // Purpur - Ridables
- float f2 = Mth.lerp(0.125F, this.guardian.getSpeed(), f1);
-
- this.guardian.setSpeed(f2);
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Husk.java b/src/main/java/net/minecraft/world/entity/monster/Husk.java
-index 184fa759db065fb345f3623752229430816d8ad3..f396652fdc30ec5638b30a8b4189f05fb0eee12d 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Husk.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Husk.java
-@@ -23,6 +23,23 @@ public class Husk extends Zombie {
- super(type, world);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.huskRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.huskRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.huskControllable;
-+ }
-+ // Purpur end - Ridables
-+
- public static boolean checkHuskSpawnRules(EntityType type, ServerLevelAccessor world, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random) {
- return checkMonsterSpawnRules(type, world, spawnReason, pos, random) && (EntitySpawnReason.isSpawner(spawnReason) || world.canSeeSky(pos));
- }
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Illusioner.java b/src/main/java/net/minecraft/world/entity/monster/Illusioner.java
-index db3aac9ba711dcd18ffc35c4a745ecaec89d0166..8ff616fe05071bd2a197a465c7e17f35a3e72d44 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Illusioner.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Illusioner.java
-@@ -59,10 +59,28 @@ public class Illusioner extends SpellcasterIllager implements RangedAttackMob {
-
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.illusionerRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.illusionerRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.illusionerControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void registerGoals() {
- super.registerGoals();
- this.goalSelector.addGoal(0, new FloatGoal(this));
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(1, new SpellcasterIllager.SpellcasterCastingSpellGoal());
- this.goalSelector.addGoal(3, new AvoidEntityGoal<>(this, Creaking.class, 8.0F, 1.0D, 1.2D));
- this.goalSelector.addGoal(4, new Illusioner.IllusionerMirrorSpellGoal());
-@@ -71,6 +89,7 @@ public class Illusioner extends SpellcasterIllager implements RangedAttackMob {
- this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6D));
- this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 3.0F, 1.0F));
- this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F));
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers());
- this.targetSelector.addGoal(2, (new NearestAttackableTargetGoal<>(this, Player.class, true)).setUnseenMemoryTicks(300));
- this.targetSelector.addGoal(3, (new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)).setUnseenMemoryTicks(300));
-diff --git a/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java b/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java
-index ae710c3fffc7840a9ff2cbc5cdacef8a2e248253..4f7d99fadfa1ba31439ec02bfb107288a722e828 100644
---- a/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java
-@@ -24,6 +24,28 @@ public class MagmaCube extends Slime {
- super(type, world);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.magmaCubeRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.magmaCubeRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.magmaCubeControllable;
-+ }
-+
-+ @Override
-+ public float getJumpPower() {
-+ return 0.42F * this.getBlockJumpFactor(); // from EntityLiving
-+ }
-+ // Purpur end - Ridables
-+
- public static AttributeSupplier.Builder createAttributes() {
- return Monster.createMonsterAttributes().add(Attributes.MOVEMENT_SPEED, 0.2F);
- }
-@@ -71,6 +93,7 @@ public class MagmaCube extends Slime {
- float f = (float)this.getSize() * 0.1F;
- this.setDeltaMovement(vec3.x, (double)(this.getJumpPower() + f), vec3.z);
- this.hasImpulse = true;
-+ this.actualJump = false; // Purpur - Ridables
- }
-
- @Override
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Phantom.java b/src/main/java/net/minecraft/world/entity/monster/Phantom.java
-index 4ff75412452649ebf106ef591cb97dc7ac8175e7..afd00fd83e6c246afecf7042435ae119057b9e93 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Phantom.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Phantom.java
-@@ -60,6 +60,64 @@ public class Phantom extends FlyingMob implements Enemy {
- this.lookControl = new Phantom.PhantomLookControl(this);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.phantomRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.phantomRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.phantomControllable;
-+ }
-+
-+ @Override
-+ public double getMaxY() {
-+ return level().purpurConfig.phantomMaxY;
-+ }
-+
-+ @Override
-+ public void travel(Vec3 vec3) {
-+ super.travel(vec3);
-+ if (getRider() != null && this.isControllable() && !onGround) {
-+ float speed = (float) getAttributeValue(Attributes.FLYING_SPEED);
-+ setSpeed(speed);
-+ Vec3 mot = getDeltaMovement();
-+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, speed, speed));
-+ setDeltaMovement(mot.scale(0.9D));
-+ }
-+ }
-+
-+ public static net.minecraft.world.entity.ai.attributes.AttributeSupplier.Builder createAttributes() {
-+ return Monster.createMonsterAttributes().add(Attributes.FLYING_SPEED, 3.0D);
-+ }
-+
-+ @Override
-+ public boolean onSpacebar() {
-+ if (getRider() != null && getRider().getBukkitEntity().hasPermission("allow.special.phantom")) {
-+ shoot();
-+ }
-+ return false;
-+ }
-+
-+ public boolean shoot() {
-+ org.bukkit.Location loc = ((org.bukkit.entity.LivingEntity) getBukkitEntity()).getEyeLocation();
-+ loc.setPitch(-loc.getPitch());
-+ org.bukkit.util.Vector target = loc.getDirection().normalize().multiply(100).add(loc.toVector());
-+
-+ org.purpurmc.purpur.entity.PhantomFlames flames = new org.purpurmc.purpur.entity.PhantomFlames(level(), this);
-+ flames.canGrief = level().purpurConfig.phantomAllowGriefing;
-+ flames.shoot(target.getX() - getX(), target.getY() - getY(), target.getZ() - getZ(), 1.0F, 5.0F);
-+ level().addFreshEntity(flames);
-+ return true;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- public boolean isFlapping() {
- return (this.getUniqueFlapTickOffset() + this.tickCount) % Phantom.TICKS_PER_FLAP == 0;
-@@ -72,9 +130,11 @@ public class Phantom extends FlyingMob implements Enemy {
-
- @Override
- protected void registerGoals() {
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(1, new Phantom.PhantomAttackStrategyGoal());
- this.goalSelector.addGoal(2, new Phantom.PhantomSweepAttackGoal());
- this.goalSelector.addGoal(3, new Phantom.PhantomCircleAroundAnchorGoal());
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.targetSelector.addGoal(1, new Phantom.PhantomAttackPlayerTargetGoal());
- }
-
-@@ -140,6 +200,7 @@ public class Phantom extends FlyingMob implements Enemy {
- @Override
- public void aiStep() {
- if (this.isAlive() && this.shouldBurnInDay && this.isSunBurnTick()) { // Paper - shouldBurnInDay API
-+ if (getRider() == null || !this.isControllable()) // Purpur - Ridables
- this.igniteForSeconds(8.0F);
- }
-
-@@ -254,7 +315,7 @@ public class Phantom extends FlyingMob implements Enemy {
- private AttackPhase() {}
- }
-
-- private class PhantomMoveControl extends MoveControl {
-+ private class PhantomMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur - Ridables
-
- private float speed = 0.1F;
-
-@@ -262,8 +323,19 @@ public class Phantom extends FlyingMob implements Enemy {
- super(entity);
- }
-
-+ // Purpur start - Ridables
-+ public void purpurTick(Player rider) {
-+ if (!Phantom.this.onGround) {
-+ // phantom is always in motion when flying
-+ // TODO - FIX THIS
-+ // rider.setForward(1.0F);
-+ }
-+ super.purpurTick(rider);
-+ }
-+ // Purpur end - Ridables
-+
- @Override
-- public void tick() {
-+ public void vanillaTick() { // Purpur - Ridables
- if (Phantom.this.horizontalCollision) {
- Phantom.this.setYRot(Phantom.this.getYRot() + 180.0F);
- this.speed = 0.1F;
-@@ -309,14 +381,20 @@ public class Phantom extends FlyingMob implements Enemy {
- }
- }
-
-- private static class PhantomLookControl extends LookControl {
-+ private static class PhantomLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - Ridables
-
- public PhantomLookControl(Mob entity) {
- super(entity);
- }
-
-+ // Purpur start - Ridables
-+ public void purpurTick(Player rider) {
-+ setYawPitch(rider.getYRot(), -rider.xRotO * 0.75F);
-+ }
-+ // Purpur end - Ridables
-+
- @Override
-- public void tick() {}
-+ public void vanillaTick() {} // Purpur - Ridables
- }
-
- private class PhantomBodyRotationControl extends BodyRotationControl {
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Pillager.java b/src/main/java/net/minecraft/world/entity/monster/Pillager.java
-index 91edf8767541982b8cd1be83c33a7b287ffb62fe..a629e31e6ea0f49f88746856382fcec96918c490 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Pillager.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Pillager.java
-@@ -67,16 +67,35 @@ public class Pillager extends AbstractIllager implements CrossbowAttackMob, Inve
- super(type, world);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.pillagerRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.pillagerRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.pillagerControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void registerGoals() {
- super.registerGoals();
- this.goalSelector.addGoal(0, new FloatGoal(this));
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Creaking.class, 8.0F, 1.0D, 1.2D));
- this.goalSelector.addGoal(2, new Raider.HoldGroundAttackGoal(this, 10.0F));
- this.goalSelector.addGoal(3, new RangedCrossbowAttackGoal<>(this, 1.0D, 8.0F));
- this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6D));
- this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 15.0F, 1.0F));
- this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 15.0F));
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers());
- this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
- this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false));
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Ravager.java b/src/main/java/net/minecraft/world/entity/monster/Ravager.java
-index cfc28828a5b81563a826ae6045553e7350f67986..6222ed8408096e0bb8e9572c07c0db6971fc8308 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Ravager.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Ravager.java
-@@ -75,14 +75,39 @@ public class Ravager extends Raider {
- this.setPathfindingMalus(PathType.LEAVES, 0.0F);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.ravagerRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.ravagerRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.ravagerControllable;
-+ }
-+
-+ @Override
-+ public void onMount(Player rider) {
-+ super.onMount(rider);
-+ getNavigation().stop();
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void registerGoals() {
- super.registerGoals();
- this.goalSelector.addGoal(0, new FloatGoal(this));
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(4, new MeleeAttackGoal(this, 1.0D, true));
- this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.4D));
- this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F));
- this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F));
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.targetSelector.addGoal(2, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers());
- this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, true));
- this.targetSelector.addGoal(4, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, true, (entityliving, worldserver) -> {
-@@ -135,7 +160,7 @@ public class Ravager extends Raider {
- @Override
- public void aiStep() {
- super.aiStep();
-- if (this.isAlive()) {
-+ if (this.isAlive() && (getRider() == null || !this.isControllable())) { // Purpur - Ridables
- if (this.isImmobile()) {
- this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(0.0D);
- } else {
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Shulker.java b/src/main/java/net/minecraft/world/entity/monster/Shulker.java
-index 64d99b8b576212f754bd316343562b1ba7f604fa..6541e1059ca16cfd01bf01aae2c56400cbe78132 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Shulker.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Shulker.java
-@@ -98,12 +98,31 @@ public class Shulker extends AbstractGolem implements VariantHolder(this, Player.class, true));
- }
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java
-index 3972e2ed0554e2550519e994888e068df0a151e5..78c8483da3c0ac7f93e236dd723fc6051427a50e 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java
-@@ -29,6 +29,23 @@ public class Skeleton extends AbstractSkeleton {
- super(type, world);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.skeletonRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.skeletonRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.skeletonControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void defineSynchedData(SynchedEntityData.Builder builder) {
- super.defineSynchedData(builder);
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Slime.java b/src/main/java/net/minecraft/world/entity/monster/Slime.java
-index 72346a7e5269c91e3143933ac37e65ad9639b791..b7941230b082d4de9ab77c981bd396fa1184d78e 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Slime.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java
-@@ -65,6 +65,7 @@ public class Slime extends Mob implements Enemy {
- public float squish;
- public float oSquish;
- private boolean wasOnGround;
-+ protected boolean actualJump; // Purpur - Ridables
-
- public Slime(EntityType extends Slime> type, Level world) {
- super(type, world);
-@@ -72,12 +73,48 @@ public class Slime extends Mob implements Enemy {
- this.moveControl = new Slime.SlimeMoveControl(this);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.slimeRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.slimeRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.slimeControllable;
-+ }
-+
-+ @Override
-+ public float getJumpPower() {
-+ float height = super.getJumpPower();
-+ return getRider() != null && this.isControllable() && actualJump ? height * 1.5F : height;
-+ }
-+
-+ @Override
-+ public boolean onSpacebar() {
-+ if (onGround && getRider() != null && this.isControllable()) {
-+ actualJump = true;
-+ if (getRider().getForwardMot() == 0 || getRider().getStrafeMot() == 0) {
-+ jumpFromGround(); // jump() here if not moving
-+ }
-+ }
-+ return true; // do not jump() in wasd controller, let vanilla controller handle
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void registerGoals() {
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(1, new Slime.SlimeFloatGoal(this));
- this.goalSelector.addGoal(2, new Slime.SlimeAttackGoal(this));
- this.goalSelector.addGoal(3, new Slime.SlimeRandomDirectionGoal(this));
- this.goalSelector.addGoal(5, new Slime.SlimeKeepOnJumpingGoal(this));
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (entityliving, worldserver) -> {
- return Math.abs(entityliving.getY() - this.getY()) <= 4.0D;
- }));
-@@ -386,6 +423,7 @@ public class Slime extends Mob implements Enemy {
-
- this.setDeltaMovement(vec3d.x, (double) this.getJumpPower(), vec3d.z);
- this.hasImpulse = true;
-+ this.actualJump = false; // Purpur - Ridables
- }
-
- @Nullable
-@@ -419,7 +457,7 @@ public class Slime extends Mob implements Enemy {
- return super.getDefaultDimensions(pose).scale((float) this.getSize());
- }
-
-- private static class SlimeMoveControl extends MoveControl {
-+ private static class SlimeMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables
-
- private float yRot;
- private int jumpDelay;
-@@ -438,21 +476,33 @@ public class Slime extends Mob implements Enemy {
- }
-
- public void setWantedMovement(double speed) {
-- this.speedModifier = speed;
-+ this.setSpeedModifier(speed); // Purpur - Ridables
- this.operation = MoveControl.Operation.MOVE_TO;
- }
-
- @Override
- public void tick() {
-+ // Purpur start - Ridables
-+ if (slime.getRider() != null && slime.isControllable()) {
-+ purpurTick(slime.getRider());
-+ if (slime.getForwardMot() != 0 || slime.getStrafeMot() != 0) {
-+ if (jumpDelay > 10) {
-+ jumpDelay = 6;
-+ }
-+ } else {
-+ jumpDelay = 20;
-+ }
-+ } else {
-+ // Purpur end - Ridables
- this.mob.setYRot(this.rotlerp(this.mob.getYRot(), this.yRot, 90.0F));
- this.mob.yHeadRot = this.mob.getYRot();
- this.mob.yBodyRot = this.mob.getYRot();
-- if (this.operation != MoveControl.Operation.MOVE_TO) {
-+ } if ((slime.getRider() == null || !slime.isControllable()) && this.operation != MoveControl.Operation.MOVE_TO) { // Purpur - Ridables
- this.mob.setZza(0.0F);
- } else {
- this.operation = MoveControl.Operation.WAIT;
- if (this.mob.onGround()) {
-- this.mob.setSpeed((float) (this.speedModifier * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED)));
-+ this.mob.setSpeed((float) (this.getSpeedModifier() * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED) * (slime.getRider() != null && slime.isControllable() && (slime.getRider().getForwardMot() != 0 || slime.getRider().getStrafeMot() != 0) ? 2.0D : 1.0D))); // Purpur - Ridables
- if (this.jumpDelay-- <= 0) {
- this.jumpDelay = this.slime.getJumpDelay();
- if (this.isAggressive) {
-@@ -469,7 +519,7 @@ public class Slime extends Mob implements Enemy {
- this.mob.setSpeed(0.0F);
- }
- } else {
-- this.mob.setSpeed((float) (this.speedModifier * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED)));
-+ this.mob.setSpeed((float) (this.getSpeedModifier() * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED) * (slime.getRider() != null && slime.isControllable() && (slime.getRider().getForwardMot() != 0 || slime.getRider().getStrafeMot() != 0) ? 2.0D : 1.0D))); // Purpur - Ridables
- }
-
- }
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Spider.java b/src/main/java/net/minecraft/world/entity/monster/Spider.java
-index 91e521414c3ea5722aac7506b7589fbb399e9636..d3eabdde9e9bf010cae7fc81165f0123adfcf958 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Spider.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Spider.java
-@@ -51,9 +51,27 @@ public class Spider extends Monster {
- super(type, world);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.spiderRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.spiderRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.spiderControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void registerGoals() {
- this.goalSelector.addGoal(1, new FloatGoal(this));
-+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(2, new AvoidEntityGoal<>(this, Armadillo.class, 6.0F, 1.0D, 1.2D, (entityliving) -> {
- return !((Armadillo) entityliving).isScared();
- }));
-@@ -62,6 +80,7 @@ public class Spider extends Monster {
- this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 0.8D));
- this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F));
- this.goalSelector.addGoal(6, new RandomLookAroundGoal(this));
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[0]));
- this.targetSelector.addGoal(2, new Spider.SpiderTargetGoal<>(this, Player.class));
- this.targetSelector.addGoal(3, new Spider.SpiderTargetGoal<>(this, IronGolem.class));
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Stray.java b/src/main/java/net/minecraft/world/entity/monster/Stray.java
-index baaf17107584b253d7e268749849bf5b0d0c88ab..879748708e3fc8c0a3f126d265e99a7c054d2a10 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Stray.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Stray.java
-@@ -22,6 +22,23 @@ public class Stray extends AbstractSkeleton {
- super(type, world);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.strayRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.strayRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.strayControllable;
-+ }
-+ // Purpur end - Ridables
-+
- public static boolean checkStraySpawnRules(
- EntityType type, ServerLevelAccessor world, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random
- ) {
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Strider.java b/src/main/java/net/minecraft/world/entity/monster/Strider.java
-index 0a9246241985d2d97beb865b7163f1d2198f03b8..7b1525c6bc46d65660588d90c3121ad3d12cf077 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Strider.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Strider.java
-@@ -97,6 +97,23 @@ public class Strider extends Animal implements ItemSteerable, Saddleable {
- this.setPathfindingMalus(PathType.DAMAGE_FIRE, 0.0F);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.striderRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.striderRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.striderControllable;
-+ }
-+ // Purpur end - Ridables
-+
- public static boolean checkStriderSpawnRules(EntityType type, LevelAccessor world, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random) {
- BlockPos.MutableBlockPos blockposition_mutableblockposition = pos.mutable();
-
-@@ -158,6 +175,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable {
- @Override
- protected void registerGoals() {
- this.goalSelector.addGoal(1, new PanicGoal(this, 1.65D));
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D));
- this.temptGoal = new TemptGoal(this, 1.4D, (itemstack) -> {
- return itemstack.is(ItemTags.STRIDER_TEMPT_ITEMS);
-@@ -466,7 +484,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable {
- if (!enuminteractionresult.consumesAction()) {
- ItemStack itemstack = player.getItemInHand(hand);
-
-- return (InteractionResult) (itemstack.is(Items.SADDLE) ? itemstack.interactLivingEntity(player, this, hand) : InteractionResult.PASS);
-+ return (InteractionResult) (itemstack.is(Items.SADDLE) ? itemstack.interactLivingEntity(player, this, hand) : tryRide(player, hand)); // Purpur - Ridables
- } else {
- if (flag && !this.isSilent()) {
- this.level().playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.STRIDER_EAT, this.getSoundSource(), 1.0F, 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F);
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Vex.java b/src/main/java/net/minecraft/world/entity/monster/Vex.java
-index 183a33b7d666d652b455baa7e8339e9c4a870a58..fba52457f85573f5918aeeb5f3b69b3f113cc9d5 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Vex.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Vex.java
-@@ -59,6 +59,50 @@ public class Vex extends Monster implements TraceableEntity {
- this.xpReward = 3;
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.vexRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.vexRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.vexControllable;
-+ }
-+
-+ @Override
-+ public double getMaxY() {
-+ return level().purpurConfig.vexMaxY;
-+ }
-+
-+ @Override
-+ public void travel(Vec3 vec3) {
-+ super.travel(vec3);
-+ if (getRider() != null && this.isControllable()) {
-+ float speed;
-+ if (onGround) {
-+ speed = (float) getAttributeValue(Attributes.MOVEMENT_SPEED) * 0.1F;
-+ } else {
-+ speed = (float) getAttributeValue(Attributes.FLYING_SPEED);
-+ }
-+ setSpeed(speed);
-+ Vec3 mot = getDeltaMovement();
-+ move(net.minecraft.world.entity.MoverType.SELF, mot.multiply(speed, 1.0, speed));
-+ setDeltaMovement(mot.scale(0.9D));
-+ }
-+ }
-+
-+ @Override
-+ public boolean causeFallDamage(float fallDistance, float damageMultiplier, DamageSource damageSource) {
-+ return false; // no fall damage please
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- public boolean isFlapping() {
- return this.tickCount % Vex.TICKS_PER_FLAP == 0;
-@@ -71,7 +115,7 @@ public class Vex extends Monster implements TraceableEntity {
-
- @Override
- public void tick() {
-- this.noPhysics = true;
-+ this.noPhysics = getRider() == null || !this.isControllable(); // Purpur - Ridables
- super.tick();
- this.noPhysics = false;
- this.setNoGravity(true);
-@@ -86,17 +130,19 @@ public class Vex extends Monster implements TraceableEntity {
- protected void registerGoals() {
- super.registerGoals();
- this.goalSelector.addGoal(0, new FloatGoal(this));
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(4, new Vex.VexChargeAttackGoal());
- this.goalSelector.addGoal(8, new Vex.VexRandomMoveGoal());
- this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 3.0F, 1.0F));
- this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F));
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers());
- this.targetSelector.addGoal(2, new Vex.VexCopyOwnerTargetGoal(this));
- this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, true));
- }
-
- public static AttributeSupplier.Builder createAttributes() {
-- return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 14.0D).add(Attributes.ATTACK_DAMAGE, 4.0D);
-+ return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 14.0D).add(Attributes.ATTACK_DAMAGE, 4.0D).add(Attributes.FLYING_SPEED, 0.6D); // Purpur;
- }
-
- @Override
-@@ -228,14 +274,14 @@ public class Vex extends Monster implements TraceableEntity {
- this.setDropChance(EquipmentSlot.MAINHAND, 0.0F);
- }
-
-- private class VexMoveControl extends MoveControl {
-+ private class VexMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur - Ridables
-
- public VexMoveControl(final Vex entityvex) {
- super(entityvex);
- }
-
- @Override
-- public void tick() {
-+ public void vanillaTick() { // Purpur - Ridables
- if (this.operation == MoveControl.Operation.MOVE_TO) {
- Vec3 vec3d = new Vec3(this.wantedX - Vex.this.getX(), this.wantedY - Vex.this.getY(), this.wantedZ - Vex.this.getZ());
- double d0 = vec3d.length();
-@@ -244,7 +290,7 @@ public class Vex extends Monster implements TraceableEntity {
- this.operation = MoveControl.Operation.WAIT;
- Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().scale(0.5D));
- } else {
-- Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().add(vec3d.scale(this.speedModifier * 0.05D / d0)));
-+ Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().add(vec3d.scale(this.getSpeedModifier() * 0.05D / d0))); // Purpur - Ridables
- if (Vex.this.getTarget() == null) {
- Vec3 vec3d1 = Vex.this.getDeltaMovement();
-
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java
-index 96b105697c91314148fd1b783501389214b1a3f0..5bf2aad976be5d6149b8252c84cd870551a2aa8e 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java
-@@ -55,15 +55,34 @@ public class Vindicator extends AbstractIllager {
- super(type, world);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.vindicatorRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.vindicatorRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.vindicatorControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void registerGoals() {
- super.registerGoals();
- this.goalSelector.addGoal(0, new FloatGoal(this));
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Creaking.class, 8.0F, 1.0, 1.2));
- this.goalSelector.addGoal(2, new Vindicator.VindicatorBreakDoorGoal(this));
- this.goalSelector.addGoal(3, new AbstractIllager.RaiderOpenDoorGoal(this));
- this.goalSelector.addGoal(4, new Raider.HoldGroundAttackGoal(this, 10.0F));
- this.goalSelector.addGoal(5, new MeleeAttackGoal(this, 1.0, false));
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.targetSelector.addGoal(1, new HurtByTargetGoal(this, Raider.class).setAlertOthers());
- this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
- this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, true));
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Witch.java b/src/main/java/net/minecraft/world/entity/monster/Witch.java
-index a03fa8a3e648532a7ffaaf523ca87c13e8af4c0a..5cba860f9ce81d90eec4c6bf45699d28cf8d93e6 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Witch.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Witch.java
-@@ -57,6 +57,23 @@ public class Witch extends Raider implements RangedAttackMob {
- super(type, world);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.witchRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.witchRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.witchControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void registerGoals() {
- super.registerGoals();
-@@ -65,10 +82,12 @@ public class Witch extends Raider implements RangedAttackMob {
- });
- this.attackPlayersGoal = new NearestAttackableWitchTargetGoal<>(this, Player.class, 10, true, false, (TargetingConditions.Selector) null);
- this.goalSelector.addGoal(1, new FloatGoal(this));
-+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.goalSelector.addGoal(2, new RangedAttackGoal(this, 1.0D, 60, 10.0F));
- this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 1.0D));
- this.goalSelector.addGoal(3, new LookAtPlayerGoal(this, Player.class, 8.0F));
- this.goalSelector.addGoal(3, new RandomLookAroundGoal(this));
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[]{Raider.class}));
- this.targetSelector.addGoal(2, this.healRaidersGoal);
- this.targetSelector.addGoal(3, this.attackPlayersGoal);
-diff --git a/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java
-index 557b4e225688416132281e9b1759d46a9b775ff9..626cab5a974d2c8736123cc23e535b5cf0e5349e 100644
---- a/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java
-@@ -36,6 +36,23 @@ public class WitherSkeleton extends AbstractSkeleton {
- this.setPathfindingMalus(PathType.LAVA, 8.0F);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.witherSkeletonRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.witherSkeletonRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.witherSkeletonControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void registerGoals() {
- this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractPiglin.class, true));
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Zoglin.java b/src/main/java/net/minecraft/world/entity/monster/Zoglin.java
-index 35b0c5c322864e2f5ae5a412296072f268adcd05..2ac14783e7b5739a13c487d5028ecba38480980d 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Zoglin.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Zoglin.java
-@@ -85,6 +85,23 @@ public class Zoglin extends Monster implements HoglinBase {
- this.xpReward = 5;
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.zoglinRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.zoglinRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.zoglinControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected Brain.Provider brainProvider() {
- return Brain.provider(MEMORY_TYPES, SENSOR_TYPES);
-@@ -250,6 +267,7 @@ public class Zoglin extends Monster implements HoglinBase {
- protected void customServerAiStep(ServerLevel world) {
- ProfilerFiller profilerFiller = Profiler.get();
- profilerFiller.push("zoglinBrain");
-+ if (getRider() == null || !this.isControllable()) // Purpur - only use brain if no rider
- this.getBrain().tick(world, this);
- profilerFiller.pop();
- this.updateActivity();
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java
-index a12461907278cfbfa3b1c0aa74b9f07a31768b8a..9b4b923117a7025bdbb6d222c6388aeae9bef8a2 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java
-@@ -110,11 +110,30 @@ public class Zombie extends Monster {
- this(EntityType.ZOMBIE, world);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.zombieRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.zombieRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.zombieControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void registerGoals() {
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- if (this.level().paperConfig().entities.behavior.zombiesTargetTurtleEggs) this.goalSelector.addGoal(4, new Zombie.ZombieAttackTurtleEggGoal(this, 1.0D, 3)); // Paper - Add zombie targets turtle egg config
- this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0F));
- this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
- this.addBehaviourGoals();
- }
-
-diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java
-index 30bce56a70f923b0ec77c8e3f29e435a71c71510..9fafe05ef0ffc1120873727082290a8ea177d62f 100644
---- a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java
-@@ -85,6 +85,23 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder {
- });
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.zombieVillagerRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.zombieVillagerRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.zombieVillagerControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void defineSynchedData(SynchedEntityData.Builder builder) {
- super.defineSynchedData(builder);
-diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java
-index 03e3cbe73119ca76417d4dd192e1560bdfc373ec..69c291c3347a3e3f454ecb8f418a310bbd688a43 100644
---- a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java
-@@ -63,6 +63,23 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob {
- this.setPathfindingMalus(PathType.LAVA, 8.0F);
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.zombifiedPiglinRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.zombifiedPiglinRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.zombifiedPiglinControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- public void setPersistentAngerTarget(@Nullable UUID angryAt) {
- this.persistentAngerTarget = angryAt;
-diff --git a/src/main/java/net/minecraft/world/entity/monster/creaking/Creaking.java b/src/main/java/net/minecraft/world/entity/monster/creaking/Creaking.java
-index 6a7e725edece3043c8523d387e2929d5ba8932cb..6716bfa903be5ab34b80c963cc9d6a8a26272621 100644
---- a/src/main/java/net/minecraft/world/entity/monster/creaking/Creaking.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/creaking/Creaking.java
-@@ -106,6 +106,29 @@ public class Creaking extends Monster {
- return this.getHomePos() != null;
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.creakingRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.creakingRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.creakingControllable;
-+ }
-+
-+ @Override
-+ protected void registerGoals() {
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected BodyRotationControl createBodyControl() {
- return new Creaking.CreakingBodyRotationControl(this);
-@@ -575,31 +598,31 @@ public class Creaking extends Monster {
- return 0.0F;
- }
-
-- private class CreakingLookControl extends LookControl {
-+ private class CreakingLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur - Ridables
-
- public CreakingLookControl(final Creaking creaking) {
- super(creaking);
- }
-
- @Override
-- public void tick() {
-+ public void vanillaTick() { // Purpur - Ridables
- if (Creaking.this.canMove()) {
-- super.tick();
-+ super.vanillaTick(); // Purpur - Ridables
- }
-
- }
- }
-
-- private class CreakingMoveControl extends MoveControl {
-+ private class CreakingMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - Ridables
-
- public CreakingMoveControl(final Creaking creaking) {
- super(creaking);
- }
-
- @Override
-- public void tick() {
-+ public void vanillaTick() { // Purpur - Ridables
- if (Creaking.this.canMove()) {
-- super.tick();
-+ super.vanillaTick(); // Purpur - Ridables
- }
-
- }
-diff --git a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java
-index 92270912ef26924f611a1df7cb3d5b485b0a262d..4ab971e86b48ce3010928fe9046e8f68224719ca 100644
---- a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java
-@@ -71,6 +71,23 @@ public class Hoglin extends Animal implements Enemy, HoglinBase {
- this.xpReward = 5;
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.hoglinRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.hoglinRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.hoglinControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @VisibleForTesting
- public void setTimeInOverworld(int timeInOverworld) {
- this.timeInOverworld = timeInOverworld;
-@@ -143,6 +160,7 @@ public class Hoglin extends Animal implements Enemy, HoglinBase {
- ProfilerFiller gameprofilerfiller = Profiler.get();
-
- gameprofilerfiller.push("hoglinBrain");
-+ //if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider
- this.getBrain().tick(world, this);
- gameprofilerfiller.pop();
- HoglinAi.updateActivity(this);
-diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java
-index 2121d2a2e1aa1d0f0390cc515317096431f6dcb0..b19e3b442a650f773df462e32088648a2b7eafd4 100644
---- a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java
-@@ -99,6 +99,23 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento
- this.xpReward = 5;
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.piglinRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.piglinRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.piglinControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- public void addAdditionalSaveData(CompoundTag nbt) {
- super.addAdditionalSaveData(nbt);
-@@ -312,6 +329,7 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento
- ProfilerFiller gameprofilerfiller = Profiler.get();
-
- gameprofilerfiller.push("piglinBrain");
-+ //if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider
- this.getBrain().tick(world, this);
- gameprofilerfiller.pop();
- PiglinAi.updateActivity(this);
-diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java
-index 24eaeb93284fe1a573026b85818a93a34fd9e1ec..3dc234f8cea8769f715a4913ae4ecf7d47433577 100644
---- a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java
-@@ -65,6 +65,23 @@ public class PiglinBrute extends AbstractPiglin {
- this.xpReward = 20;
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.piglinBruteRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.piglinBruteRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.piglinBruteControllable;
-+ }
-+ // Purpur end - Ridables
-+
- public static AttributeSupplier.Builder createAttributes() {
- return Monster.createMonsterAttributes()
- .add(Attributes.MAX_HEALTH, 50.0)
-@@ -117,6 +134,7 @@ public class PiglinBrute extends AbstractPiglin {
- protected void customServerAiStep(ServerLevel world) {
- ProfilerFiller profilerFiller = Profiler.get();
- profilerFiller.push("piglinBruteBrain");
-+ if (getRider() == null || this.isControllable()) // Purpur - only use brain if no rider
- this.getBrain().tick(world, this);
- profilerFiller.pop();
- PiglinBruteAi.updateActivity(this);
-diff --git a/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java b/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java
-index c47ed605f0822effd58df4f875297ed015e1e57e..19f7a6d55144adb5217fbea590d8f23d79ed05e0 100644
---- a/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java
-@@ -127,8 +127,32 @@ public class Warden extends Monster implements VibrationSystem {
- this.setPathfindingMalus(PathType.LAVA, 8.0F);
- this.setPathfindingMalus(PathType.DAMAGE_FIRE, 0.0F);
- this.setPathfindingMalus(PathType.DANGER_FIRE, 0.0F);
-+ this.moveControl = new org.purpurmc.purpur.controller.MoveControllerWASD(this, 0.5F); // Purpur - Ridables
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.wardenRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.wardenRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.wardenControllable;
-+ }
-+
-+ @Override
-+ protected void registerGoals() {
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- public Packet getAddEntityPacket(ServerEntity entityTrackerEntry) {
- return new ClientboundAddEntityPacket(this, entityTrackerEntry, this.hasPose(Pose.EMERGING) ? 1 : 0);
-@@ -396,17 +420,14 @@ public class Warden extends Monster implements VibrationSystem {
-
- @Contract("null->false")
- public boolean canTargetEntity(@Nullable Entity entity) {
-- boolean flag;
--
-+ if (getRider() != null && isControllable()) return false; // Purpur - Ridables
- if (entity instanceof LivingEntity entityliving) {
- if (this.level() == entity.level() && EntitySelector.NO_CREATIVE_OR_SPECTATOR.test(entity) && !this.isAlliedTo(entity) && entityliving.getType() != EntityType.ARMOR_STAND && entityliving.getType() != EntityType.WARDEN && !entityliving.isInvulnerable() && !entityliving.isDeadOrDying() && this.level().getWorldBorder().isWithinBounds(entityliving.getBoundingBox())) {
-- flag = true;
-- return flag;
-+ return true; // Purpur - wtf
- }
- }
-
-- flag = false;
-- return flag;
-+ return false; // Purpur - wtf
- }
-
- public static void applyDarknessAround(ServerLevel world, Vec3 pos, @Nullable Entity entity, int range) {
-diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java
-index 2d8ba55906c8da16fde850e3412f4a6bda3d56e7..066836e450ca8d104990c3c5fba5fe3dad2e136e 100644
---- a/src/main/java/net/minecraft/world/entity/npc/Villager.java
-+++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java
-@@ -156,6 +156,28 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
- this.setVillagerData(this.getVillagerData().setType(type).setProfession(VillagerProfession.NONE));
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.villagerRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.villagerRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.villagerControllable;
-+ }
-+
-+ @Override
-+ protected void registerGoals() {
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this));
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- public Brain getBrain() {
- return (Brain) super.getBrain(); // CraftBukkit - decompile error
-@@ -255,7 +277,11 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
- ProfilerFiller gameprofilerfiller = Profiler.get();
-
- gameprofilerfiller.push("villagerBrain");
-- if (!inactive) this.getBrain().tick(world, this);
-+ // Pufferfish start
-+ if (!inactive && (getRider() == null || !this.isControllable()) /*&& this.behaviorTick++ % this.activatedPriority == 0*/) {
-+ this.getBrain().tick(world, this); // Paper // Purpur - Ridables
-+ }
-+ // Pufferfish end
- gameprofilerfiller.pop();
- if (this.assignProfessionWhenSpawned) {
- this.assignProfessionWhenSpawned = false;
-@@ -312,7 +338,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
- if (!itemstack.is(Items.VILLAGER_SPAWN_EGG) && this.isAlive() && !this.isTrading() && !this.isSleeping()) {
- if (this.isBaby()) {
- this.setUnhappy();
-- return InteractionResult.SUCCESS;
-+ return tryRide(player, hand, InteractionResult.SUCCESS); // Purpur - Ridables
- } else {
- if (!this.level().isClientSide) {
- boolean flag = this.getOffers().isEmpty();
-@@ -326,9 +352,10 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
- }
-
- if (flag) {
-- return InteractionResult.CONSUME;
-+ return tryRide(player, hand, InteractionResult.CONSUME); // Purpur - Ridables
- }
-
-+ if (level().purpurConfig.villagerRidable && itemstack.isEmpty()) return tryRide(player, hand); // Purpur - Ridables
- this.startTrading(player);
- }
-
-diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java
-index 1e77cce428d9e53142aaa2cf780b7f862d536eca..c5bef9f0ad2a0f57d8c37ea0833e899dc588d30f 100644
---- a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java
-+++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java
-@@ -72,6 +72,23 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill
- //this.setDespawnDelay(48000); // CraftBukkit - set default from MobSpawnerTrader // Paper - move back to MobSpawnerTrader - Vanilla behavior is that only traders spawned by it have this value set.
- }
-
-+ // Purpur - start
-+ @Override
-+ public boolean isRidable() {
-+ return level().purpurConfig.wanderingTraderRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level().purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level().purpurConfig.wanderingTraderRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level().purpurConfig.wanderingTraderControllable;
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void registerGoals() {
- this.goalSelector.addGoal(0, new FloatGoal(this));
-@@ -120,9 +137,9 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill
-
- if (!this.level().isClientSide) {
- if (this.getOffers().isEmpty()) {
-- return InteractionResult.CONSUME;
-+ return tryRide(player, hand, InteractionResult.CONSUME); // Purpur - Ridables
- }
--
-+ if (level().purpurConfig.wanderingTraderRidable && itemstack.isEmpty()) return tryRide(player, hand); // Purpur - Ridables
- this.setTradingPlayer(player);
- this.openTradingScreen(player, this.getDisplayName(), 1);
- }
-diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java
-index 5b8b85a295a08ae495f729c595b3a78778965342..28a4cf814ec4b34dce883ba4f24ca55008c8f6c1 100644
---- a/src/main/java/net/minecraft/world/entity/player/Player.java
-+++ b/src/main/java/net/minecraft/world/entity/player/Player.java
-@@ -211,6 +211,19 @@ public abstract class Player extends LivingEntity {
- }
- // CraftBukkit end
-
-+ // Purpur start - Ridables
-+ public abstract void resetLastActionTime();
-+
-+ @Override
-+ public boolean processClick(InteractionHand hand) {
-+ Entity vehicle = getRootVehicle();
-+ if (vehicle != null && vehicle.getRider() == this) {
-+ return vehicle.onClick(hand);
-+ }
-+ return false;
-+ }
-+ // Purpur end - Ridables
-+
- public Player(Level world, BlockPos pos, float yaw, GameProfile gameProfile) {
- super(EntityType.PLAYER, world);
- this.lastItemInMainHand = ItemStack.EMPTY;
-diff --git a/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java b/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java
-index 958ea103cc80da7366cc33dc385b76d4f5c809f2..f8ff53488d886bfd67ca3bfe4431b42010052d87 100644
---- a/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java
-+++ b/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java
-@@ -33,6 +33,12 @@ public class LlamaSpit extends Projectile {
- this.setPos(owner.getX() - (double) (owner.getBbWidth() + 1.0F) * 0.5D * (double) Mth.sin(owner.yBodyRot * 0.017453292F), owner.getEyeY() - 0.10000000149011612D, owner.getZ() + (double) (owner.getBbWidth() + 1.0F) * 0.5D * (double) Mth.cos(owner.yBodyRot * 0.017453292F));
- }
-
-+ // Purpur start - Ridables
-+ public void super_tick() {
-+ super.tick();
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected double getDefaultGravity() {
- return 0.06D;
-diff --git a/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java b/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java
-index 4c47b30867e30d84908abf93dbefc252bc8c3453..8296765d8f63f1a9fd207b27d495d7c04646f134 100644
---- a/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java
-+++ b/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java
-@@ -115,6 +115,14 @@ public class WitherSkull extends AbstractHurtingProjectile {
-
- }
-
-+ // Purpur start - Ridables
-+ @Override
-+ public boolean canHitEntity(Entity target) {
-+ // do not hit rider
-+ return target != this.getRider() && super.canHitEntity(target);
-+ }
-+ // Purpur end - Ridables
-+
- @Override
- protected void defineSynchedData(SynchedEntityData.Builder builder) {
- builder.define(WitherSkull.DATA_DANGEROUS, false);
-diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
-index b25b10c24a379097233e61bcc10add841b6a7115..c105d0cce462df46e106eb502355225b83be32b7 100644
---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
-+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
-@@ -1306,4 +1306,27 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
- }
- }
- // Paper end - broadcast hurt animation
-+
-+ // Purpur start - Ridables
-+ @Override
-+ public org.bukkit.entity.Player getRider() {
-+ net.minecraft.world.entity.player.Player rider = getHandle().getRider();
-+ return rider != null ? (org.bukkit.entity.Player) rider.getBukkitEntity() : null;
-+ }
-+
-+ @Override
-+ public boolean hasRider() {
-+ return getHandle().getRider() != null;
-+ }
-+
-+ @Override
-+ public boolean isRidable() {
-+ return getHandle().isRidable();
-+ }
-+
-+ @Override
-+ public boolean isRidableInWater() {
-+ return !getHandle().dismountsUnderwater();
-+ }
-+ // Purpur end - Ridables
- }
-diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
-index e37aaf77f94b97b736cc20ef070cefdff0400188..eb2f9bfdaf3ed8a684337a15365e70174d1533b3 100644
---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
-+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
-@@ -602,6 +602,15 @@ public class CraftEventFactory {
- // Paper end
- craftServer.getPluginManager().callEvent(event);
-
-+ // Purpur start - Ridables
-+ if (who != null) {
-+ switch (action) {
-+ case LEFT_CLICK_BLOCK, LEFT_CLICK_AIR -> who.processClick(InteractionHand.MAIN_HAND);
-+ case RIGHT_CLICK_BLOCK, RIGHT_CLICK_AIR -> who.processClick(InteractionHand.OFF_HAND);
-+ }
-+ }
-+ // Purpur end - Ridables
-+
- return event;
- }
-
-@@ -1191,6 +1200,7 @@ public class CraftEventFactory {
- EntityDamageEvent event;
- if (damager != null) {
- event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions, critical);
-+ damager.processClick(InteractionHand.MAIN_HAND); // Purpur - Ridables
- } else {
- event = new EntityDamageEvent(damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions);
- }
-diff --git a/src/main/java/org/purpurmc/purpur/PurpurConfig.java b/src/main/java/org/purpurmc/purpur/PurpurConfig.java
-index c2991c34fd4306fae79fca2c1349c826b3247c49..e8c9393760108f2549b52a61d973ea2b4a64a312 100644
---- a/src/main/java/org/purpurmc/purpur/PurpurConfig.java
-+++ b/src/main/java/org/purpurmc/purpur/PurpurConfig.java
-@@ -175,4 +175,9 @@ public class PurpurConfig {
- }
- return builder.build();
- }
-+
-+ public static String cannotRideMob = "You cannot mount that mob";
-+ private static void messages() {
-+ cannotRideMob = getString("settings.messages.cannot-ride-mob", cannotRideMob);
-+ }
- }
-diff --git a/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java
-index 42e502cfcb8d2e775cbf738773caf1a087d2f3f4..5004e86747306cc8d4bbed6f10d3a6e9047cb5f4 100644
---- a/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java
-+++ b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java
-@@ -90,4 +90,753 @@ public class PurpurWorldConfig {
- final Map value = PurpurConfig.getMap("world-settings." + worldName + "." + path, null);
- return value.isEmpty() ? fallback : value;
- }
-+
-+ public boolean babiesAreRidable = true;
-+ public boolean untamedTamablesAreRidable = true;
-+ public boolean useNightVisionWhenRiding = false;
-+ public boolean useDismountsUnderwaterTag = true;
-+ private void ridableSettings() {
-+ babiesAreRidable = getBoolean("ridable-settings.babies-are-ridable", babiesAreRidable);
-+ untamedTamablesAreRidable = getBoolean("ridable-settings.untamed-tamables-are-ridable", untamedTamablesAreRidable);
-+ useNightVisionWhenRiding = getBoolean("ridable-settings.use-night-vision", useNightVisionWhenRiding);
-+ useDismountsUnderwaterTag = getBoolean("ridable-settings.use-dismounts-underwater-tag", useDismountsUnderwaterTag);
-+ }
-+
-+ public boolean allayRidable = false;
-+ public boolean allayRidableInWater = true;
-+ public boolean allayControllable = true;
-+ private void allaySettings() {
-+ allayRidable = getBoolean("mobs.allay.ridable", allayRidable);
-+ allayRidableInWater = getBoolean("mobs.allay.ridable-in-water", allayRidableInWater);
-+ allayControllable = getBoolean("mobs.allay.controllable", allayControllable);
-+ }
-+
-+ public boolean armadilloRidable = false;
-+ public boolean armadilloRidableInWater = true;
-+ public boolean armadilloControllable = true;
-+ private void armadilloSettings() {
-+ armadilloRidable = getBoolean("mobs.armadillo.ridable", armadilloRidable);
-+ armadilloRidableInWater = getBoolean("mobs.armadillo.ridable-in-water", armadilloRidableInWater);
-+ armadilloControllable = getBoolean("mobs.armadillo.controllable", armadilloControllable);
-+ }
-+
-+ public boolean axolotlRidable = false;
-+ public boolean axolotlControllable = true;
-+ private void axolotlSettings() {
-+ axolotlRidable = getBoolean("mobs.axolotl.ridable", axolotlRidable);
-+ axolotlControllable = getBoolean("mobs.axolotl.controllable", axolotlControllable);
-+ }
-+
-+ public boolean batRidable = false;
-+ public boolean batRidableInWater = true;
-+ public boolean batControllable = true;
-+ public double batMaxY = 320D;
-+ private void batSettings() {
-+ batRidable = getBoolean("mobs.bat.ridable", batRidable);
-+ batRidableInWater = getBoolean("mobs.bat.ridable-in-water", batRidableInWater);
-+ batControllable = getBoolean("mobs.bat.controllable", batControllable);
-+ batMaxY = getDouble("mobs.bat.ridable-max-y", batMaxY);
-+ }
-+
-+ public boolean beeRidable = false;
-+ public boolean beeRidableInWater = true;
-+ public boolean beeControllable = true;
-+ public double beeMaxY = 320D;
-+ private void beeSettings() {
-+ beeRidable = getBoolean("mobs.bee.ridable", beeRidable);
-+ beeRidableInWater = getBoolean("mobs.bee.ridable-in-water", beeRidableInWater);
-+ beeControllable = getBoolean("mobs.bee.controllable", beeControllable);
-+ beeMaxY = getDouble("mobs.bee.ridable-max-y", beeMaxY);
-+ }
-+
-+ public boolean blazeRidable = false;
-+ public boolean blazeRidableInWater = true;
-+ public boolean blazeControllable = true;
-+ public double blazeMaxY = 320D;
-+ private void blazeSettings() {
-+ blazeRidable = getBoolean("mobs.blaze.ridable", blazeRidable);
-+ blazeRidableInWater = getBoolean("mobs.blaze.ridable-in-water", blazeRidableInWater);
-+ blazeControllable = getBoolean("mobs.blaze.controllable", blazeControllable);
-+ blazeMaxY = getDouble("mobs.blaze.ridable-max-y", blazeMaxY);
-+ }
-+
-+ public boolean boggedRidable = false;
-+ public boolean boggedRidableInWater = true;
-+ public boolean boggedControllable = true;
-+ private void boggedSettings() {
-+ boggedRidable = getBoolean("mobs.bogged.ridable", boggedRidable);
-+ boggedRidableInWater = getBoolean("mobs.bogged.ridable-in-water", boggedRidableInWater);
-+ boggedControllable = getBoolean("mobs.bogged.controllable", boggedControllable);
-+ }
-+
-+ public boolean camelRidableInWater = false;
-+ private void camelSettings() {
-+ camelRidableInWater = getBoolean("mobs.camel.ridable-in-water", camelRidableInWater);
-+ }
-+
-+ public boolean catRidable = false;
-+ public boolean catRidableInWater = true;
-+ public boolean catControllable = true;
-+ private void catSettings() {
-+ catRidable = getBoolean("mobs.cat.ridable", catRidable);
-+ catRidableInWater = getBoolean("mobs.cat.ridable-in-water", catRidableInWater);
-+ catControllable = getBoolean("mobs.cat.controllable", catControllable);
-+ }
-+
-+ public boolean caveSpiderRidable = false;
-+ public boolean caveSpiderRidableInWater = true;
-+ public boolean caveSpiderControllable = true;
-+ private void caveSpiderSettings() {
-+ caveSpiderRidable = getBoolean("mobs.cave_spider.ridable", caveSpiderRidable);
-+ caveSpiderRidableInWater = getBoolean("mobs.cave_spider.ridable-in-water", caveSpiderRidableInWater);
-+ caveSpiderControllable = getBoolean("mobs.cave_spider.controllable", caveSpiderControllable);
-+ }
-+
-+ public boolean chickenRidable = false;
-+ public boolean chickenRidableInWater = false;
-+ public boolean chickenControllable = true;
-+ private void chickenSettings() {
-+ chickenRidable = getBoolean("mobs.chicken.ridable", chickenRidable);
-+ chickenRidableInWater = getBoolean("mobs.chicken.ridable-in-water", chickenRidableInWater);
-+ chickenControllable = getBoolean("mobs.chicken.controllable", chickenControllable);
-+ }
-+
-+ public boolean codRidable = false;
-+ public boolean codControllable = true;
-+ private void codSettings() {
-+ codRidable = getBoolean("mobs.cod.ridable", codRidable);
-+ codControllable = getBoolean("mobs.cod.controllable", codControllable);
-+ }
-+
-+ public boolean cowRidable = false;
-+ public boolean cowRidableInWater = true;
-+ public boolean cowControllable = true;
-+ private void cowSettings() {
-+ cowRidable = getBoolean("mobs.cow.ridable", cowRidable);
-+ cowRidableInWater = getBoolean("mobs.cow.ridable-in-water", cowRidableInWater);
-+ cowControllable = getBoolean("mobs.cow.controllable", cowControllable);
-+ }
-+
-+ public boolean creakingRidable = false;
-+ public boolean creakingRidableInWater = true;
-+ public boolean creakingControllable = true;
-+ private void creakingSettings() {
-+ creakingRidable = getBoolean("mobs.creaking.ridable", creakingRidable);
-+ creakingRidableInWater = getBoolean("mobs.creaking.ridable-in-water", creakingRidableInWater);
-+ creakingControllable = getBoolean("mobs.creaking.controllable", creakingControllable);
-+ }
-+
-+ public boolean creeperRidable = false;
-+ public boolean creeperRidableInWater = true;
-+ public boolean creeperControllable = true;
-+ private void creeperSettings() {
-+ creeperRidable = getBoolean("mobs.creeper.ridable", creeperRidable);
-+ creeperRidableInWater = getBoolean("mobs.creeper.ridable-in-water", creeperRidableInWater);
-+ creeperControllable = getBoolean("mobs.creeper.controllable", creeperControllable);
-+ }
-+
-+ public boolean dolphinRidable = false;
-+ public boolean dolphinControllable = true;
-+ public int dolphinSpitCooldown = 20;
-+ public float dolphinSpitSpeed = 1.0F;
-+ public float dolphinSpitDamage = 2.0F;
-+ private void dolphinSettings() {
-+ dolphinRidable = getBoolean("mobs.dolphin.ridable", dolphinRidable);
-+ dolphinControllable = getBoolean("mobs.dolphin.controllable", dolphinControllable);
-+ dolphinSpitCooldown = getInt("mobs.dolphin.spit.cooldown", dolphinSpitCooldown);
-+ dolphinSpitSpeed = (float) getDouble("mobs.dolphin.spit.speed", dolphinSpitSpeed);
-+ dolphinSpitDamage = (float) getDouble("mobs.dolphin.spit.damage", dolphinSpitDamage);
-+ }
-+
-+ public boolean donkeyRidableInWater = false;
-+ private void donkeySettings() {
-+ donkeyRidableInWater = getBoolean("mobs.donkey.ridable-in-water", donkeyRidableInWater);
-+ }
-+
-+ public boolean drownedRidable = false;
-+ public boolean drownedRidableInWater = true;
-+ public boolean drownedControllable = true;
-+ private void drownedSettings() {
-+ drownedRidable = getBoolean("mobs.drowned.ridable", drownedRidable);
-+ drownedRidableInWater = getBoolean("mobs.drowned.ridable-in-water", drownedRidableInWater);
-+ drownedControllable = getBoolean("mobs.drowned.controllable", drownedControllable);
-+ }
-+
-+ public boolean elderGuardianRidable = false;
-+ public boolean elderGuardianControllable = true;
-+ private void elderGuardianSettings() {
-+ elderGuardianRidable = getBoolean("mobs.elder_guardian.ridable", elderGuardianRidable);
-+ elderGuardianControllable = getBoolean("mobs.elder_guardian.controllable", elderGuardianControllable);
-+ }
-+
-+ public boolean enderDragonRidable = false;
-+ public boolean enderDragonRidableInWater = true;
-+ public boolean enderDragonControllable = true;
-+ public double enderDragonMaxY = 320D;
-+ private void enderDragonSettings() {
-+ enderDragonRidable = getBoolean("mobs.ender_dragon.ridable", enderDragonRidable);
-+ enderDragonRidableInWater = getBoolean("mobs.ender_dragon.ridable-in-water", enderDragonRidableInWater);
-+ enderDragonControllable = getBoolean("mobs.ender_dragon.controllable", enderDragonControllable);
-+ enderDragonMaxY = getDouble("mobs.ender_dragon.ridable-max-y", enderDragonMaxY);
-+ }
-+
-+ public boolean endermanRidable = false;
-+ public boolean endermanRidableInWater = true;
-+ public boolean endermanControllable = true;
-+ private void endermanSettings() {
-+ endermanRidable = getBoolean("mobs.enderman.ridable", endermanRidable);
-+ endermanRidableInWater = getBoolean("mobs.enderman.ridable-in-water", endermanRidableInWater);
-+ endermanControllable = getBoolean("mobs.enderman.controllable", endermanControllable);
-+ }
-+
-+ public boolean endermiteRidable = false;
-+ public boolean endermiteRidableInWater = true;
-+ public boolean endermiteControllable = true;
-+ private void endermiteSettings() {
-+ endermiteRidable = getBoolean("mobs.endermite.ridable", endermiteRidable);
-+ endermiteRidableInWater = getBoolean("mobs.endermite.ridable-in-water", endermiteRidableInWater);
-+ endermiteControllable = getBoolean("mobs.endermite.controllable", endermiteControllable);
-+ }
-+
-+ public boolean evokerRidable = false;
-+ public boolean evokerRidableInWater = true;
-+ public boolean evokerControllable = true;
-+ private void evokerSettings() {
-+ evokerRidable = getBoolean("mobs.evoker.ridable", evokerRidable);
-+ evokerRidableInWater = getBoolean("mobs.evoker.ridable-in-water", evokerRidableInWater);
-+ evokerControllable = getBoolean("mobs.evoker.controllable", evokerControllable);
-+ }
-+
-+ public boolean foxRidable = false;
-+ public boolean foxRidableInWater = true;
-+ public boolean foxControllable = true;
-+ private void foxSettings() {
-+ foxRidable = getBoolean("mobs.fox.ridable", foxRidable);
-+ foxRidableInWater = getBoolean("mobs.fox.ridable-in-water", foxRidableInWater);
-+ foxControllable = getBoolean("mobs.fox.controllable", foxControllable);
-+ }
-+
-+ public boolean frogRidable = false;
-+ public boolean frogRidableInWater = true;
-+ public boolean frogControllable = true;
-+ public float frogRidableJumpHeight = 0.65F;
-+ private void frogSettings() {
-+ frogRidable = getBoolean("mobs.frog.ridable", frogRidable);
-+ frogRidableInWater = getBoolean("mobs.frog.ridable-in-water", frogRidableInWater);
-+ frogControllable = getBoolean("mobs.frog.controllable", frogControllable);
-+ frogRidableJumpHeight = (float) getDouble("mobs.frog.ridable-jump-height", frogRidableJumpHeight);
-+ }
-+
-+ public boolean ghastRidable = false;
-+ public boolean ghastRidableInWater = true;
-+ public boolean ghastControllable = true;
-+ public double ghastMaxY = 320D;
-+ private void ghastSettings() {
-+ ghastRidable = getBoolean("mobs.ghast.ridable", ghastRidable);
-+ ghastRidableInWater = getBoolean("mobs.ghast.ridable-in-water", ghastRidableInWater);
-+ ghastControllable = getBoolean("mobs.ghast.controllable", ghastControllable);
-+ ghastMaxY = getDouble("mobs.ghast.ridable-max-y", ghastMaxY);
-+ }
-+
-+ public boolean giantRidable = false;
-+ public boolean giantRidableInWater = true;
-+ public boolean giantControllable = true;
-+ private void giantSettings() {
-+ giantRidable = getBoolean("mobs.giant.ridable", giantRidable);
-+ giantRidableInWater = getBoolean("mobs.giant.ridable-in-water", giantRidableInWater);
-+ giantControllable = getBoolean("mobs.giant.controllable", giantControllable);
-+ }
-+
-+ public boolean glowSquidRidable = false;
-+ public boolean glowSquidControllable = true;
-+ private void glowSquidSettings() {
-+ glowSquidRidable = getBoolean("mobs.glow_squid.ridable", glowSquidRidable);
-+ glowSquidControllable = getBoolean("mobs.glow_squid.controllable", glowSquidControllable);
-+ }
-+
-+ public boolean goatRidable = false;
-+ public boolean goatRidableInWater = true;
-+ public boolean goatControllable = true;
-+ private void goatSettings() {
-+ goatRidable = getBoolean("mobs.goat.ridable", goatRidable);
-+ goatRidableInWater = getBoolean("mobs.goat.ridable-in-water", goatRidableInWater);
-+ goatControllable = getBoolean("mobs.goat.controllable", goatControllable);
-+ }
-+
-+ public boolean guardianRidable = false;
-+ public boolean guardianControllable = true;
-+ private void guardianSettings() {
-+ guardianRidable = getBoolean("mobs.guardian.ridable", guardianRidable);
-+ guardianControllable = getBoolean("mobs.guardian.controllable", guardianControllable);
-+ }
-+
-+ public boolean hoglinRidable = false;
-+ public boolean hoglinRidableInWater = true;
-+ public boolean hoglinControllable = true;
-+ private void hoglinSettings() {
-+ hoglinRidable = getBoolean("mobs.hoglin.ridable", hoglinRidable);
-+ hoglinRidableInWater = getBoolean("mobs.hoglin.ridable-in-water", hoglinRidableInWater);
-+ hoglinControllable = getBoolean("mobs.hoglin.controllable", hoglinControllable);
-+ }
-+
-+ public boolean horseRidableInWater = false;
-+ private void horseSettings() {
-+ horseRidableInWater = getBoolean("mobs.horse.ridable-in-water", horseRidableInWater);
-+ }
-+
-+ public boolean huskRidable = false;
-+ public boolean huskRidableInWater = true;
-+ public boolean huskControllable = true;
-+ private void huskSettings() {
-+ huskRidable = getBoolean("mobs.husk.ridable", huskRidable);
-+ huskRidableInWater = getBoolean("mobs.husk.ridable-in-water", huskRidableInWater);
-+ huskControllable = getBoolean("mobs.husk.controllable", huskControllable);
-+ }
-+
-+ public boolean illusionerRidable = false;
-+ public boolean illusionerRidableInWater = true;
-+ public boolean illusionerControllable = true;
-+ private void illusionerSettings() {
-+ illusionerRidable = getBoolean("mobs.illusioner.ridable", illusionerRidable);
-+ illusionerRidableInWater = getBoolean("mobs.illusioner.ridable-in-water", illusionerRidableInWater);
-+ illusionerControllable = getBoolean("mobs.illusioner.controllable", illusionerControllable);
-+ }
-+
-+ public boolean ironGolemRidable = false;
-+ public boolean ironGolemRidableInWater = true;
-+ public boolean ironGolemControllable = true;
-+ public boolean ironGolemCanSwim = false;
-+ private void ironGolemSettings() {
-+ ironGolemRidable = getBoolean("mobs.iron_golem.ridable", ironGolemRidable);
-+ ironGolemRidableInWater = getBoolean("mobs.iron_golem.ridable-in-water", ironGolemRidableInWater);
-+ ironGolemControllable = getBoolean("mobs.iron_golem.controllable", ironGolemControllable);
-+ ironGolemCanSwim = getBoolean("mobs.iron_golem.can-swim", ironGolemCanSwim);
-+ }
-+
-+ public boolean llamaRidable = false;
-+ public boolean llamaRidableInWater = false;
-+ public boolean llamaControllable = true;
-+ private void llamaSettings() {
-+ llamaRidable = getBoolean("mobs.llama.ridable", llamaRidable);
-+ llamaRidableInWater = getBoolean("mobs.llama.ridable-in-water", llamaRidableInWater);
-+ llamaControllable = getBoolean("mobs.llama.controllable", llamaControllable);
-+ }
-+
-+ public boolean magmaCubeRidable = false;
-+ public boolean magmaCubeRidableInWater = true;
-+ public boolean magmaCubeControllable = true;
-+ private void magmaCubeSettings() {
-+ magmaCubeRidable = getBoolean("mobs.magma_cube.ridable", magmaCubeRidable);
-+ magmaCubeRidableInWater = getBoolean("mobs.magma_cube.ridable-in-water", magmaCubeRidableInWater);
-+ magmaCubeControllable = getBoolean("mobs.magma_cube.controllable", magmaCubeControllable);
-+ }
-+
-+ public boolean mooshroomRidable = false;
-+ public boolean mooshroomRidableInWater = true;
-+ public boolean mooshroomControllable = true;
-+ private void mooshroomSettings() {
-+ mooshroomRidable = getBoolean("mobs.mooshroom.ridable", mooshroomRidable);
-+ mooshroomRidableInWater = getBoolean("mobs.mooshroom.ridable-in-water", mooshroomRidableInWater);
-+ mooshroomControllable = getBoolean("mobs.mooshroom.controllable", mooshroomControllable);
-+ }
-+
-+ public boolean muleRidableInWater = false;
-+ private void muleSettings() {
-+ muleRidableInWater = getBoolean("mobs.mule.ridable-in-water", muleRidableInWater);
-+ }
-+
-+ public boolean ocelotRidable = false;
-+ public boolean ocelotRidableInWater = true;
-+ public boolean ocelotControllable = true;
-+ private void ocelotSettings() {
-+ ocelotRidable = getBoolean("mobs.ocelot.ridable", ocelotRidable);
-+ ocelotRidableInWater = getBoolean("mobs.ocelot.ridable-in-water", ocelotRidableInWater);
-+ ocelotControllable = getBoolean("mobs.ocelot.controllable", ocelotControllable);
-+ }
-+
-+ public boolean pandaRidable = false;
-+ public boolean pandaRidableInWater = true;
-+ public boolean pandaControllable = true;
-+ private void pandaSettings() {
-+ pandaRidable = getBoolean("mobs.panda.ridable", pandaRidable);
-+ pandaRidableInWater = getBoolean("mobs.panda.ridable-in-water", pandaRidableInWater);
-+ pandaControllable = getBoolean("mobs.panda.controllable", pandaControllable);
-+ }
-+
-+ public boolean parrotRidable = false;
-+ public boolean parrotRidableInWater = true;
-+ public boolean parrotControllable = true;
-+ public double parrotMaxY = 320D;
-+ private void parrotSettings() {
-+ parrotRidable = getBoolean("mobs.parrot.ridable", parrotRidable);
-+ parrotRidableInWater = getBoolean("mobs.parrot.ridable-in-water", parrotRidableInWater);
-+ parrotControllable = getBoolean("mobs.parrot.controllable", parrotControllable);
-+ parrotMaxY = getDouble("mobs.parrot.ridable-max-y", parrotMaxY);
-+ }
-+
-+ public boolean phantomRidable = false;
-+ public boolean phantomRidableInWater = true;
-+ public boolean phantomControllable = true;
-+ public double phantomMaxY = 320D;
-+ public float phantomFlameDamage = 1.0F;
-+ public int phantomFlameFireTime = 8;
-+ public boolean phantomAllowGriefing = false;
-+ private void phantomSettings() {
-+ phantomRidable = getBoolean("mobs.phantom.ridable", phantomRidable);
-+ phantomRidableInWater = getBoolean("mobs.phantom.ridable-in-water", phantomRidableInWater);
-+ phantomControllable = getBoolean("mobs.phantom.controllable", phantomControllable);
-+ phantomMaxY = getDouble("mobs.phantom.ridable-max-y", phantomMaxY);
-+ phantomFlameDamage = (float) getDouble("mobs.phantom.flames.damage", phantomFlameDamage);
-+ phantomFlameFireTime = getInt("mobs.phantom.flames.fire-time", phantomFlameFireTime);
-+ phantomAllowGriefing = getBoolean("mobs.phantom.allow-griefing", phantomAllowGriefing);
-+ }
-+
-+ public boolean pigRidable = false;
-+ public boolean pigRidableInWater = false;
-+ public boolean pigControllable = true;
-+ private void pigSettings() {
-+ pigRidable = getBoolean("mobs.pig.ridable", pigRidable);
-+ pigRidableInWater = getBoolean("mobs.pig.ridable-in-water", pigRidableInWater);
-+ pigControllable = getBoolean("mobs.pig.controllable", pigControllable);
-+ }
-+
-+ public boolean piglinRidable = false;
-+ public boolean piglinRidableInWater = true;
-+ public boolean piglinControllable = true;
-+ private void piglinSettings() {
-+ piglinRidable = getBoolean("mobs.piglin.ridable", piglinRidable);
-+ piglinRidableInWater = getBoolean("mobs.piglin.ridable-in-water", piglinRidableInWater);
-+ piglinControllable = getBoolean("mobs.piglin.controllable", piglinControllable);
-+ }
-+
-+ public boolean piglinBruteRidable = false;
-+ public boolean piglinBruteRidableInWater = true;
-+ public boolean piglinBruteControllable = true;
-+ private void piglinBruteSettings() {
-+ piglinBruteRidable = getBoolean("mobs.piglin_brute.ridable", piglinBruteRidable);
-+ piglinBruteRidableInWater = getBoolean("mobs.piglin_brute.ridable-in-water", piglinBruteRidableInWater);
-+ piglinBruteControllable = getBoolean("mobs.piglin_brute.controllable", piglinBruteControllable);
-+ }
-+
-+ public boolean pillagerRidable = false;
-+ public boolean pillagerRidableInWater = true;
-+ public boolean pillagerControllable = true;
-+ private void pillagerSettings() {
-+ pillagerRidable = getBoolean("mobs.pillager.ridable", pillagerRidable);
-+ pillagerRidableInWater = getBoolean("mobs.pillager.ridable-in-water", pillagerRidableInWater);
-+ pillagerControllable = getBoolean("mobs.pillager.controllable", pillagerControllable);
-+ }
-+
-+ public boolean polarBearRidable = false;
-+ public boolean polarBearRidableInWater = true;
-+ public boolean polarBearControllable = true;
-+ private void polarBearSettings() {
-+ polarBearRidable = getBoolean("mobs.polar_bear.ridable", polarBearRidable);
-+ polarBearRidableInWater = getBoolean("mobs.polar_bear.ridable-in-water", polarBearRidableInWater);
-+ polarBearControllable = getBoolean("mobs.polar_bear.controllable", polarBearControllable);
-+ }
-+
-+ public boolean pufferfishRidable = false;
-+ public boolean pufferfishControllable = true;
-+ private void pufferfishSettings() {
-+ pufferfishRidable = getBoolean("mobs.pufferfish.ridable", pufferfishRidable);
-+ pufferfishControllable = getBoolean("mobs.pufferfish.controllable", pufferfishControllable);
-+ }
-+
-+ public boolean rabbitRidable = false;
-+ public boolean rabbitRidableInWater = true;
-+ public boolean rabbitControllable = true;
-+ private void rabbitSettings() {
-+ rabbitRidable = getBoolean("mobs.rabbit.ridable", rabbitRidable);
-+ rabbitRidableInWater = getBoolean("mobs.rabbit.ridable-in-water", rabbitRidableInWater);
-+ rabbitControllable = getBoolean("mobs.rabbit.controllable", rabbitControllable);
-+ }
-+
-+ public boolean ravagerRidable = false;
-+ public boolean ravagerRidableInWater = false;
-+ public boolean ravagerControllable = true;
-+ private void ravagerSettings() {
-+ ravagerRidable = getBoolean("mobs.ravager.ridable", ravagerRidable);
-+ ravagerRidableInWater = getBoolean("mobs.ravager.ridable-in-water", ravagerRidableInWater);
-+ ravagerControllable = getBoolean("mobs.ravager.controllable", ravagerControllable);
-+ }
-+
-+ public boolean salmonRidable = false;
-+ public boolean salmonControllable = true;
-+ private void salmonSettings() {
-+ salmonRidable = getBoolean("mobs.salmon.ridable", salmonRidable);
-+ salmonControllable = getBoolean("mobs.salmon.controllable", salmonControllable);
-+ }
-+
-+ public boolean sheepRidable = false;
-+ public boolean sheepRidableInWater = true;
-+ public boolean sheepControllable = true;
-+ private void sheepSettings() {
-+ sheepRidable = getBoolean("mobs.sheep.ridable", sheepRidable);
-+ sheepRidableInWater = getBoolean("mobs.sheep.ridable-in-water", sheepRidableInWater);
-+ sheepControllable = getBoolean("mobs.sheep.controllable", sheepControllable);
-+ }
-+
-+ public boolean shulkerRidable = false;
-+ public boolean shulkerRidableInWater = true;
-+ public boolean shulkerControllable = true;
-+ private void shulkerSettings() {
-+ shulkerRidable = getBoolean("mobs.shulker.ridable", shulkerRidable);
-+ shulkerRidableInWater = getBoolean("mobs.shulker.ridable-in-water", shulkerRidableInWater);
-+ shulkerControllable = getBoolean("mobs.shulker.controllable", shulkerControllable);
-+ }
-+
-+ public boolean silverfishRidable = false;
-+ public boolean silverfishRidableInWater = true;
-+ public boolean silverfishControllable = true;
-+ private void silverfishSettings() {
-+ silverfishRidable = getBoolean("mobs.silverfish.ridable", silverfishRidable);
-+ silverfishRidableInWater = getBoolean("mobs.silverfish.ridable-in-water", silverfishRidableInWater);
-+ silverfishControllable = getBoolean("mobs.silverfish.controllable", silverfishControllable);
-+ }
-+
-+ public boolean skeletonRidable = false;
-+ public boolean skeletonRidableInWater = true;
-+ public boolean skeletonControllable = true;
-+ private void skeletonSettings() {
-+ skeletonRidable = getBoolean("mobs.skeleton.ridable", skeletonRidable);
-+ skeletonRidableInWater = getBoolean("mobs.skeleton.ridable-in-water", skeletonRidableInWater);
-+ skeletonControllable = getBoolean("mobs.skeleton.controllable", skeletonControllable);
-+ }
-+
-+ public boolean skeletonHorseRidable = false;
-+ public boolean skeletonHorseRidableInWater = true;
-+ public boolean skeletonHorseCanSwim = false;
-+ private void skeletonHorseSettings() {
-+ skeletonHorseRidable = getBoolean("mobs.skeleton_horse.ridable", skeletonHorseRidable);
-+ skeletonHorseRidableInWater = getBoolean("mobs.skeleton_horse.ridable-in-water", skeletonHorseRidableInWater);
-+ skeletonHorseCanSwim = getBoolean("mobs.skeleton_horse.can-swim", skeletonHorseCanSwim);
-+ }
-+
-+ public boolean slimeRidable = false;
-+ public boolean slimeRidableInWater = true;
-+ public boolean slimeControllable = true;
-+ private void slimeSettings() {
-+ slimeRidable = getBoolean("mobs.slime.ridable", slimeRidable);
-+ slimeRidableInWater = getBoolean("mobs.slime.ridable-in-water", slimeRidableInWater);
-+ slimeControllable = getBoolean("mobs.slime.controllable", slimeControllable);
-+ }
-+
-+ public boolean snowGolemRidable = false;
-+ public boolean snowGolemRidableInWater = true;
-+ public boolean snowGolemControllable = true;
-+ public boolean snowGolemLeaveTrailWhenRidden = false;
-+ private void snowGolemSettings() {
-+ snowGolemRidable = getBoolean("mobs.snow_golem.ridable", snowGolemRidable);
-+ snowGolemRidableInWater = getBoolean("mobs.snow_golem.ridable-in-water", snowGolemRidableInWater);
-+ snowGolemControllable = getBoolean("mobs.snow_golem.controllable", snowGolemControllable);
-+ snowGolemLeaveTrailWhenRidden = getBoolean("mobs.snow_golem.leave-trail-when-ridden", snowGolemLeaveTrailWhenRidden);
-+ }
-+
-+ public boolean snifferRidable = false;
-+ public boolean snifferRidableInWater = true;
-+ public boolean snifferControllable = true;
-+ private void snifferSettings() {
-+ snifferRidable = getBoolean("mobs.sniffer.ridable", snifferRidable);
-+ snifferRidableInWater = getBoolean("mobs.sniffer.ridable-in-water", snifferRidableInWater);
-+ snifferControllable = getBoolean("mobs.sniffer.controllable", snifferControllable);
-+ }
-+
-+ public boolean squidRidable = false;
-+ public boolean squidControllable = true;
-+ private void squidSettings() {
-+ squidRidable = getBoolean("mobs.squid.ridable", squidRidable);
-+ squidControllable = getBoolean("mobs.squid.controllable", squidControllable);
-+ }
-+
-+ public boolean spiderRidable = false;
-+ public boolean spiderRidableInWater = false;
-+ public boolean spiderControllable = true;
-+ private void spiderSettings() {
-+ spiderRidable = getBoolean("mobs.spider.ridable", spiderRidable);
-+ spiderRidableInWater = getBoolean("mobs.spider.ridable-in-water", spiderRidableInWater);
-+ spiderControllable = getBoolean("mobs.spider.controllable", spiderControllable);
-+ }
-+
-+ public boolean strayRidable = false;
-+ public boolean strayRidableInWater = true;
-+ public boolean strayControllable = true;
-+ private void straySettings() {
-+ strayRidable = getBoolean("mobs.stray.ridable", strayRidable);
-+ strayRidableInWater = getBoolean("mobs.stray.ridable-in-water", strayRidableInWater);
-+ strayControllable = getBoolean("mobs.stray.controllable", strayControllable);
-+ }
-+
-+ public boolean striderRidable = false;
-+ public boolean striderRidableInWater = false;
-+ public boolean striderControllable = true;
-+ private void striderSettings() {
-+ striderRidable = getBoolean("mobs.strider.ridable", striderRidable);
-+ striderRidableInWater = getBoolean("mobs.strider.ridable-in-water", striderRidableInWater);
-+ striderControllable = getBoolean("mobs.strider.controllable", striderControllable);
-+ }
-+
-+ public boolean tadpoleRidable = false;
-+ public boolean tadpoleRidableInWater = true;
-+ public boolean tadpoleControllable = true;
-+ private void tadpoleSettings() {
-+ tadpoleRidable = getBoolean("mobs.tadpole.ridable", tadpoleRidable);
-+ tadpoleRidableInWater = getBoolean("mobs.tadpole.ridable-in-water", tadpoleRidableInWater);
-+ tadpoleControllable = getBoolean("mobs.tadpole.controllable", tadpoleControllable);
-+ }
-+
-+ public boolean traderLlamaRidable = false;
-+ public boolean traderLlamaRidableInWater = false;
-+ public boolean traderLlamaControllable = true;
-+ private void traderLlamaSettings() {
-+ traderLlamaRidable = getBoolean("mobs.trader_llama.ridable", traderLlamaRidable);
-+ traderLlamaRidableInWater = getBoolean("mobs.trader_llama.ridable-in-water", traderLlamaRidableInWater);
-+ traderLlamaControllable = getBoolean("mobs.trader_llama.controllable", traderLlamaControllable);
-+ }
-+
-+ public boolean tropicalFishRidable = false;
-+ public boolean tropicalFishControllable = true;
-+ private void tropicalFishSettings() {
-+ tropicalFishRidable = getBoolean("mobs.tropical_fish.ridable", tropicalFishRidable);
-+ tropicalFishControllable = getBoolean("mobs.tropical_fish.controllable", tropicalFishControllable);
-+ }
-+
-+ public boolean turtleRidable = false;
-+ public boolean turtleRidableInWater = true;
-+ public boolean turtleControllable = true;
-+ private void turtleSettings() {
-+ turtleRidable = getBoolean("mobs.turtle.ridable", turtleRidable);
-+ turtleRidableInWater = getBoolean("mobs.turtle.ridable-in-water", turtleRidableInWater);
-+ turtleControllable = getBoolean("mobs.turtle.controllable", turtleControllable);
-+ }
-+
-+ public boolean vexRidable = false;
-+ public boolean vexRidableInWater = true;
-+ public boolean vexControllable = true;
-+ public double vexMaxY = 320D;
-+ private void vexSettings() {
-+ vexRidable = getBoolean("mobs.vex.ridable", vexRidable);
-+ vexRidableInWater = getBoolean("mobs.vex.ridable-in-water", vexRidableInWater);
-+ vexControllable = getBoolean("mobs.vex.controllable", vexControllable);
-+ vexMaxY = getDouble("mobs.vex.ridable-max-y", vexMaxY);
-+ }
-+
-+ public boolean villagerRidable = false;
-+ public boolean villagerRidableInWater = true;
-+ public boolean villagerControllable = true;
-+ private void villagerSettings() {
-+ villagerRidable = getBoolean("mobs.villager.ridable", villagerRidable);
-+ villagerRidableInWater = getBoolean("mobs.villager.ridable-in-water", villagerRidableInWater);
-+ villagerControllable = getBoolean("mobs.villager.controllable", villagerControllable);
-+ }
-+
-+ public boolean vindicatorRidable = false;
-+ public boolean vindicatorRidableInWater = true;
-+ public boolean vindicatorControllable = true;
-+ private void vindicatorSettings() {
-+ vindicatorRidable = getBoolean("mobs.vindicator.ridable", vindicatorRidable);
-+ vindicatorRidableInWater = getBoolean("mobs.vindicator.ridable-in-water", vindicatorRidableInWater);
-+ vindicatorControllable = getBoolean("mobs.vindicator.controllable", vindicatorControllable);
-+ }
-+
-+ public boolean wanderingTraderRidable = false;
-+ public boolean wanderingTraderRidableInWater = true;
-+ public boolean wanderingTraderControllable = true;
-+ private void wanderingTraderSettings() {
-+ wanderingTraderRidable = getBoolean("mobs.wandering_trader.ridable", wanderingTraderRidable);
-+ wanderingTraderRidableInWater = getBoolean("mobs.wandering_trader.ridable-in-water", wanderingTraderRidableInWater);
-+ wanderingTraderControllable = getBoolean("mobs.wandering_trader.controllable", wanderingTraderControllable);
-+ }
-+
-+ public boolean wardenRidable = false;
-+ public boolean wardenRidableInWater = true;
-+ public boolean wardenControllable = true;
-+ private void wardenSettings() {
-+ wardenRidable = getBoolean("mobs.warden.ridable", wardenRidable);
-+ wardenRidableInWater = getBoolean("mobs.warden.ridable-in-water", wardenRidableInWater);
-+ wardenControllable = getBoolean("mobs.warden.controllable", wardenControllable);
-+ }
-+
-+ public boolean witchRidable = false;
-+ public boolean witchRidableInWater = true;
-+ public boolean witchControllable = true;
-+ private void witchSettings() {
-+ witchRidable = getBoolean("mobs.witch.ridable", witchRidable);
-+ witchRidableInWater = getBoolean("mobs.witch.ridable-in-water", witchRidableInWater);
-+ witchControllable = getBoolean("mobs.witch.controllable", witchControllable);
-+ }
-+
-+ public boolean witherRidable = false;
-+ public boolean witherRidableInWater = true;
-+ public boolean witherControllable = true;
-+ public double witherMaxY = 320D;
-+ private void witherSettings() {
-+ witherRidable = getBoolean("mobs.wither.ridable", witherRidable);
-+ witherRidableInWater = getBoolean("mobs.wither.ridable-in-water", witherRidableInWater);
-+ witherControllable = getBoolean("mobs.wither.controllable", witherControllable);
-+ witherMaxY = getDouble("mobs.wither.ridable-max-y", witherMaxY);
-+ }
-+
-+ public boolean witherSkeletonRidable = false;
-+ public boolean witherSkeletonRidableInWater = true;
-+ public boolean witherSkeletonControllable = true;
-+ private void witherSkeletonSettings() {
-+ witherSkeletonRidable = getBoolean("mobs.wither_skeleton.ridable", witherSkeletonRidable);
-+ witherSkeletonRidableInWater = getBoolean("mobs.wither_skeleton.ridable-in-water", witherSkeletonRidableInWater);
-+ witherSkeletonControllable = getBoolean("mobs.wither_skeleton.controllable", witherSkeletonControllable);
-+ }
-+
-+ public boolean wolfRidable = false;
-+ public boolean wolfRidableInWater = true;
-+ public boolean wolfControllable = true;
-+ private void wolfSettings() {
-+ wolfRidable = getBoolean("mobs.wolf.ridable", wolfRidable);
-+ wolfRidableInWater = getBoolean("mobs.wolf.ridable-in-water", wolfRidableInWater);
-+ wolfControllable = getBoolean("mobs.wolf.controllable", wolfControllable);
-+ }
-+
-+ public boolean zoglinRidable = false;
-+ public boolean zoglinRidableInWater = true;
-+ public boolean zoglinControllable = true;
-+ private void zoglinSettings() {
-+ zoglinRidable = getBoolean("mobs.zoglin.ridable", zoglinRidable);
-+ zoglinRidableInWater = getBoolean("mobs.zoglin.ridable-in-water", zoglinRidableInWater);
-+ zoglinControllable = getBoolean("mobs.zoglin.controllable", zoglinControllable);
-+ }
-+
-+ public boolean zombieRidable = false;
-+ public boolean zombieRidableInWater = true;
-+ public boolean zombieControllable = true;
-+ private void zombieSettings() {
-+ zombieRidable = getBoolean("mobs.zombie.ridable", zombieRidable);
-+ zombieRidableInWater = getBoolean("mobs.zombie.ridable-in-water", zombieRidableInWater);
-+ zombieControllable = getBoolean("mobs.zombie.controllable", zombieControllable);
-+ }
-+
-+ public boolean zombieHorseRidable = false;
-+ public boolean zombieHorseRidableInWater = false;
-+ public boolean zombieHorseCanSwim = false;
-+ private void zombieHorseSettings() {
-+ zombieHorseRidable = getBoolean("mobs.zombie_horse.ridable", zombieHorseRidable);
-+ zombieHorseRidableInWater = getBoolean("mobs.zombie_horse.ridable-in-water", zombieHorseRidableInWater);
-+ zombieHorseCanSwim = getBoolean("mobs.zombie_horse.can-swim", zombieHorseCanSwim);
-+ }
-+
-+ public boolean zombieVillagerRidable = false;
-+ public boolean zombieVillagerRidableInWater = true;
-+ public boolean zombieVillagerControllable = true;
-+ private void zombieVillagerSettings() {
-+ zombieVillagerRidable = getBoolean("mobs.zombie_villager.ridable", zombieVillagerRidable);
-+ zombieVillagerRidableInWater = getBoolean("mobs.zombie_villager.ridable-in-water", zombieVillagerRidableInWater);
-+ zombieVillagerControllable = getBoolean("mobs.zombie_villager.controllable", zombieVillagerControllable);
-+ }
-+
-+ public boolean zombifiedPiglinRidable = false;
-+ public boolean zombifiedPiglinRidableInWater = true;
-+ public boolean zombifiedPiglinControllable = true;
-+ private void zombifiedPiglinSettings() {
-+ zombifiedPiglinRidable = getBoolean("mobs.zombified_piglin.ridable", zombifiedPiglinRidable);
-+ zombifiedPiglinRidableInWater = getBoolean("mobs.zombified_piglin.ridable-in-water", zombifiedPiglinRidableInWater);
-+ zombifiedPiglinControllable = getBoolean("mobs.zombified_piglin.controllable", zombifiedPiglinControllable);
-+ }
- }
-diff --git a/src/main/java/org/purpurmc/purpur/controller/FlyingMoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/FlyingMoveControllerWASD.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..940bcc6f79b59cb3cce578912eb789efd394f456
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/controller/FlyingMoveControllerWASD.java
-@@ -0,0 +1,74 @@
-+package org.purpurmc.purpur.controller;
-+
-+import net.minecraft.server.level.ServerPlayer;
-+import net.minecraft.world.entity.Mob;
-+import net.minecraft.world.entity.ai.attributes.Attributes;
-+import net.minecraft.world.entity.player.Input;
-+import net.minecraft.world.entity.player.Player;
-+
-+public class FlyingMoveControllerWASD extends MoveControllerWASD {
-+ protected final float groundSpeedModifier;
-+ protected final float flyingSpeedModifier;
-+ protected int tooHighCooldown = 0;
-+ protected boolean setNoGravityFlag;
-+
-+ public FlyingMoveControllerWASD(Mob entity) {
-+ this(entity, 1.0F);
-+ }
-+
-+ public FlyingMoveControllerWASD(Mob entity, float groundSpeedModifier) {
-+ this(entity, groundSpeedModifier, 1.0F, true);
-+ }
-+
-+ public FlyingMoveControllerWASD(Mob entity, float groundSpeedModifier, float flyingSpeedModifier) {
-+ this(entity, groundSpeedModifier, flyingSpeedModifier, true);
-+ }
-+
-+ public FlyingMoveControllerWASD(Mob entity, float groundSpeedModifier, float flyingSpeedModifier, boolean setNoGravityFlag) {
-+ super(entity);
-+ this.groundSpeedModifier = groundSpeedModifier;
-+ this.flyingSpeedModifier = flyingSpeedModifier;
-+ this.setNoGravityFlag = setNoGravityFlag;
-+ }
-+
-+ @Override
-+ public void purpurTick(Player rider) {
-+ Input lastClientInput = ((ServerPlayer) rider).getLastClientInput();
-+ float forward = lastClientInput.forward() == lastClientInput.backward() ? 0.0F : lastClientInput.forward() ? 1.0F : 0.0F;
-+ float vertical = forward == 0.0F ? 0.0F : -(rider.xRotO / 45.0F);
-+ float strafe = (lastClientInput.left() == lastClientInput.right() ? 0.0F : lastClientInput.left() ? 1.0F : -1.0F);
-+
-+ if (lastClientInput.jump() && spacebarEvent(entity)) {
-+ entity.onSpacebar();
-+ }
-+
-+ if (entity.getY() >= entity.getMaxY() || --tooHighCooldown > 0) {
-+ if (tooHighCooldown <= 0) {
-+ tooHighCooldown = 20;
-+ }
-+ entity.setDeltaMovement(entity.getDeltaMovement().add(0.0D, -0.05D, 0.0D));
-+ vertical = 0.0F;
-+ }
-+
-+ setSpeedModifier(entity.getAttributeValue(Attributes.MOVEMENT_SPEED));
-+ float speed = (float) getSpeedModifier();
-+
-+ if (entity.onGround) {
-+ speed *= groundSpeedModifier; // TODO = fix this!
-+ } else {
-+ speed *= flyingSpeedModifier;
-+ }
-+
-+ if (setNoGravityFlag) {
-+ entity.setNoGravity(forward > 0);
-+ }
-+
-+ entity.setSpeed(speed);
-+ entity.setVerticalMot(vertical);
-+ entity.setStrafeMot(strafe);
-+ entity.setForwardMot(forward);
-+
-+ setForward(entity.getForwardMot());
-+ setStrafe(entity.getStrafeMot());
-+ }
-+}
-diff --git a/src/main/java/org/purpurmc/purpur/controller/FlyingWithSpacebarMoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/FlyingWithSpacebarMoveControllerWASD.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..e0bbaec05afa0ae67ed486b14ea1fbadbbe90d9b
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/controller/FlyingWithSpacebarMoveControllerWASD.java
-@@ -0,0 +1,66 @@
-+package org.purpurmc.purpur.controller;
-+
-+import net.minecraft.server.level.ServerPlayer;
-+import net.minecraft.world.entity.Mob;
-+import net.minecraft.world.entity.ai.attributes.Attributes;
-+import net.minecraft.world.entity.player.Input;
-+import net.minecraft.world.entity.player.Player;
-+import net.minecraft.world.phys.Vec3;
-+
-+public class FlyingWithSpacebarMoveControllerWASD extends FlyingMoveControllerWASD {
-+ public FlyingWithSpacebarMoveControllerWASD(Mob entity) {
-+ super(entity);
-+ }
-+
-+ public FlyingWithSpacebarMoveControllerWASD(Mob entity, float groundSpeedModifier) {
-+ super(entity, groundSpeedModifier);
-+ }
-+
-+ @Override
-+ public void purpurTick(Player rider) {
-+ Input lastClientInput = ((ServerPlayer) rider).getLastClientInput();
-+ float forward = (lastClientInput.forward() == lastClientInput.backward() ? 0.0F : lastClientInput.forward() ? 1.0F : -1.0F);
-+ float strafe = (lastClientInput.left() == lastClientInput.right() ? 0.0F : lastClientInput.left() ? 1.0F : -1.0F) * 0.5F;
-+ float vertical = 0;
-+
-+ if (forward < 0.0F) {
-+ forward *= 0.5F;
-+ strafe *= 0.5F;
-+ }
-+
-+ float speed = (float) entity.getAttributeValue(Attributes.MOVEMENT_SPEED);
-+
-+ if (entity.onGround) {
-+ speed *= groundSpeedModifier;
-+ }
-+
-+ if (lastClientInput.jump() && spacebarEvent(entity) && !entity.onSpacebar()) {
-+ entity.setNoGravity(true);
-+ vertical = 1.0F;
-+ } else {
-+ entity.setNoGravity(false);
-+ }
-+
-+ if (entity.getY() >= entity.getMaxY() || --tooHighCooldown > 0) {
-+ if (tooHighCooldown <= 0) {
-+ tooHighCooldown = 20;
-+ }
-+ entity.setDeltaMovement(entity.getDeltaMovement().add(0.0D, -0.2D, 0.0D));
-+ vertical = 0.0F;
-+ }
-+
-+ setSpeedModifier(speed);
-+ entity.setSpeed((float) getSpeedModifier());
-+ entity.setVerticalMot(vertical);
-+ entity.setStrafeMot(strafe);
-+ entity.setForwardMot(forward);
-+
-+ setForward(entity.getForwardMot());
-+ setStrafe(entity.getStrafeMot());
-+
-+ Vec3 mot = entity.getDeltaMovement();
-+ if (mot.y > 0.2D) {
-+ entity.setDeltaMovement(mot.x, 0.2D, mot.z);
-+ }
-+ }
-+}
-diff --git a/src/main/java/org/purpurmc/purpur/controller/LookControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/LookControllerWASD.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..dd219518150ca90f89ad238904fd4095efe032d8
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/controller/LookControllerWASD.java
-@@ -0,0 +1,79 @@
-+package org.purpurmc.purpur.controller;
-+
-+
-+import net.minecraft.network.protocol.game.ClientboundMoveEntityPacket;
-+import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.util.Mth;
-+import net.minecraft.world.entity.Mob;
-+import net.minecraft.world.entity.ai.control.LookControl;
-+import net.minecraft.world.entity.player.Player;
-+
-+public class LookControllerWASD extends LookControl {
-+ protected final Mob entity;
-+ private float yOffset = 0;
-+ private float xOffset = 0;
-+
-+ public LookControllerWASD(Mob entity) {
-+ super(entity);
-+ this.entity = entity;
-+ }
-+
-+ // tick
-+ @Override
-+ public void tick() {
-+ if (entity.getRider() != null && entity.isControllable()) {
-+ purpurTick(entity.getRider());
-+ } else {
-+ vanillaTick();
-+ }
-+ }
-+
-+ protected void purpurTick(Player rider) {
-+ setYawPitch(rider.getYRot(), rider.getXRot());
-+ }
-+
-+ public void vanillaTick() {
-+ super.tick();
-+ }
-+
-+ public void setYawPitch(float yRot, float xRot) {
-+ entity.setXRot(normalizePitch(xRot + xOffset));
-+ entity.setYRot(normalizeYaw(yRot + yOffset));
-+ entity.setYHeadRot(entity.getYRot());
-+ entity.xRotO = entity.getXRot();
-+ entity.yRotO = entity.getYRot();
-+
-+ ClientboundMoveEntityPacket.PosRot entityPacket = new ClientboundMoveEntityPacket.PosRot(
-+ entity.getId(),
-+ (short) 0, (short) 0, (short) 0,
-+ (byte) Mth.floor(entity.getYRot() * 256.0F / 360.0F),
-+ (byte) Mth.floor(entity.getXRot() * 256.0F / 360.0F),
-+ entity.onGround
-+ );
-+ ((ServerLevel) entity.level()).getChunkSource().broadcast(entity, entityPacket);
-+ }
-+
-+ public void setOffsets(float yaw, float pitch) {
-+ yOffset = yaw;
-+ xOffset = pitch;
-+ }
-+
-+ public float normalizeYaw(float yaw) {
-+ yaw %= 360.0f;
-+ if (yaw >= 180.0f) {
-+ yaw -= 360.0f;
-+ } else if (yaw < -180.0f) {
-+ yaw += 360.0f;
-+ }
-+ return yaw;
-+ }
-+
-+ public float normalizePitch(float pitch) {
-+ if (pitch > 90.0f) {
-+ pitch = 90.0f;
-+ } else if (pitch < -90.0f) {
-+ pitch = -90.0f;
-+ }
-+ return pitch;
-+ }
-+}
-diff --git a/src/main/java/org/purpurmc/purpur/controller/MoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/MoveControllerWASD.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..34f3c43fa16e950326ac5e3d93faee0466ffedc6
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/controller/MoveControllerWASD.java
-@@ -0,0 +1,92 @@
-+package org.purpurmc.purpur.controller;
-+
-+import net.minecraft.server.level.ServerPlayer;
-+import net.minecraft.world.entity.Mob;
-+import net.minecraft.world.entity.ai.attributes.Attributes;
-+import net.minecraft.world.entity.ai.control.MoveControl;
-+import net.minecraft.world.entity.player.Input;
-+import net.minecraft.world.entity.player.Player;
-+import org.purpurmc.purpur.event.entity.RidableSpacebarEvent;
-+
-+public class MoveControllerWASD extends MoveControl {
-+ protected final Mob entity;
-+ private final double speedModifier;
-+
-+ public MoveControllerWASD(Mob entity) {
-+ this(entity, 1.0D);
-+ }
-+
-+ public MoveControllerWASD(Mob entity, double speedModifier) {
-+ super(entity);
-+ this.entity = entity;
-+ this.speedModifier = speedModifier;
-+ }
-+
-+ @Override
-+ public boolean hasWanted() {
-+ return entity.getRider() != null ? strafeForwards != 0 || strafeRight != 0 : super.hasWanted();
-+ }
-+
-+ @Override
-+ public void tick() {
-+ if (entity.getRider() != null && entity.isControllable()) {
-+ purpurTick(entity.getRider());
-+ } else {
-+ vanillaTick();
-+ }
-+ }
-+
-+ public void vanillaTick() {
-+ super.tick();
-+ }
-+
-+ public void purpurTick(Player rider) {
-+ Input lastClientInput = ((ServerPlayer) rider).getLastClientInput();
-+ float forward = (lastClientInput.forward() == lastClientInput.backward() ? 0.0F : lastClientInput.forward() ? 1.0F : -1.0F) * 0.5F;
-+ float strafe = (lastClientInput.left() == lastClientInput.right() ? 0.0F : lastClientInput.left() ? 1.0F : -1.0F) * 0.25F;
-+
-+ if (forward <= 0.0F) {
-+ forward *= 0.5F;
-+ }
-+
-+ float yawOffset = 0;
-+ if (strafe != 0) {
-+ if (forward == 0) {
-+ yawOffset += strafe > 0 ? -90 : 90;
-+ forward = Math.abs(strafe * 2);
-+ } else {
-+ yawOffset += strafe > 0 ? -30 : 30;
-+ strafe /= 2;
-+ if (forward < 0) {
-+ yawOffset += strafe > 0 ? -110 : 110;
-+ forward *= -1;
-+ }
-+ }
-+ } else if (forward < 0) {
-+ yawOffset -= 180;
-+ forward *= -1;
-+ }
-+
-+ ((LookControllerWASD) entity.getLookControl()).setOffsets(yawOffset, 0);
-+
-+ if (lastClientInput.jump() && spacebarEvent(entity) && !entity.onSpacebar() && entity.onGround) {
-+ entity.jumpFromGround();
-+ }
-+
-+ setSpeedModifier(entity.getAttributeValue(Attributes.MOVEMENT_SPEED) * speedModifier);
-+
-+ entity.setSpeed((float) getSpeedModifier());
-+ entity.setForwardMot(forward);
-+
-+ setForward(entity.getForwardMot());
-+ setStrafe(entity.getStrafeMot());
-+ }
-+
-+ public static boolean spacebarEvent(Mob entity) {
-+ if (RidableSpacebarEvent.getHandlerList().getRegisteredListeners().length > 0) {
-+ return new RidableSpacebarEvent(entity.getBukkitEntity()).callEvent();
-+ } else {
-+ return true;
-+ }
-+ }
-+}
-diff --git a/src/main/java/org/purpurmc/purpur/controller/WaterMoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/WaterMoveControllerWASD.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..922e48799c43ca322a8f550c98a26e1e2959439c
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/controller/WaterMoveControllerWASD.java
-@@ -0,0 +1,53 @@
-+package org.purpurmc.purpur.controller;
-+
-+import net.minecraft.server.level.ServerPlayer;
-+import net.minecraft.world.entity.Mob;
-+import net.minecraft.world.entity.ai.attributes.Attributes;
-+import net.minecraft.world.entity.player.Input;
-+import net.minecraft.world.entity.player.Player;
-+
-+public class WaterMoveControllerWASD extends MoveControllerWASD {
-+ private final double speedModifier;
-+
-+ public WaterMoveControllerWASD(Mob entity) {
-+ this(entity, 1.0D);
-+ }
-+
-+ public WaterMoveControllerWASD(Mob entity, double speedModifier) {
-+ super(entity);
-+ this.speedModifier = speedModifier;
-+ }
-+
-+ @Override
-+ public void purpurTick(Player rider) {
-+ Input lastClientInput = ((ServerPlayer) rider).getLastClientInput();
-+ float forward = (lastClientInput.forward() == lastClientInput.backward() ? 0.0F : lastClientInput.forward() ? 1.0F : -1.0F);
-+ float strafe = (lastClientInput.left() == lastClientInput.right() ? 0.0F : lastClientInput.left() ? 1.0F : -1.0F) * 0.5F; // strafe slower by default
-+ float vertical = -(rider.xRotO / 90);
-+
-+ if (forward == 0.0F) {
-+ // strafe slower if not moving forward
-+ strafe *= 0.5F;
-+ // do not move vertically if not moving forward
-+ vertical = 0.0F;
-+ } else if (forward < 0.0F) {
-+ // water animals can't swim backwards
-+ forward = 0.0F;
-+ vertical = 0.0F;
-+ }
-+
-+ if (rider.jumping && spacebarEvent(entity)) {
-+ entity.onSpacebar();
-+ }
-+
-+ setSpeedModifier(entity.getAttributeValue(Attributes.MOVEMENT_SPEED) * speedModifier);
-+ entity.setSpeed((float) getSpeedModifier() * 0.1F);
-+
-+ entity.setForwardMot(forward * (float) speedModifier);
-+ entity.setStrafeMot(strafe * (float) speedModifier);
-+ entity.setVerticalMot(vertical * (float) speedModifier);
-+
-+ setForward(entity.getForwardMot());
-+ setStrafe(entity.getStrafeMot());
-+ }
-+}
-diff --git a/src/main/java/org/purpurmc/purpur/entity/DolphinSpit.java b/src/main/java/org/purpurmc/purpur/entity/DolphinSpit.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..e33e54fc31ab7dcff054d0ab245d6c3391d06449
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/entity/DolphinSpit.java
-@@ -0,0 +1,101 @@
-+package org.purpurmc.purpur.entity;
-+
-+import net.minecraft.core.particles.ParticleTypes;
-+import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.util.Mth;
-+import net.minecraft.world.damagesource.DamageSource;
-+import net.minecraft.world.entity.Entity;
-+import net.minecraft.world.entity.EntityType;
-+import net.minecraft.world.entity.LivingEntity;
-+import net.minecraft.world.entity.animal.Dolphin;
-+import net.minecraft.world.entity.projectile.LlamaSpit;
-+import net.minecraft.world.entity.projectile.ProjectileUtil;
-+import net.minecraft.world.level.Level;
-+import net.minecraft.world.level.block.state.BlockState;
-+import net.minecraft.world.phys.BlockHitResult;
-+import net.minecraft.world.phys.EntityHitResult;
-+import net.minecraft.world.phys.HitResult;
-+import net.minecraft.world.phys.Vec3;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+
-+public class DolphinSpit extends LlamaSpit {
-+ public LivingEntity dolphin;
-+ public int ticksLived;
-+
-+ public DolphinSpit(EntityType extends LlamaSpit> type, Level world) {
-+ super(type, world);
-+ }
-+
-+ public DolphinSpit(Level world, Dolphin dolphin) {
-+ this(EntityType.LLAMA_SPIT, world);
-+ setOwner(dolphin.getRider() != null ? dolphin.getRider() : dolphin);
-+ this.dolphin = dolphin;
-+ this.setPos(
-+ dolphin.getX() - (double) (dolphin.getBbWidth() + 1.0F) * 0.5D * (double) Mth.sin(dolphin.yBodyRot * 0.017453292F),
-+ dolphin.getEyeY() - 0.10000000149011612D,
-+ dolphin.getZ() + (double) (dolphin.getBbWidth() + 1.0F) * 0.5D * (double) Mth.cos(dolphin.yBodyRot * 0.017453292F));
-+ }
-+
-+ public void tick() {
-+ super_tick();
-+
-+ Vec3 mot = this.getDeltaMovement();
-+ HitResult hitResult = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity);
-+
-+ this.preHitTargetOrDeflectSelf(hitResult);
-+
-+ double x = this.getX() + mot.x;
-+ double y = this.getY() + mot.y;
-+ double z = this.getZ() + mot.z;
-+
-+ this.updateRotation();
-+
-+ Vec3 motDouble = mot.scale(2.0);
-+ for (int i = 0; i < 5; i++) {
-+ ((ServerLevel) level()).sendParticlesSource(null, ParticleTypes.BUBBLE,
-+ false, true,
-+ getX() + random.nextFloat() / 2 - 0.25F,
-+ getY() + random.nextFloat() / 2 - 0.25F,
-+ getZ() + random.nextFloat() / 2 - 0.25F,
-+ 0, motDouble.x(), motDouble.y(), motDouble.z(), 0.1D);
-+ }
-+
-+ if (++ticksLived > 20) {
-+ this.discard(EntityRemoveEvent.Cause.DISCARD);
-+ } else {
-+ this.setDeltaMovement(mot.scale(0.99D));
-+ if (!this.isNoGravity()) {
-+ this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -0.06D, 0.0D));
-+ }
-+
-+ this.setPos(x, y, z);
-+ }
-+ }
-+
-+ @Override
-+ public void shoot(double x, double y, double z, float speed, float inaccuracy) {
-+ setDeltaMovement(new Vec3(x, y, z).normalize().add(
-+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy,
-+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy,
-+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy)
-+ .scale(speed));
-+ }
-+
-+ @Override
-+ protected void onHitEntity(EntityHitResult entityHitResult) {
-+ Entity shooter = this.getOwner();
-+ if (shooter instanceof LivingEntity) {
-+ entityHitResult.getEntity().hurt(entityHitResult.getEntity().damageSources().mobProjectile(this, (LivingEntity) shooter), level().purpurConfig.dolphinSpitDamage);
-+ }
-+ }
-+
-+ @Override
-+ protected void onHitBlock(BlockHitResult blockHitResult) {
-+ if (this.hitCancelled) {
-+ return;
-+ }
-+ BlockState state = this.level().getBlockState(blockHitResult.getBlockPos());
-+ state.onProjectileHit(this.level(), state, blockHitResult, this);
-+ this.discard(EntityRemoveEvent.Cause.DISCARD);
-+ }
-+}
-diff --git a/src/main/java/org/purpurmc/purpur/entity/PhantomFlames.java b/src/main/java/org/purpurmc/purpur/entity/PhantomFlames.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..3759e45afe16bf1d8a37b78d3526ee446e63cfe5
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/entity/PhantomFlames.java
-@@ -0,0 +1,123 @@
-+package org.purpurmc.purpur.entity;
-+
-+import net.minecraft.core.particles.ParticleTypes;
-+import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.util.Mth;
-+import net.minecraft.world.damagesource.DamageSource;
-+import net.minecraft.world.entity.Entity;
-+import net.minecraft.world.entity.EntityType;
-+import net.minecraft.world.entity.LivingEntity;
-+import net.minecraft.world.entity.decoration.ArmorStand;
-+import net.minecraft.world.entity.monster.Phantom;
-+import net.minecraft.world.entity.projectile.LlamaSpit;
-+import net.minecraft.world.entity.projectile.ProjectileUtil;
-+import net.minecraft.world.level.Level;
-+import net.minecraft.world.level.block.state.BlockBehaviour;
-+import net.minecraft.world.level.block.state.BlockState;
-+import net.minecraft.world.phys.BlockHitResult;
-+import net.minecraft.world.phys.EntityHitResult;
-+import net.minecraft.world.phys.HitResult;
-+import net.minecraft.world.phys.Vec3;
-+
-+public class PhantomFlames extends LlamaSpit {
-+ public Phantom phantom;
-+ public int ticksLived;
-+ public boolean canGrief = false;
-+
-+ public PhantomFlames(EntityType extends LlamaSpit> type, Level world) {
-+ super(type, world);
-+ }
-+
-+ public PhantomFlames(Level world, Phantom phantom) {
-+ this(EntityType.LLAMA_SPIT, world);
-+ setOwner(phantom.getRider() != null ? phantom.getRider() : phantom);
-+ this.phantom = phantom;
-+ this.setPos(
-+ phantom.getX() - (double) (phantom.getBbWidth() + 1.0F) * 0.5D * (double) Mth.sin(phantom.yBodyRot * 0.017453292F),
-+ phantom.getEyeY() - 0.10000000149011612D,
-+ phantom.getZ() + (double) (phantom.getBbWidth() + 1.0F) * 0.5D * (double) Mth.cos(phantom.yBodyRot * 0.017453292F));
-+ }
-+
-+ public void tick() {
-+ super_tick();
-+
-+ Vec3 mot = this.getDeltaMovement();
-+ HitResult hitResult = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity);
-+
-+ this.preHitTargetOrDeflectSelf(hitResult);
-+
-+ double x = this.getX() + mot.x;
-+ double y = this.getY() + mot.y;
-+ double z = this.getZ() + mot.z;
-+
-+ this.updateRotation();
-+
-+ Vec3 motDouble = mot.scale(2.0);
-+ for (int i = 0; i < 5; i++) {
-+ ((ServerLevel) level()).sendParticlesSource(null, ParticleTypes.FLAME,
-+ false, true,
-+ getX() + random.nextFloat() / 2 - 0.25F,
-+ getY() + random.nextFloat() / 2 - 0.25F,
-+ getZ() + random.nextFloat() / 2 - 0.25F,
-+ 0, motDouble.x(), motDouble.y(), motDouble.z(), 0.1D);
-+ }
-+
-+ if (++ticksLived > 20) {
-+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD);
-+ } else if (this.level().getBlockStates(this.getBoundingBox()).noneMatch(BlockBehaviour.BlockStateBase::isAir)) {
-+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD);
-+ } else if (this.isInWaterOrBubble()) {
-+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD);
-+ } else {
-+ this.setDeltaMovement(mot.scale(0.99D));
-+ if (!this.isNoGravity()) {
-+ this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -0.06D, 0.0D));
-+ }
-+
-+ this.setPos(x, y, z);
-+ }
-+ }
-+
-+ @Override
-+ public void shoot(double x, double y, double z, float speed, float inaccuracy) {
-+ setDeltaMovement(new Vec3(x, y, z).normalize().add(
-+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy,
-+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy,
-+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy)
-+ .scale(speed));
-+ }
-+
-+ @Override
-+ protected void onHitEntity(EntityHitResult entityHitResult) {
-+ Level world = this.level();
-+
-+ if (world instanceof ServerLevel worldserver) {
-+ Entity shooter = this.getOwner();
-+ if (shooter instanceof LivingEntity) {
-+ Entity target = entityHitResult.getEntity();
-+ if (canGrief || (target instanceof LivingEntity && !(target instanceof ArmorStand))) {
-+ boolean hurt = target.hurtServer(worldserver, target.damageSources().mobProjectile(this, (LivingEntity) shooter), worldserver.purpurConfig.phantomFlameDamage);
-+ if (hurt && worldserver.purpurConfig.phantomFlameFireTime > 0) {
-+ target.igniteForSeconds(worldserver.purpurConfig.phantomFlameFireTime);
-+ }
-+ }
-+ }
-+ }
-+ }
-+
-+ @Override
-+ protected void onHitBlock(BlockHitResult blockHitResult) {
-+ Level world = this.level();
-+
-+ if (world instanceof ServerLevel worldserver) {
-+ if (this.hitCancelled) {
-+ return;
-+ }
-+ if (this.canGrief) {
-+ BlockState state = worldserver.getBlockState(blockHitResult.getBlockPos());
-+ state.onProjectileHit(worldserver, state, blockHitResult, this);
-+ }
-+ this.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.DISCARD);
-+ }
-+ }
-+}
-diff --git a/src/main/java/org/purpurmc/purpur/entity/ai/HasRider.java b/src/main/java/org/purpurmc/purpur/entity/ai/HasRider.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..8babdaddd8b33278aea0369dbbeeb445abe45016
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/entity/ai/HasRider.java
-@@ -0,0 +1,20 @@
-+package org.purpurmc.purpur.entity.ai;
-+
-+import net.minecraft.world.entity.Mob;
-+import net.minecraft.world.entity.ai.goal.Goal;
-+
-+import java.util.EnumSet;
-+
-+public class HasRider extends Goal {
-+ public final Mob entity;
-+
-+ public HasRider(Mob entity) {
-+ this.entity = entity;
-+ setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK, Flag.TARGET, Flag.UNKNOWN_BEHAVIOR));
-+ }
-+
-+ @Override
-+ public boolean canUse() {
-+ return entity.getRider() != null && entity.isControllable();
-+ }
-+}
-diff --git a/src/main/java/org/purpurmc/purpur/entity/ai/HorseHasRider.java b/src/main/java/org/purpurmc/purpur/entity/ai/HorseHasRider.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..432f4f3d82af2f19820890b68d33189a9f2c69f9
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/entity/ai/HorseHasRider.java
-@@ -0,0 +1,17 @@
-+package org.purpurmc.purpur.entity.ai;
-+
-+import net.minecraft.world.entity.animal.horse.AbstractHorse;
-+
-+public class HorseHasRider extends HasRider {
-+ public final AbstractHorse horse;
-+
-+ public HorseHasRider(AbstractHorse entity) {
-+ super(entity);
-+ this.horse = entity;
-+ }
-+
-+ @Override
-+ public boolean canUse() {
-+ return super.canUse() && horse.isSaddled();
-+ }
-+}
-diff --git a/src/main/java/org/purpurmc/purpur/entity/ai/LlamaHasRider.java b/src/main/java/org/purpurmc/purpur/entity/ai/LlamaHasRider.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..18a95e043cbffa65eeaaf65ff7695e5dc939820c
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/entity/ai/LlamaHasRider.java
-@@ -0,0 +1,17 @@
-+package org.purpurmc.purpur.entity.ai;
-+
-+import net.minecraft.world.entity.animal.horse.Llama;
-+
-+public class LlamaHasRider extends HasRider {
-+ public final Llama llama;
-+
-+ public LlamaHasRider(Llama entity) {
-+ super(entity);
-+ this.llama = entity;
-+ }
-+
-+ @Override
-+ public boolean canUse() {
-+ return super.canUse() && llama.isSaddled() && llama.isControllable();
-+ }
-+}
diff --git a/patches/server/0008-Configurable-entity-base-attributes.patch b/patches/server/0008-Configurable-entity-base-attributes.patch
deleted file mode 100644
index f3e6124c05..0000000000
--- a/patches/server/0008-Configurable-entity-base-attributes.patch
+++ /dev/null
@@ -1,3253 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: William Blake Galbreath
-Date: Thu, 10 Dec 2020 16:44:54 -0600
-Subject: [PATCH] Configurable entity base attributes
-
-
-diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
-index 5c14180d92e1baebe59b08311746418e7d9f6a24..755861185bd8434027acca7f03ed0bfdf9fa2cde 100644
---- a/src/main/java/net/minecraft/world/entity/Entity.java
-+++ b/src/main/java/net/minecraft/world/entity/Entity.java
-@@ -177,7 +177,7 @@ import org.bukkit.plugin.PluginManager;
- // CraftBukkit end
-
- public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess, ScoreHolder, ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity, ca.spottedleaf.moonrise.patches.entity_tracker.EntityTrackerEntity { // Paper - rewrite chunk system // Paper - optimise entity tracker
--
-+ public static javax.script.ScriptEngine scriptEngine = new javax.script.ScriptEngineManager().getEngineByName("rhino"); // Purpur - Configurable entity base attributes
- // CraftBukkit start
- private static final int CURRENT_LEVEL = 2;
- public boolean preserveMotion = true; // Paper - Fix Entity Teleportation and cancel velocity if teleported; keep initial motion on first setPositionRotation
-diff --git a/src/main/java/net/minecraft/world/entity/GlowSquid.java b/src/main/java/net/minecraft/world/entity/GlowSquid.java
-index 293ffe990de70f4f8872f063388a3a50c60b68e6..66a5c485ed2d29d0079ae714c2dd7b01aab11d86 100644
---- a/src/main/java/net/minecraft/world/entity/GlowSquid.java
-+++ b/src/main/java/net/minecraft/world/entity/GlowSquid.java
-@@ -38,6 +38,12 @@ public class GlowSquid extends Squid {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.glowSquidMaxHealth);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- protected ParticleOptions getInkParticle() {
- return ParticleTypes.GLOW_SQUID_INK;
-diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
-index 715b76bd0ccc0c29583a55f82a8ecd889ab49b56..6884475a5217acb8073dc38e5f7a7b341c8b1dd1 100644
---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
-+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
-@@ -324,6 +324,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
- this.activeLocationDependentEnchantments = new EnumMap(EquipmentSlot.class);
- this.appliedScale = 1.0F;
- this.attributes = new AttributeMap(DefaultAttributes.getSupplier(type), this); // Purpur - Ridables
-+ this.initAttributes(); // Purpur - Configurable entity base attributes
- this.craftAttributes = new CraftAttributeMap(this.attributes); // CraftBukkit
- // CraftBukkit - setHealth(getMaxHealth()) inlined and simplified to skip the instanceof check for EntityPlayer, as getBukkitEntity() is not initialized in constructor
- this.entityData.set(LivingEntity.DATA_HEALTH_ID, (float) this.getAttribute(Attributes.MAX_HEALTH).getValue());
-@@ -338,6 +339,8 @@ public abstract class LivingEntity extends Entity implements Attackable {
- this.brain = this.makeBrain(new Dynamic(dynamicopsnbt, (Tag) dynamicopsnbt.createMap((Map) ImmutableMap.of(dynamicopsnbt.createString("memories"), (Tag) dynamicopsnbt.emptyMap()))));
- }
-
-+ protected void initAttributes() {}// Purpur - Configurable entity base attributes
-+
- public Brain> getBrain() {
- return this.brain;
- }
-diff --git a/src/main/java/net/minecraft/world/entity/ambient/Bat.java b/src/main/java/net/minecraft/world/entity/ambient/Bat.java
-index add1c146cd7428547d9ef8810841b4cf39a6a05e..5f11c687df87015261d1d39c957e241fbeb5476a 100644
---- a/src/main/java/net/minecraft/world/entity/ambient/Bat.java
-+++ b/src/main/java/net/minecraft/world/entity/ambient/Bat.java
-@@ -100,6 +100,20 @@ public class Bat extends AmbientCreature {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.batMaxHealth);
-+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.batScale);
-+ this.getAttribute(Attributes.FOLLOW_RANGE).setBaseValue(this.level().purpurConfig.batFollowRange);
-+ this.getAttribute(Attributes.KNOCKBACK_RESISTANCE).setBaseValue(this.level().purpurConfig.batKnockbackResistance);
-+ this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.level().purpurConfig.batMovementSpeed);
-+ this.getAttribute(Attributes.FLYING_SPEED).setBaseValue(this.level().purpurConfig.batFlyingSpeed);
-+ this.getAttribute(Attributes.ARMOR).setBaseValue(this.level().purpurConfig.batArmor);
-+ this.getAttribute(Attributes.ARMOR_TOUGHNESS).setBaseValue(this.level().purpurConfig.batArmorToughness);
-+ this.getAttribute(Attributes.ATTACK_KNOCKBACK).setBaseValue(this.level().purpurConfig.batAttackKnockback);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- public boolean isFlapping() {
- return !this.isResting() && (float) this.tickCount % 10.0F == 0.0F;
-diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java
-index 13f6e4c83e1775daadb13e3532d7dfe6eef15aac..8751cc882f2dcbf6dfc10cebab9d9a4f95ebfb10 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Bee.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java
-@@ -489,6 +489,13 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
- return tileentitybeehive != null && tileentitybeehive.isFireNearby();
- }
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.beeMaxHealth);
-+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.beeScale);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- public int getRemainingPersistentAngerTime() {
- return (Integer) this.entityData.get(Bee.DATA_REMAINING_ANGER_TIME);
-diff --git a/src/main/java/net/minecraft/world/entity/animal/Cat.java b/src/main/java/net/minecraft/world/entity/animal/Cat.java
-index 472bbf4c3f932e2b1c7d7fa3c74b41f5be11431f..b4f022093a52c1fe13ad67ad70d57fd0278a9d55 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Cat.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Cat.java
-@@ -125,6 +125,13 @@ public class Cat extends TamableAnimal implements VariantHolder {
-diff --git a/src/main/java/net/minecraft/world/entity/animal/Chicken.java b/src/main/java/net/minecraft/world/entity/animal/Chicken.java
-index 5eaa1d42ec72ff077d974db3284cae32a9809da7..2f1518536e63dfd94db5c8a2076004319408409c 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Chicken.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Chicken.java
-@@ -70,6 +70,13 @@ public class Chicken extends Animal {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.chickenMaxHealth);
-+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.chickenScale);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- protected void registerGoals() {
- this.goalSelector.addGoal(0, new FloatGoal(this));
-diff --git a/src/main/java/net/minecraft/world/entity/animal/Cod.java b/src/main/java/net/minecraft/world/entity/animal/Cod.java
-index fcf7073dd2d79f1483bdc6e7fdc37c8c260ae418..dad2dc77afead53e0fa7f2f797ac3850279d5d40 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Cod.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Cod.java
-@@ -25,6 +25,12 @@ public class Cod extends AbstractSchoolingFish {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.codMaxHealth);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- public ItemStack getBucketItemStack() {
- return new ItemStack(Items.COD_BUCKET);
-diff --git a/src/main/java/net/minecraft/world/entity/animal/Cow.java b/src/main/java/net/minecraft/world/entity/animal/Cow.java
-index dc7ccfe90a82892d65098a325fd71fbbc734da86..064188a7032170ed16cf3b538efc444e54325036 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Cow.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Cow.java
-@@ -61,6 +61,13 @@ public class Cow extends Animal {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.cowMaxHealth);
-+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.cowScale);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- protected void registerGoals() {
- this.goalSelector.addGoal(0, new FloatGoal(this));
-diff --git a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java
-index af677b6581514a07e6455977ffc591538d43bbc6..2ee7de39712d67b593ff287a9ed17c28fa768b3c 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java
-@@ -161,6 +161,13 @@ public class Dolphin extends AgeableWaterCreature {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.dolphinMaxHealth);
-+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.dolphinScale);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Nullable
- @Override
- public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, EntitySpawnReason spawnReason, @Nullable SpawnGroupData entityData) {
-diff --git a/src/main/java/net/minecraft/world/entity/animal/Fox.java b/src/main/java/net/minecraft/world/entity/animal/Fox.java
-index ce5ac300582f61d0f3eeb1e94340cfefbdff1ba9..584d08bca961f8b8487b844cd2412b773401296d 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Fox.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java
-@@ -182,6 +182,13 @@ public class Fox extends Animal implements VariantHolder {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.foxMaxHealth);
-+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.foxScale);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- protected void defineSynchedData(SynchedEntityData.Builder builder) {
- super.defineSynchedData(builder);
-diff --git a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java
-index 938a0c6f7cfbb6cd459d5a2ec46f912d45fd2226..124b7d6881964039829313c52427e332e1ac526b 100644
---- a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java
-@@ -79,6 +79,13 @@ public class IronGolem extends AbstractGolem implements NeutralMob {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.ironGolemMaxHealth);
-+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.ironGolemScale);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- protected void registerGoals() {
- if (level().purpurConfig.ironGolemCanSwim) this.goalSelector.addGoal(0, new net.minecraft.world.entity.ai.goal.FloatGoal(this)); // Purpur - Ridables
-diff --git a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java
-index f37c8efa34efcb289bbeed06ea2d3860ff2662ac..215ec83077a1d40fdcee5bf4cd1af0d46fdd695e 100644
---- a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java
-@@ -81,6 +81,12 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.rabbitMaxHealth);
-+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.rabbitScale);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- public void registerGoals() {
- this.goalSelector.addGoal(1, new FloatGoal(this));
-diff --git a/src/main/java/net/minecraft/world/entity/animal/Salmon.java b/src/main/java/net/minecraft/world/entity/animal/Salmon.java
-index 9bc58ca4556baf6f6bc494ae249c11c5c627f86c..88f0f9074db9a9afff55aa6bd17c38fa2e1e1f81 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Salmon.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Salmon.java
-@@ -47,6 +47,12 @@ public class Salmon extends AbstractSchoolingFish implements VariantHolder brainProvider() {
- return Brain.provider(Allay.MEMORY_TYPES, Allay.SENSOR_TYPES);
-diff --git a/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java b/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java
-index 4c9771725f9567790841094dae72c2bbf0d5ba62..1a6c88558a11066ec2a78d40e6a1b0f2fa546b88 100644
---- a/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/armadillo/Armadillo.java
-@@ -99,6 +99,13 @@ public class Armadillo extends Animal {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.armadilloMaxHealth);
-+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.armadilloScale);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- protected void defineSynchedData(SynchedEntityData.Builder builder) {
- super.defineSynchedData(builder);
-diff --git a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java
-index b414edf515890066bd970f65c073964839851840..f95b9be60c3725fe279a07300f706abb25e152f1 100644
---- a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java
-@@ -117,6 +117,13 @@ public class Axolotl extends Animal implements VariantHolder, B
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.axolotlMaxHealth);
-+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.axolotlScale);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- public float getWalkTargetValue(BlockPos pos, LevelReader world) {
- return 0.0F;
-diff --git a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java
-index f794ac7af227d413ed030457cbe4cd68e6eca969..ab0b90c300d0610e423abe7ac9e5b93305a21c5a 100644
---- a/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/camel/Camel.java
-@@ -321,6 +321,22 @@ public class Camel extends AbstractHorse {
- return this.dashCooldown;
- }
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public float generateMaxHealth(net.minecraft.util.RandomSource random) {
-+ return (float) generateMaxHealth(this.level().purpurConfig.camelMaxHealthMin, this.level().purpurConfig.camelMaxHealthMax);
-+ }
-+
-+ @Override
-+ public double generateJumpStrength(net.minecraft.util.RandomSource random) {
-+ return generateJumpStrength(this.level().purpurConfig.camelJumpStrengthMin, this.level().purpurConfig.camelJumpStrengthMax);
-+ }
-+
-+ @Override
-+ public double generateSpeed(net.minecraft.util.RandomSource random) {
-+ return generateSpeed(this.level().purpurConfig.camelMovementSpeedMin, this.level().purpurConfig.camelMovementSpeedMax);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- protected SoundEvent getAmbientSound() {
- return SoundEvents.CAMEL_AMBIENT;
-diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java
-index d9e4eb76209abffd0079ccdbbd2fc3f29bd67052..f58a0f50d04004587d342c1bb5f681cd485cf302 100644
---- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java
-@@ -240,6 +240,45 @@ public abstract class AbstractHorse extends Animal implements ContainerListener,
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.generateMaxHealth(random));
-+ this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.generateSpeed(random));
-+ this.getAttribute(Attributes.JUMP_STRENGTH).setBaseValue(this.generateJumpStrength(random));
-+ }
-+
-+ protected double generateMaxHealth(double min, double max) {
-+ if (min == max) return min;
-+ int diff = Mth.floor(max - min);
-+ double base = max - diff;
-+ int first = Mth.floor((double) diff / 2);
-+ int rest = diff - first;
-+ return base + random.nextInt(first + 1) + random.nextInt(rest + 1);
-+ }
-+
-+ protected double generateJumpStrength(double min, double max) {
-+ if (min == max) return min;
-+ return min + (max - min) * this.random.nextDouble();
-+ }
-+
-+ protected double generateSpeed(double min, double max) {
-+ if (min == max) return min;
-+ return min + (max - min) * this.random.nextDouble();
-+ }
-+
-+ protected float generateMaxHealth(RandomSource random) {
-+ return 15.0F + (float) random.nextInt(8) + (float) random.nextInt(9);
-+ }
-+
-+ protected double generateJumpStrength(RandomSource random) {
-+ return 0.4000000059604645D + random.nextDouble() * 0.2D + random.nextDouble() * 0.2D + random.nextDouble() * 0.2D;
-+ }
-+
-+ protected double generateSpeed(RandomSource random) {
-+ return (0.44999998807907104D + random.nextDouble() * 0.3D + random.nextDouble() * 0.3D + random.nextDouble() * 0.3D) * 0.25D;
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- protected void registerGoals() {
- this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HorseHasRider(this)); // Purpur - Ridables
-@@ -1280,7 +1319,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener,
- entityData = new AgeableMob.AgeableMobGroupData(0.2F);
- }
-
-- this.randomizeAttributes(world.getRandom());
-+ //this.randomizeAttributes(world.getRandom()); // Purpur - replaced by initAttributes()
- return super.finalizeSpawn(world, difficulty, spawnReason, (SpawnGroupData) entityData);
- }
-
-diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java b/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java
-index f4ef46c6cf40993b878ee965a0af397894231ba6..8fd709bcd4c7a5a875bdc65fd4dd1420ea618e3a 100644
---- a/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/horse/Donkey.java
-@@ -23,6 +23,22 @@ public class Donkey extends AbstractChestedHorse {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public float generateMaxHealth(net.minecraft.util.RandomSource random) {
-+ return (float) generateMaxHealth(this.level().purpurConfig.donkeyMaxHealthMin, this.level().purpurConfig.donkeyMaxHealthMax);
-+ }
-+
-+ @Override
-+ public double generateJumpStrength(net.minecraft.util.RandomSource random) {
-+ return generateJumpStrength(this.level().purpurConfig.donkeyJumpStrengthMin, this.level().purpurConfig.donkeyJumpStrengthMax);
-+ }
-+
-+ @Override
-+ public double generateSpeed(net.minecraft.util.RandomSource random) {
-+ return generateSpeed(this.level().purpurConfig.donkeyMovementSpeedMin, this.level().purpurConfig.donkeyMovementSpeedMax);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- protected SoundEvent getAmbientSound() {
- return SoundEvents.DONKEY_AMBIENT;
-diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java b/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java
-index 4505bcc6ab70ee2bc969ecfaecf7cff072f48ca1..13056a0a13eeb3dcc164344b973e6ff656c0793d 100644
---- a/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/horse/Horse.java
-@@ -50,6 +50,22 @@ public class Horse extends AbstractHorse implements VariantHolder {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public float generateMaxHealth(RandomSource random) {
-+ return (float) generateMaxHealth(this.level().purpurConfig.horseMaxHealthMin, this.level().purpurConfig.horseMaxHealthMax);
-+ }
-+
-+ @Override
-+ public double generateJumpStrength(RandomSource random) {
-+ return generateJumpStrength(this.level().purpurConfig.horseJumpStrengthMin, this.level().purpurConfig.horseJumpStrengthMax);
-+ }
-+
-+ @Override
-+ public double generateSpeed(RandomSource random) {
-+ return generateSpeed(this.level().purpurConfig.horseMovementSpeedMin, this.level().purpurConfig.horseMovementSpeedMax);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- protected void randomizeAttributes(RandomSource random) {
- this.getAttribute(Attributes.MAX_HEALTH).setBaseValue((double)generateMaxHealth(random::nextInt));
-diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java
-index 0e15cb99cab5ed664dc265f3754b9da7fef8958f..83fdbf55384a5c4429d65a88fcb788e449a8862a 100644
---- a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java
-@@ -123,6 +123,22 @@ public class Llama extends AbstractChestedHorse implements VariantHolder type, ServerLevelAccessor world, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random) {
- return checkMonsterSpawnRules(type, world, spawnReason, pos, random) && (EntitySpawnReason.isSpawner(spawnReason) || world.canSeeSky(pos));
- }
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Illusioner.java b/src/main/java/net/minecraft/world/entity/monster/Illusioner.java
-index 8ff616fe05071bd2a197a465c7e17f35a3e72d44..6e3362324c81afacaaa0f9766e2d23a0a4f53ac3 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Illusioner.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Illusioner.java
-@@ -76,6 +76,15 @@ public class Illusioner extends SpellcasterIllager implements RangedAttackMob {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ protected void initAttributes() {
-+ this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.level().purpurConfig.illusionerMovementSpeed);
-+ this.getAttribute(Attributes.FOLLOW_RANGE).setBaseValue(this.level().purpurConfig.illusionerFollowRange);
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.illusionerMaxHealth);
-+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.illusionerScale);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- protected void registerGoals() {
- super.registerGoals();
-diff --git a/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java b/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java
-index 4f7d99fadfa1ba31439ec02bfb107288a722e828..5ba64f9c653345b742624f18c4cf692d1e7880e1 100644
---- a/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java
-@@ -46,6 +46,27 @@ public class MagmaCube extends Slime {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ protected String getMaxHealthEquation() {
-+ return level().purpurConfig.magmaCubeMaxHealth;
-+ }
-+
-+ @Override
-+ protected String getAttackDamageEquation() {
-+ return level().purpurConfig.magmaCubeAttackDamage;
-+ }
-+
-+ @Override
-+ protected java.util.Map getMaxHealthCache() {
-+ return level().purpurConfig.magmaCubeMaxHealthCache;
-+ }
-+
-+ @Override
-+ protected java.util.Map getAttackDamageCache() {
-+ return level().purpurConfig.magmaCubeAttackDamageCache;
-+ }
-+ // Purpur end - Configurable entity base attributes
- public static AttributeSupplier.Builder createAttributes() {
- return Monster.createMonsterAttributes().add(Attributes.MOVEMENT_SPEED, 0.2F);
- }
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Phantom.java b/src/main/java/net/minecraft/world/entity/monster/Phantom.java
-index afd00fd83e6c246afecf7042435ae119057b9e93..ee00c4c7a1e0f08cdeccab63a20c4b465fdeb549 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Phantom.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Phantom.java
-@@ -150,7 +150,10 @@ public class Phantom extends FlyingMob implements Enemy {
-
- private void updatePhantomSizeInfo() {
- this.refreshDimensions();
-- this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue((double) (6 + this.getPhantomSize()));
-+ // Purpur start - Configurable entity base attributes
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(getFromCache(() -> this.level().purpurConfig.phantomMaxHealth, () -> this.level().purpurConfig.phantomMaxHealthCache, () -> 20.0D));
-+ this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(getFromCache(() -> this.level().purpurConfig.phantomAttackDamage, () -> this.level().purpurConfig.phantomAttackDamageCache, () -> (double) 6 + this.getPhantomSize()));
-+ // Purpur end - Configurable entity base attributes
- }
-
- public int getPhantomSize() {
-@@ -175,6 +178,22 @@ public class Phantom extends FlyingMob implements Enemy {
- return true;
- }
-
-+ // Purpur start - Configurable entity base attributes
-+ private double getFromCache(java.util.function.Supplier equation, java.util.function.Supplier> cache, java.util.function.Supplier defaultValue) {
-+ int size = getPhantomSize();
-+ Double value = cache.get().get(size);
-+ if (value == null) {
-+ try {
-+ value = ((Number) scriptEngine.eval("let size = " + size + "; " + equation.get())).doubleValue();
-+ } catch (javax.script.ScriptException e) {
-+ e.printStackTrace();
-+ value = defaultValue.get();
-+ }
-+ cache.get().put(size, value);
-+ }
-+ return value;
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- public void tick() {
- super.tick();
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Pillager.java b/src/main/java/net/minecraft/world/entity/monster/Pillager.java
-index a629e31e6ea0f49f88746856382fcec96918c490..78c01f0be0cc6689f68e2fcf3111d79abb5a59fb 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Pillager.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Pillager.java
-@@ -84,6 +84,13 @@ public class Pillager extends AbstractIllager implements CrossbowAttackMob, Inve
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.pillagerMaxHealth);
-+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.pillagerScale);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- protected void registerGoals() {
- super.registerGoals();
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Ravager.java b/src/main/java/net/minecraft/world/entity/monster/Ravager.java
-index 6222ed8408096e0bb8e9572c07c0db6971fc8308..ecf37a99fa5e8919146ba73c7313998855ea0d88 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Ravager.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Ravager.java
-@@ -98,6 +98,13 @@ public class Ravager extends Raider {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.ravagerMaxHealth);
-+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.ravagerScale);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- protected void registerGoals() {
- super.registerGoals();
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Shulker.java b/src/main/java/net/minecraft/world/entity/monster/Shulker.java
-index 6541e1059ca16cfd01bf01aae2c56400cbe78132..c04d6a5190f3db06601c3a0ab0ddaede3a9f88ac 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Shulker.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Shulker.java
-@@ -84,7 +84,7 @@ public class Shulker extends AbstractGolem implements VariantHolder public - Configurable entity base attributes
- private float currentPeekAmountO;
- private float currentPeekAmount;
- @Nullable
-@@ -115,6 +115,12 @@ public class Shulker extends AbstractGolem implements VariantHolder variant) {
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Silverfish.java b/src/main/java/net/minecraft/world/entity/monster/Silverfish.java
-index 6f1d59651552ef7443eb8274765614d5abc984df..191724aa48081017adf3db0b6ff99a77dd4ce68d 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Silverfish.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Silverfish.java
-@@ -61,6 +61,15 @@ public class Silverfish extends Monster {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.silverfishMaxHealth);
-+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.silverfishScale);
-+ this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.level().purpurConfig.silverfishMovementSpeed);
-+ this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(this.level().purpurConfig.silverfishAttackDamage);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- protected void registerGoals() {
- this.friendsGoal = new Silverfish.SilverfishWakeUpFriendsGoal(this);
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java
-index 78c8483da3c0ac7f93e236dd723fc6051427a50e..8e93da5112cbb14af1ca755c40698740303717ee 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java
-@@ -46,6 +46,12 @@ public class Skeleton extends AbstractSkeleton {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.skeletonMaxHealth);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- protected void defineSynchedData(SynchedEntityData.Builder builder) {
- super.defineSynchedData(builder);
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Slime.java b/src/main/java/net/minecraft/world/entity/monster/Slime.java
-index b7941230b082d4de9ab77c981bd396fa1184d78e..d2cdb2119cd6e0afd57e00b1b706d36342b8858d 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Slime.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java
-@@ -107,6 +107,38 @@ public class Slime extends Mob implements Enemy {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ protected String getMaxHealthEquation() {
-+ return level().purpurConfig.slimeMaxHealth;
-+ }
-+
-+ protected String getAttackDamageEquation() {
-+ return level().purpurConfig.slimeAttackDamage;
-+ }
-+
-+ protected java.util.Map getMaxHealthCache() {
-+ return level().purpurConfig.slimeMaxHealthCache;
-+ }
-+
-+ protected java.util.Map getAttackDamageCache() {
-+ return level().purpurConfig.slimeAttackDamageCache;
-+ }
-+
-+ protected double getFromCache(java.util.function.Supplier equation, java.util.function.Supplier> cache, java.util.function.Supplier defaultValue) {
-+ int size = getSize();
-+ Double value = cache.get().get(size);
-+ if (value == null) {
-+ try {
-+ value = ((Number) scriptEngine.eval("let size = " + size + "; " + equation.get())).doubleValue();
-+ } catch (javax.script.ScriptException e) {
-+ e.printStackTrace();
-+ value = defaultValue.get();
-+ }
-+ cache.get().put(size, value);
-+ }
-+ return value;
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- protected void registerGoals() {
- this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
-@@ -139,9 +171,9 @@ public class Slime extends Mob implements Enemy {
- this.entityData.set(Slime.ID_SIZE, j);
- this.reapplyPosition();
- this.refreshDimensions();
-- this.getAttribute(Attributes.MAX_HEALTH).setBaseValue((double) (j * j));
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(getFromCache(this::getMaxHealthEquation, this::getMaxHealthCache, () -> (double) size * size)); // Purpur - Configurable entity base attributes
- this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue((double) (0.2F + 0.1F * (float) j));
-- this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue((double) j);
-+ this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(getFromCache(this::getAttackDamageEquation, this::getAttackDamageCache, () -> (double) j)); // Purpur - Configurable entity base attributes
- if (heal) {
- this.setHealth(this.getMaxHealth());
- }
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Spider.java b/src/main/java/net/minecraft/world/entity/monster/Spider.java
-index d3eabdde9e9bf010cae7fc81165f0123adfcf958..26ce5425d8217f8954ceef898b806d3b56c01027 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Spider.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Spider.java
-@@ -68,6 +68,13 @@ public class Spider extends Monster {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.spiderMaxHealth);
-+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.spiderScale);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- protected void registerGoals() {
- this.goalSelector.addGoal(1, new FloatGoal(this));
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Stray.java b/src/main/java/net/minecraft/world/entity/monster/Stray.java
-index 879748708e3fc8c0a3f126d265e99a7c054d2a10..4b4eced856ad68c38042b5368bf1b9433ec1e8b1 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Stray.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Stray.java
-@@ -39,6 +39,12 @@ public class Stray extends AbstractSkeleton {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.strayMaxHealth);
-+ }
-+ // Purpur end - Configurable entity base attributes
- public static boolean checkStraySpawnRules(
- EntityType type, ServerLevelAccessor world, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random
- ) {
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Strider.java b/src/main/java/net/minecraft/world/entity/monster/Strider.java
-index 7b1525c6bc46d65660588d90c3121ad3d12cf077..c66126f3a43e6e011e5f9a977ad481c96529d1d5 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Strider.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Strider.java
-@@ -114,6 +114,13 @@ public class Strider extends Animal implements ItemSteerable, Saddleable {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.striderMaxHealth);
-+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.striderScale);
-+ }
-+ // Purpur end - Configurable entity base attributes
- public static boolean checkStriderSpawnRules(EntityType type, LevelAccessor world, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random) {
- BlockPos.MutableBlockPos blockposition_mutableblockposition = pos.mutable();
-
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Vex.java b/src/main/java/net/minecraft/world/entity/monster/Vex.java
-index fba52457f85573f5918aeeb5f3b69b3f113cc9d5..906934076dd721a39f8ee960fc7c0d1058b66ae2 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Vex.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Vex.java
-@@ -103,6 +103,13 @@ public class Vex extends Monster implements TraceableEntity {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.vexMaxHealth);
-+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.vexScale);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- public boolean isFlapping() {
- return this.tickCount % Vex.TICKS_PER_FLAP == 0;
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java
-index 5bf2aad976be5d6149b8252c84cd870551a2aa8e..065bd31afe948c1ffab4da6f2a236cd931e75259 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java
-@@ -72,6 +72,13 @@ public class Vindicator extends AbstractIllager {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.vindicatorMaxHealth);
-+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.vindicatorScale);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- protected void registerGoals() {
- super.registerGoals();
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Witch.java b/src/main/java/net/minecraft/world/entity/monster/Witch.java
-index 5cba860f9ce81d90eec4c6bf45699d28cf8d93e6..9b2d76722385ccf9d0ace747339ca7705ca41f4f 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Witch.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Witch.java
-@@ -74,6 +74,13 @@ public class Witch extends Raider implements RangedAttackMob {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.witchMaxHealth);
-+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.witchScale);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- protected void registerGoals() {
- super.registerGoals();
-diff --git a/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java
-index 626cab5a974d2c8736123cc23e535b5cf0e5349e..feb1b516c7ac7200e7cebeea739369426e87bf27 100644
---- a/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java
-@@ -53,6 +53,13 @@ public class WitherSkeleton extends AbstractSkeleton {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.witherSkeletonMaxHealth);
-+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.witherSkeletonScale);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- protected void registerGoals() {
- this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractPiglin.class, true));
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Zoglin.java b/src/main/java/net/minecraft/world/entity/monster/Zoglin.java
-index 2ac14783e7b5739a13c487d5028ecba38480980d..b5c4e127298795567ea4f35aa5a209ee6e1629b3 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Zoglin.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Zoglin.java
-@@ -102,6 +102,13 @@ public class Zoglin extends Monster implements HoglinBase {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.zoglinMaxHealth);
-+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.zoglinScale);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- protected Brain.Provider brainProvider() {
- return Brain.provider(MEMORY_TYPES, SENSOR_TYPES);
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java
-index 9b4b923117a7025bdbb6d222c6388aeae9bef8a2..5763d259162750297e08acc51551489150dbc593 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java
-@@ -127,6 +127,13 @@ public class Zombie extends Monster {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.zombieMaxHealth);
-+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.zombieScale);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- protected void registerGoals() {
- this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - Ridables
-@@ -627,7 +634,7 @@ public class Zombie extends Monster {
- }
-
- protected void randomizeReinforcementsChance() {
-- this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * 0.10000000149011612D);
-+ this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.zombieSpawnReinforcements); // Purpur - Configurable entity base attributes
- }
-
- @Override
-diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java
-index 9fafe05ef0ffc1120873727082290a8ea177d62f..35d8cef3c84bfd1bbf8afe2885b4f303a4985cdd 100644
---- a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java
-@@ -102,6 +102,17 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.zombieVillagerMaxHealth);
-+ }
-+
-+ @Override
-+ protected void randomizeReinforcementsChance() {
-+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.zombieVillagerSpawnReinforcements);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- protected void defineSynchedData(SynchedEntityData.Builder builder) {
- super.defineSynchedData(builder);
-diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java
-index 69c291c3347a3e3f454ecb8f418a310bbd688a43..ca74450f13198fd7bf0190b4dc1df4288df5729d 100644
---- a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java
-@@ -80,6 +80,13 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.zombifiedPiglinMaxHealth);
-+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.zombifiedPiglinScale);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- public void setPersistentAngerTarget(@Nullable UUID angryAt) {
- this.persistentAngerTarget = angryAt;
-@@ -262,7 +269,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob {
-
- @Override
- protected void randomizeReinforcementsChance() {
-- this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(0.0D);
-+ this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level().purpurConfig.zombifiedPiglinSpawnReinforcements); // Purpur - Configurable entity base attributes
- }
-
- @Nullable
-diff --git a/src/main/java/net/minecraft/world/entity/monster/creaking/Creaking.java b/src/main/java/net/minecraft/world/entity/monster/creaking/Creaking.java
-index 6716bfa903be5ab34b80c963cc9d6a8a26272621..1f37384368c26b4bdd69533887a8e9b8456f7096 100644
---- a/src/main/java/net/minecraft/world/entity/monster/creaking/Creaking.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/creaking/Creaking.java
-@@ -129,6 +129,13 @@ public class Creaking extends Monster {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.creakingMaxHealth);
-+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.creakingScale);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- protected BodyRotationControl createBodyControl() {
- return new Creaking.CreakingBodyRotationControl(this);
-diff --git a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java
-index 4ab971e86b48ce3010928fe9046e8f68224719ca..476dcc613f566d88273f195b847e6b4dec777e44 100644
---- a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java
-@@ -88,6 +88,13 @@ public class Hoglin extends Animal implements Enemy, HoglinBase {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.hoglinMaxHealth);
-+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.hoglinScale);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @VisibleForTesting
- public void setTimeInOverworld(int timeInOverworld) {
- this.timeInOverworld = timeInOverworld;
-diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java
-index b19e3b442a650f773df462e32088648a2b7eafd4..1a40babc8f6b3f56377cb2af45e9d17d0028a77b 100644
---- a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java
-@@ -116,6 +116,13 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.piglinMaxHealth);
-+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.piglinScale);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- public void addAdditionalSaveData(CompoundTag nbt) {
- super.addAdditionalSaveData(nbt);
-diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java
-index 3dc234f8cea8769f715a4913ae4ecf7d47433577..a0fa32a1217bbdae2c91e5a51598d7bd555ca1eb 100644
---- a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java
-@@ -82,6 +82,13 @@ public class PiglinBrute extends AbstractPiglin {
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.piglinBruteMaxHealth);
-+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.piglinBruteScale);
-+ }
-+ // Purpur end - Configurable entity base attributes
- public static AttributeSupplier.Builder createAttributes() {
- return Monster.createMonsterAttributes()
- .add(Attributes.MAX_HEALTH, 50.0)
-diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java
-index 066836e450ca8d104990c3c5fba5fe3dad2e136e..39f2091391222bcd4ab0f2677f896c5ecdc6e86c 100644
---- a/src/main/java/net/minecraft/world/entity/npc/Villager.java
-+++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java
-@@ -178,6 +178,13 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.villagerMaxHealth);
-+ this.getAttribute(Attributes.SCALE).setBaseValue(this.level().purpurConfig.villagerScale);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- public Brain getBrain() {
- return (Brain) super.getBrain(); // CraftBukkit - decompile error
-diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java
-index c5bef9f0ad2a0f57d8c37ea0833e899dc588d30f..8e12ae313c76d742b61aa83a35d41d83b92bded5 100644
---- a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java
-+++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java
-@@ -89,6 +89,12 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill
- }
- // Purpur end - Ridables
-
-+ // Purpur start - Configurable entity base attributes
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level().purpurConfig.wanderingTraderMaxHealth);
-+ }
-+ // Purpur end - Configurable entity base attributes
- @Override
- protected void registerGoals() {
- this.goalSelector.addGoal(0, new FloatGoal(this));
-diff --git a/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java
-index 5004e86747306cc8d4bbed6f10d3a6e9047cb5f4..ee9bcb7d011f20575cbbbe2e0ded1e53087aba7a 100644
---- a/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java
-+++ b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java
-@@ -105,134 +105,261 @@ public class PurpurWorldConfig {
- public boolean allayRidable = false;
- public boolean allayRidableInWater = true;
- public boolean allayControllable = true;
-+ public double allayMaxHealth = 20.0D;
-+ public double allayScale = 1.0D;
- private void allaySettings() {
- allayRidable = getBoolean("mobs.allay.ridable", allayRidable);
- allayRidableInWater = getBoolean("mobs.allay.ridable-in-water", allayRidableInWater);
- allayControllable = getBoolean("mobs.allay.controllable", allayControllable);
-+ allayMaxHealth = getDouble("mobs.allay.attributes.max_health", allayMaxHealth);
-+ allayScale = Mth.clamp(getDouble("mobs.allay.attributes.scale", allayScale), 0.0625D, 16.0D);
- }
-
- public boolean armadilloRidable = false;
- public boolean armadilloRidableInWater = true;
- public boolean armadilloControllable = true;
-+ public double armadilloMaxHealth = 12.0D;
-+ public double armadilloScale = 1.0D;
- private void armadilloSettings() {
- armadilloRidable = getBoolean("mobs.armadillo.ridable", armadilloRidable);
- armadilloRidableInWater = getBoolean("mobs.armadillo.ridable-in-water", armadilloRidableInWater);
- armadilloControllable = getBoolean("mobs.armadillo.controllable", armadilloControllable);
-+ armadilloMaxHealth = getDouble("mobs.armadillo.attributes.max_health", armadilloMaxHealth);
-+ armadilloScale = Mth.clamp(getDouble("mobs.armadillo.attributes.scale", armadilloScale), 0.0625D, 16.0D);
- }
-
- public boolean axolotlRidable = false;
- public boolean axolotlControllable = true;
-+ public double axolotlMaxHealth = 14.0D;
-+ public double axolotlScale = 1.0D;
- private void axolotlSettings() {
- axolotlRidable = getBoolean("mobs.axolotl.ridable", axolotlRidable);
- axolotlControllable = getBoolean("mobs.axolotl.controllable", axolotlControllable);
-+ axolotlMaxHealth = getDouble("mobs.axolotl.attributes.max_health", axolotlMaxHealth);
-+ axolotlScale = Mth.clamp(getDouble("mobs.axolotl.attributes.scale", axolotlScale), 0.0625D, 16.0D);
- }
-
- public boolean batRidable = false;
- public boolean batRidableInWater = true;
- public boolean batControllable = true;
- public double batMaxY = 320D;
-+ public double batMaxHealth = 6.0D;
-+ public double batScale = 1.0D;
-+ public double batFollowRange = 16.0D;
-+ public double batKnockbackResistance = 0.0D;
-+ public double batMovementSpeed = 0.6D;
-+ public double batFlyingSpeed = 0.6D;
-+ public double batArmor = 0.0D;
-+ public double batArmorToughness = 0.0D;
-+ public double batAttackKnockback = 0.0D;
- private void batSettings() {
- batRidable = getBoolean("mobs.bat.ridable", batRidable);
- batRidableInWater = getBoolean("mobs.bat.ridable-in-water", batRidableInWater);
- batControllable = getBoolean("mobs.bat.controllable", batControllable);
- batMaxY = getDouble("mobs.bat.ridable-max-y", batMaxY);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.bat.attributes.max-health", batMaxHealth);
-+ set("mobs.bat.attributes.max-health", null);
-+ set("mobs.bat.attributes.max_health", oldValue);
-+ }
-+ batMaxHealth = getDouble("mobs.bat.attributes.max_health", batMaxHealth);
-+ batScale = Mth.clamp(getDouble("mobs.bat.attributes.scale", batScale), 0.0625D, 16.0D);
-+ batFollowRange = getDouble("mobs.bat.attributes.follow_range", batFollowRange);
-+ batKnockbackResistance = getDouble("mobs.bat.attributes.knockback_resistance", batKnockbackResistance);
-+ batMovementSpeed = getDouble("mobs.bat.attributes.movement_speed", batMovementSpeed);
-+ batFlyingSpeed = getDouble("mobs.bat.attributes.flying_speed", batFlyingSpeed);
-+ batArmor = getDouble("mobs.bat.attributes.armor", batArmor);
-+ batArmorToughness = getDouble("mobs.bat.attributes.armor_toughness", batArmorToughness);
-+ batAttackKnockback = getDouble("mobs.bat.attributes.attack_knockback", batAttackKnockback);
- }
-
- public boolean beeRidable = false;
- public boolean beeRidableInWater = true;
- public boolean beeControllable = true;
- public double beeMaxY = 320D;
-+ public double beeMaxHealth = 10.0D;
-+ public double beeScale = 1.0D;
- private void beeSettings() {
- beeRidable = getBoolean("mobs.bee.ridable", beeRidable);
- beeRidableInWater = getBoolean("mobs.bee.ridable-in-water", beeRidableInWater);
- beeControllable = getBoolean("mobs.bee.controllable", beeControllable);
- beeMaxY = getDouble("mobs.bee.ridable-max-y", beeMaxY);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.bee.attributes.max-health", beeMaxHealth);
-+ set("mobs.bee.attributes.max-health", null);
-+ set("mobs.bee.attributes.max_health", oldValue);
-+ }
-+ beeMaxHealth = getDouble("mobs.bee.attributes.max_health", beeMaxHealth);
-+ beeScale = Mth.clamp(getDouble("mobs.bee.attributes.scale", beeScale), 0.0625D, 16.0D);
- }
-
- public boolean blazeRidable = false;
- public boolean blazeRidableInWater = true;
- public boolean blazeControllable = true;
- public double blazeMaxY = 320D;
-+ public double blazeMaxHealth = 20.0D;
-+ public double blazeScale = 1.0D;
- private void blazeSettings() {
- blazeRidable = getBoolean("mobs.blaze.ridable", blazeRidable);
- blazeRidableInWater = getBoolean("mobs.blaze.ridable-in-water", blazeRidableInWater);
- blazeControllable = getBoolean("mobs.blaze.controllable", blazeControllable);
- blazeMaxY = getDouble("mobs.blaze.ridable-max-y", blazeMaxY);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.blaze.attributes.max-health", blazeMaxHealth);
-+ set("mobs.blaze.attributes.max-health", null);
-+ set("mobs.blaze.attributes.max_health", oldValue);
-+ }
-+ blazeMaxHealth = getDouble("mobs.blaze.attributes.max_health", blazeMaxHealth);
-+ blazeScale = Mth.clamp(getDouble("mobs.blaze.attributes.scale", blazeScale), 0.0625D, 16.0D);
- }
-
- public boolean boggedRidable = false;
- public boolean boggedRidableInWater = true;
- public boolean boggedControllable = true;
-+ public double boggedMaxHealth = 16.0D;
-+ public double boggedScale = 1.0D;
- private void boggedSettings() {
- boggedRidable = getBoolean("mobs.bogged.ridable", boggedRidable);
- boggedRidableInWater = getBoolean("mobs.bogged.ridable-in-water", boggedRidableInWater);
- boggedControllable = getBoolean("mobs.bogged.controllable", boggedControllable);
-+ boggedMaxHealth = getDouble("mobs.bogged.attributes.max_health", boggedMaxHealth);
-+ boggedScale = Mth.clamp(getDouble("mobs.bogged.attributes.scale", boggedScale), 0.0625D, 16.0D);
- }
-
- public boolean camelRidableInWater = false;
-+ public double camelMaxHealthMin = 32.0D;
-+ public double camelMaxHealthMax = 32.0D;
-+ public double camelJumpStrengthMin = 0.42D;
-+ public double camelJumpStrengthMax = 0.42D;
-+ public double camelMovementSpeedMin = 0.09D;
-+ public double camelMovementSpeedMax = 0.09D;
- private void camelSettings() {
- camelRidableInWater = getBoolean("mobs.camel.ridable-in-water", camelRidableInWater);
-+ camelMaxHealthMin = getDouble("mobs.camel.attributes.max_health.min", camelMaxHealthMin);
-+ camelMaxHealthMax = getDouble("mobs.camel.attributes.max_health.max", camelMaxHealthMax);
-+ camelJumpStrengthMin = getDouble("mobs.camel.attributes.jump_strength.min", camelJumpStrengthMin);
-+ camelJumpStrengthMax = getDouble("mobs.camel.attributes.jump_strength.max", camelJumpStrengthMax);
-+ camelMovementSpeedMin = getDouble("mobs.camel.attributes.movement_speed.min", camelMovementSpeedMin);
-+ camelMovementSpeedMax = getDouble("mobs.camel.attributes.movement_speed.max", camelMovementSpeedMax);
- }
-
- public boolean catRidable = false;
- public boolean catRidableInWater = true;
- public boolean catControllable = true;
-+ public double catMaxHealth = 10.0D;
-+ public double catScale = 1.0D;
- private void catSettings() {
- catRidable = getBoolean("mobs.cat.ridable", catRidable);
- catRidableInWater = getBoolean("mobs.cat.ridable-in-water", catRidableInWater);
- catControllable = getBoolean("mobs.cat.controllable", catControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.cat.attributes.max-health", catMaxHealth);
-+ set("mobs.cat.attributes.max-health", null);
-+ set("mobs.cat.attributes.max_health", oldValue);
-+ }
-+ catMaxHealth = getDouble("mobs.cat.attributes.max_health", catMaxHealth);
-+ catScale = Mth.clamp(getDouble("mobs.cat.attributes.scale", catScale), 0.0625D, 16.0D);
- }
-
- public boolean caveSpiderRidable = false;
- public boolean caveSpiderRidableInWater = true;
- public boolean caveSpiderControllable = true;
-+ public double caveSpiderMaxHealth = 12.0D;
-+ public double caveSpiderScale = 1.0D;
- private void caveSpiderSettings() {
- caveSpiderRidable = getBoolean("mobs.cave_spider.ridable", caveSpiderRidable);
- caveSpiderRidableInWater = getBoolean("mobs.cave_spider.ridable-in-water", caveSpiderRidableInWater);
- caveSpiderControllable = getBoolean("mobs.cave_spider.controllable", caveSpiderControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.cave_spider.attributes.max-health", caveSpiderMaxHealth);
-+ set("mobs.cave_spider.attributes.max-health", null);
-+ set("mobs.cave_spider.attributes.max_health", oldValue);
-+ }
-+ caveSpiderMaxHealth = getDouble("mobs.cave_spider.attributes.max_health", caveSpiderMaxHealth);
-+ caveSpiderScale = Mth.clamp(getDouble("mobs.cave_spider.attributes.scale", caveSpiderScale), 0.0625D, 16.0D);
- }
-
- public boolean chickenRidable = false;
- public boolean chickenRidableInWater = false;
- public boolean chickenControllable = true;
-+ public double chickenMaxHealth = 4.0D;
-+ public double chickenScale = 1.0D;
- private void chickenSettings() {
- chickenRidable = getBoolean("mobs.chicken.ridable", chickenRidable);
- chickenRidableInWater = getBoolean("mobs.chicken.ridable-in-water", chickenRidableInWater);
- chickenControllable = getBoolean("mobs.chicken.controllable", chickenControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.chicken.attributes.max-health", chickenMaxHealth);
-+ set("mobs.chicken.attributes.max-health", null);
-+ set("mobs.chicken.attributes.max_health", oldValue);
-+ }
-+ chickenMaxHealth = getDouble("mobs.chicken.attributes.max_health", chickenMaxHealth);
-+ chickenScale = Mth.clamp(getDouble("mobs.chicken.attributes.scale", chickenScale), 0.0625D, 16.0D);
- }
-
- public boolean codRidable = false;
- public boolean codControllable = true;
-+ public double codMaxHealth = 3.0D;
-+ public double codScale = 1.0D;
- private void codSettings() {
- codRidable = getBoolean("mobs.cod.ridable", codRidable);
- codControllable = getBoolean("mobs.cod.controllable", codControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.cod.attributes.max-health", codMaxHealth);
-+ set("mobs.cod.attributes.max-health", null);
-+ set("mobs.cod.attributes.max_health", oldValue);
-+ }
-+ codMaxHealth = getDouble("mobs.cod.attributes.max_health", codMaxHealth);
-+ codScale = Mth.clamp(getDouble("mobs.cod.attributes.scale", codScale), 0.0625D, 16.0D);
- }
-
- public boolean cowRidable = false;
- public boolean cowRidableInWater = true;
- public boolean cowControllable = true;
-+ public double cowMaxHealth = 10.0D;
-+ public double cowScale = 1.0D;
- private void cowSettings() {
- cowRidable = getBoolean("mobs.cow.ridable", cowRidable);
- cowRidableInWater = getBoolean("mobs.cow.ridable-in-water", cowRidableInWater);
- cowControllable = getBoolean("mobs.cow.controllable", cowControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.cow.attributes.max-health", cowMaxHealth);
-+ set("mobs.cow.attributes.max-health", null);
-+ set("mobs.cow.attributes.max_health", oldValue);
-+ }
-+ cowMaxHealth = getDouble("mobs.cow.attributes.max_health", cowMaxHealth);
-+ cowScale = Mth.clamp(getDouble("mobs.cow.attributes.scale", cowScale), 0.0625D, 16.0D);
- }
-
- public boolean creakingRidable = false;
- public boolean creakingRidableInWater = true;
- public boolean creakingControllable = true;
-+ public double creakingMaxHealth = 1.0D;
-+ public double creakingScale = 1.0D;
- private void creakingSettings() {
- creakingRidable = getBoolean("mobs.creaking.ridable", creakingRidable);
- creakingRidableInWater = getBoolean("mobs.creaking.ridable-in-water", creakingRidableInWater);
- creakingControllable = getBoolean("mobs.creaking.controllable", creakingControllable);
-+ creakingMaxHealth = getDouble("mobs.creaking.attributes.max_health", creakingMaxHealth);
-+ creakingScale = Mth.clamp(getDouble("mobs.creaking.attributes.scale", creakingScale), 0.0625D, 16.0D);
- }
-
- public boolean creeperRidable = false;
- public boolean creeperRidableInWater = true;
- public boolean creeperControllable = true;
-+ public double creeperMaxHealth = 20.0D;
-+ public double creeperScale = 1.0D;
- private void creeperSettings() {
- creeperRidable = getBoolean("mobs.creeper.ridable", creeperRidable);
- creeperRidableInWater = getBoolean("mobs.creeper.ridable-in-water", creeperRidableInWater);
- creeperControllable = getBoolean("mobs.creeper.controllable", creeperControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.creeper.attributes.max-health", creeperMaxHealth);
-+ set("mobs.creeper.attributes.max-health", null);
-+ set("mobs.creeper.attributes.max_health", oldValue);
-+ }
-+ creeperMaxHealth = getDouble("mobs.creeper.attributes.max_health", creeperMaxHealth);
-+ creeperScale = Mth.clamp(getDouble("mobs.creeper.attributes.scale", creeperScale), 0.0625D, 16.0D);
- }
-
- public boolean dolphinRidable = false;
-@@ -240,80 +367,175 @@ public class PurpurWorldConfig {
- public int dolphinSpitCooldown = 20;
- public float dolphinSpitSpeed = 1.0F;
- public float dolphinSpitDamage = 2.0F;
-+ public double dolphinMaxHealth = 10.0D;
-+ public double dolphinScale = 1.0D;
- private void dolphinSettings() {
- dolphinRidable = getBoolean("mobs.dolphin.ridable", dolphinRidable);
- dolphinControllable = getBoolean("mobs.dolphin.controllable", dolphinControllable);
- dolphinSpitCooldown = getInt("mobs.dolphin.spit.cooldown", dolphinSpitCooldown);
- dolphinSpitSpeed = (float) getDouble("mobs.dolphin.spit.speed", dolphinSpitSpeed);
- dolphinSpitDamage = (float) getDouble("mobs.dolphin.spit.damage", dolphinSpitDamage);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.dolphin.attributes.max-health", dolphinMaxHealth);
-+ set("mobs.dolphin.attributes.max-health", null);
-+ set("mobs.dolphin.attributes.max_health", oldValue);
-+ }
-+ dolphinMaxHealth = getDouble("mobs.dolphin.attributes.max_health", dolphinMaxHealth);
-+ dolphinScale = Mth.clamp(getDouble("mobs.dolphin.attributes.scale", dolphinScale), 0.0625D, 16.0D);
- }
-
- public boolean donkeyRidableInWater = false;
-+ public double donkeyMaxHealthMin = 15.0D;
-+ public double donkeyMaxHealthMax = 30.0D;
-+ public double donkeyJumpStrengthMin = 0.5D;
-+ public double donkeyJumpStrengthMax = 0.5D;
-+ public double donkeyMovementSpeedMin = 0.175D;
-+ public double donkeyMovementSpeedMax = 0.175D;
- private void donkeySettings() {
- donkeyRidableInWater = getBoolean("mobs.donkey.ridable-in-water", donkeyRidableInWater);
-+ if (PurpurConfig.version < 10) {
-+ double oldMin = getDouble("mobs.donkey.attributes.max-health.min", donkeyMaxHealthMin);
-+ double oldMax = getDouble("mobs.donkey.attributes.max-health.max", donkeyMaxHealthMax);
-+ set("mobs.donkey.attributes.max-health", null);
-+ set("mobs.donkey.attributes.max_health.min", oldMin);
-+ set("mobs.donkey.attributes.max_health.max", oldMax);
-+ }
-+ donkeyMaxHealthMin = getDouble("mobs.donkey.attributes.max_health.min", donkeyMaxHealthMin);
-+ donkeyMaxHealthMax = getDouble("mobs.donkey.attributes.max_health.max", donkeyMaxHealthMax);
-+ donkeyJumpStrengthMin = getDouble("mobs.donkey.attributes.jump_strength.min", donkeyJumpStrengthMin);
-+ donkeyJumpStrengthMax = getDouble("mobs.donkey.attributes.jump_strength.max", donkeyJumpStrengthMax);
-+ donkeyMovementSpeedMin = getDouble("mobs.donkey.attributes.movement_speed.min", donkeyMovementSpeedMin);
-+ donkeyMovementSpeedMax = getDouble("mobs.donkey.attributes.movement_speed.max", donkeyMovementSpeedMax);
- }
-
- public boolean drownedRidable = false;
- public boolean drownedRidableInWater = true;
- public boolean drownedControllable = true;
-+ public double drownedMaxHealth = 20.0D;
-+ public double drownedScale = 1.0D;
-+ public double drownedSpawnReinforcements = 0.1D;
- private void drownedSettings() {
- drownedRidable = getBoolean("mobs.drowned.ridable", drownedRidable);
- drownedRidableInWater = getBoolean("mobs.drowned.ridable-in-water", drownedRidableInWater);
- drownedControllable = getBoolean("mobs.drowned.controllable", drownedControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.drowned.attributes.max-health", drownedMaxHealth);
-+ set("mobs.drowned.attributes.max-health", null);
-+ set("mobs.drowned.attributes.max_health", oldValue);
-+ }
-+ drownedMaxHealth = getDouble("mobs.drowned.attributes.max_health", drownedMaxHealth);
-+ drownedScale = Mth.clamp(getDouble("mobs.drowned.attributes.scale", drownedScale), 0.0625D, 16.0D);
-+ drownedSpawnReinforcements = getDouble("mobs.drowned.attributes.spawn_reinforcements", drownedSpawnReinforcements);
- }
-
- public boolean elderGuardianRidable = false;
- public boolean elderGuardianControllable = true;
-+ public double elderGuardianMaxHealth = 80.0D;
-+ public double elderGuardianScale = 1.0D;
- private void elderGuardianSettings() {
- elderGuardianRidable = getBoolean("mobs.elder_guardian.ridable", elderGuardianRidable);
- elderGuardianControllable = getBoolean("mobs.elder_guardian.controllable", elderGuardianControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.elder_guardian.attributes.max-health", elderGuardianMaxHealth);
-+ set("mobs.elder_guardian.attributes.max-health", null);
-+ set("mobs.elder_guardian.attributes.max_health", oldValue);
-+ }
-+ elderGuardianMaxHealth = getDouble("mobs.elder_guardian.attributes.max_health", elderGuardianMaxHealth);
-+ elderGuardianScale = Mth.clamp(getDouble("mobs.elder_guardian.attributes.scale", elderGuardianScale), 0.0625D, 16.0D);
- }
-
- public boolean enderDragonRidable = false;
- public boolean enderDragonRidableInWater = true;
- public boolean enderDragonControllable = true;
- public double enderDragonMaxY = 320D;
-+ public double enderDragonMaxHealth = 200.0D;
- private void enderDragonSettings() {
- enderDragonRidable = getBoolean("mobs.ender_dragon.ridable", enderDragonRidable);
- enderDragonRidableInWater = getBoolean("mobs.ender_dragon.ridable-in-water", enderDragonRidableInWater);
- enderDragonControllable = getBoolean("mobs.ender_dragon.controllable", enderDragonControllable);
- enderDragonMaxY = getDouble("mobs.ender_dragon.ridable-max-y", enderDragonMaxY);
-+ if (PurpurConfig.version < 8) {
-+ double oldValue = getDouble("mobs.ender_dragon.max-health", enderDragonMaxHealth);
-+ set("mobs.ender_dragon.max-health", null);
-+ set("mobs.ender_dragon.attributes.max_health", oldValue);
-+ } else if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.ender_dragon.attributes.max-health", enderDragonMaxHealth);
-+ set("mobs.ender_dragon.attributes.max-health", null);
-+ set("mobs.ender_dragon.attributes.max_health", oldValue);
-+ }
-+ enderDragonMaxHealth = getDouble("mobs.ender_dragon.attributes.max_health", enderDragonMaxHealth);
- }
-
- public boolean endermanRidable = false;
- public boolean endermanRidableInWater = true;
- public boolean endermanControllable = true;
-+ public double endermanMaxHealth = 40.0D;
-+ public double endermanScale = 1.0D;
- private void endermanSettings() {
- endermanRidable = getBoolean("mobs.enderman.ridable", endermanRidable);
- endermanRidableInWater = getBoolean("mobs.enderman.ridable-in-water", endermanRidableInWater);
- endermanControllable = getBoolean("mobs.enderman.controllable", endermanControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.enderman.attributes.max-health", endermanMaxHealth);
-+ set("mobs.enderman.attributes.max-health", null);
-+ set("mobs.enderman.attributes.max_health", oldValue);
-+ }
-+ endermanMaxHealth = getDouble("mobs.enderman.attributes.max_health", endermanMaxHealth);
-+ endermanScale = Mth.clamp(getDouble("mobs.enderman.attributes.scale", endermanScale), 0.0625D, 16.0D);
- }
-
- public boolean endermiteRidable = false;
- public boolean endermiteRidableInWater = true;
- public boolean endermiteControllable = true;
-+ public double endermiteMaxHealth = 8.0D;
-+ public double endermiteScale = 1.0D;
- private void endermiteSettings() {
- endermiteRidable = getBoolean("mobs.endermite.ridable", endermiteRidable);
- endermiteRidableInWater = getBoolean("mobs.endermite.ridable-in-water", endermiteRidableInWater);
- endermiteControllable = getBoolean("mobs.endermite.controllable", endermiteControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.endermite.attributes.max-health", endermiteMaxHealth);
-+ set("mobs.endermite.attributes.max-health", null);
-+ set("mobs.endermite.attributes.max_health", oldValue);
-+ }
-+ endermiteMaxHealth = getDouble("mobs.endermite.attributes.max_health", endermiteMaxHealth);
-+ endermiteScale = Mth.clamp(getDouble("mobs.endermite.attributes.scale", endermiteScale), 0.0625D, 16.0D);
- }
-
- public boolean evokerRidable = false;
- public boolean evokerRidableInWater = true;
- public boolean evokerControllable = true;
-+ public double evokerMaxHealth = 24.0D;
-+ public double evokerScale = 1.0D;
- private void evokerSettings() {
- evokerRidable = getBoolean("mobs.evoker.ridable", evokerRidable);
- evokerRidableInWater = getBoolean("mobs.evoker.ridable-in-water", evokerRidableInWater);
- evokerControllable = getBoolean("mobs.evoker.controllable", evokerControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.evoker.attributes.max-health", evokerMaxHealth);
-+ set("mobs.evoker.attributes.max-health", null);
-+ set("mobs.evoker.attributes.max_health", oldValue);
-+ }
-+ evokerMaxHealth = getDouble("mobs.evoker.attributes.max_health", evokerMaxHealth);
-+ evokerScale = Mth.clamp(getDouble("mobs.evoker.attributes.scale", evokerScale), 0.0625D, 16.0D);
- }
-
- public boolean foxRidable = false;
- public boolean foxRidableInWater = true;
- public boolean foxControllable = true;
-+ public double foxMaxHealth = 10.0D;
-+ public double foxScale = 1.0D;
- private void foxSettings() {
- foxRidable = getBoolean("mobs.fox.ridable", foxRidable);
- foxRidableInWater = getBoolean("mobs.fox.ridable-in-water", foxRidableInWater);
- foxControllable = getBoolean("mobs.fox.controllable", foxControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.fox.attributes.max-health", foxMaxHealth);
-+ set("mobs.fox.attributes.max-health", null);
-+ set("mobs.fox.attributes.max_health", oldValue);
-+ }
-+ foxMaxHealth = getDouble("mobs.fox.attributes.max_health", foxMaxHealth);
-+ foxScale = Mth.clamp(getDouble("mobs.fox.attributes.scale", foxScale), 0.0625D, 16.0D);
- }
-
- public boolean frogRidable = false;
-@@ -331,147 +553,342 @@ public class PurpurWorldConfig {
- public boolean ghastRidableInWater = true;
- public boolean ghastControllable = true;
- public double ghastMaxY = 320D;
-+ public double ghastMaxHealth = 10.0D;
-+ public double ghastScale = 1.0D;
- private void ghastSettings() {
- ghastRidable = getBoolean("mobs.ghast.ridable", ghastRidable);
- ghastRidableInWater = getBoolean("mobs.ghast.ridable-in-water", ghastRidableInWater);
- ghastControllable = getBoolean("mobs.ghast.controllable", ghastControllable);
- ghastMaxY = getDouble("mobs.ghast.ridable-max-y", ghastMaxY);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.ghast.attributes.max-health", ghastMaxHealth);
-+ set("mobs.ghast.attributes.max-health", null);
-+ set("mobs.ghast.attributes.max_health", oldValue);
-+ }
-+ ghastMaxHealth = getDouble("mobs.ghast.attributes.max_health", ghastMaxHealth);
-+ ghastScale = Mth.clamp(getDouble("mobs.ghast.attributes.scale", ghastScale), 0.0625D, 16.0D);
- }
-
- public boolean giantRidable = false;
- public boolean giantRidableInWater = true;
- public boolean giantControllable = true;
-+ public double giantMovementSpeed = 0.5D;
-+ public double giantAttackDamage = 50.0D;
-+ public double giantMaxHealth = 100.0D;
-+ public double giantScale = 1.0D;
- private void giantSettings() {
- giantRidable = getBoolean("mobs.giant.ridable", giantRidable);
- giantRidableInWater = getBoolean("mobs.giant.ridable-in-water", giantRidableInWater);
- giantControllable = getBoolean("mobs.giant.controllable", giantControllable);
-+ giantMovementSpeed = getDouble("mobs.giant.movement-speed", giantMovementSpeed);
-+ giantAttackDamage = getDouble("mobs.giant.attack-damage", giantAttackDamage);
-+ if (PurpurConfig.version < 8) {
-+ double oldValue = getDouble("mobs.giant.max-health", giantMaxHealth);
-+ set("mobs.giant.max-health", null);
-+ set("mobs.giant.attributes.max_health", oldValue);
-+ } else if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.giant.attributes.max-health", giantMaxHealth);
-+ set("mobs.giant.attributes.max-health", null);
-+ set("mobs.giant.attributes.max_health", oldValue);
-+ }
-+ giantMaxHealth = getDouble("mobs.giant.attributes.max_health", giantMaxHealth);
-+ giantScale = Mth.clamp(getDouble("mobs.giant.attributes.scale", giantScale), 0.0625D, 16.0D);
- }
-
- public boolean glowSquidRidable = false;
- public boolean glowSquidControllable = true;
-+ public double glowSquidMaxHealth = 10.0D;
-+ public double glowSquidScale = 1.0D;
- private void glowSquidSettings() {
- glowSquidRidable = getBoolean("mobs.glow_squid.ridable", glowSquidRidable);
- glowSquidControllable = getBoolean("mobs.glow_squid.controllable", glowSquidControllable);
-+ glowSquidMaxHealth = getDouble("mobs.glow_squid.attributes.max_health", glowSquidMaxHealth);
-+ glowSquidScale = Mth.clamp(getDouble("mobs.glow_squid.attributes.scale", glowSquidScale), 0.0625D, 16.0D);
- }
-
- public boolean goatRidable = false;
- public boolean goatRidableInWater = true;
- public boolean goatControllable = true;
-+ public double goatMaxHealth = 10.0D;
-+ public double goatScale = 1.0D;
- private void goatSettings() {
- goatRidable = getBoolean("mobs.goat.ridable", goatRidable);
- goatRidableInWater = getBoolean("mobs.goat.ridable-in-water", goatRidableInWater);
- goatControllable = getBoolean("mobs.goat.controllable", goatControllable);
-+ goatMaxHealth = getDouble("mobs.goat.attributes.max_health", goatMaxHealth);
-+ goatScale = Mth.clamp(getDouble("mobs.goat.attributes.scale", goatScale), 0.0625D, 16.0D);
- }
-
- public boolean guardianRidable = false;
- public boolean guardianControllable = true;
-+ public double guardianMaxHealth = 30.0D;
-+ public double guardianScale = 1.0D;
- private void guardianSettings() {
- guardianRidable = getBoolean("mobs.guardian.ridable", guardianRidable);
- guardianControllable = getBoolean("mobs.guardian.controllable", guardianControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.guardian.attributes.max-health", guardianMaxHealth);
-+ set("mobs.guardian.attributes.max-health", null);
-+ set("mobs.guardian.attributes.max_health", oldValue);
-+ }
-+ guardianMaxHealth = getDouble("mobs.guardian.attributes.max_health", guardianMaxHealth);
-+ guardianScale = Mth.clamp(getDouble("mobs.guardian.attributes.scale", guardianScale), 0.0625D, 16.0D);
- }
-
- public boolean hoglinRidable = false;
- public boolean hoglinRidableInWater = true;
- public boolean hoglinControllable = true;
-+ public double hoglinMaxHealth = 40.0D;
-+ public double hoglinScale = 1.0D;
- private void hoglinSettings() {
- hoglinRidable = getBoolean("mobs.hoglin.ridable", hoglinRidable);
- hoglinRidableInWater = getBoolean("mobs.hoglin.ridable-in-water", hoglinRidableInWater);
- hoglinControllable = getBoolean("mobs.hoglin.controllable", hoglinControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.hoglin.attributes.max-health", hoglinMaxHealth);
-+ set("mobs.hoglin.attributes.max-health", null);
-+ set("mobs.hoglin.attributes.max_health", oldValue);
-+ }
-+ hoglinMaxHealth = getDouble("mobs.hoglin.attributes.max_health", hoglinMaxHealth);
-+ hoglinScale = Mth.clamp(getDouble("mobs.hoglin.attributes.scale", hoglinScale), 0.0625D, 16.0D);
- }
-
- public boolean horseRidableInWater = false;
-+ public double horseMaxHealthMin = 15.0D;
-+ public double horseMaxHealthMax = 30.0D;
-+ public double horseJumpStrengthMin = 0.4D;
-+ public double horseJumpStrengthMax = 1.0D;
-+ public double horseMovementSpeedMin = 0.1125D;
-+ public double horseMovementSpeedMax = 0.3375D;
- private void horseSettings() {
- horseRidableInWater = getBoolean("mobs.horse.ridable-in-water", horseRidableInWater);
-+ if (PurpurConfig.version < 10) {
-+ double oldMin = getDouble("mobs.horse.attributes.max-health.min", horseMaxHealthMin);
-+ double oldMax = getDouble("mobs.horse.attributes.max-health.max", horseMaxHealthMax);
-+ set("mobs.horse.attributes.max-health", null);
-+ set("mobs.horse.attributes.max_health.min", oldMin);
-+ set("mobs.horse.attributes.max_health.max", oldMax);
-+ }
-+ horseMaxHealthMin = getDouble("mobs.horse.attributes.max_health.min", horseMaxHealthMin);
-+ horseMaxHealthMax = getDouble("mobs.horse.attributes.max_health.max", horseMaxHealthMax);
-+ horseJumpStrengthMin = getDouble("mobs.horse.attributes.jump_strength.min", horseJumpStrengthMin);
-+ horseJumpStrengthMax = getDouble("mobs.horse.attributes.jump_strength.max", horseJumpStrengthMax);
-+ horseMovementSpeedMin = getDouble("mobs.horse.attributes.movement_speed.min", horseMovementSpeedMin);
-+ horseMovementSpeedMax = getDouble("mobs.horse.attributes.movement_speed.max", horseMovementSpeedMax);
- }
-
- public boolean huskRidable = false;
- public boolean huskRidableInWater = true;
- public boolean huskControllable = true;
-+ public double huskMaxHealth = 20.0D;
-+ public double huskScale = 1.0D;
-+ public double huskSpawnReinforcements = 0.1D;
- private void huskSettings() {
- huskRidable = getBoolean("mobs.husk.ridable", huskRidable);
- huskRidableInWater = getBoolean("mobs.husk.ridable-in-water", huskRidableInWater);
- huskControllable = getBoolean("mobs.husk.controllable", huskControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.husk.attributes.max-health", huskMaxHealth);
-+ set("mobs.husk.attributes.max-health", null);
-+ set("mobs.husk.attributes.max_health", oldValue);
-+ }
-+ huskMaxHealth = getDouble("mobs.husk.attributes.max_health", huskMaxHealth);
-+ huskScale = Mth.clamp(getDouble("mobs.husk.attributes.scale", huskScale), 0.0625D, 16.0D);
-+ huskSpawnReinforcements = getDouble("mobs.husk.attributes.spawn_reinforcements", huskSpawnReinforcements);
- }
-
- public boolean illusionerRidable = false;
- public boolean illusionerRidableInWater = true;
- public boolean illusionerControllable = true;
-+ public double illusionerMovementSpeed = 0.5D;
-+ public double illusionerFollowRange = 18.0D;
-+ public double illusionerMaxHealth = 32.0D;
-+ public double illusionerScale = 1.0D;
- private void illusionerSettings() {
- illusionerRidable = getBoolean("mobs.illusioner.ridable", illusionerRidable);
- illusionerRidableInWater = getBoolean("mobs.illusioner.ridable-in-water", illusionerRidableInWater);
- illusionerControllable = getBoolean("mobs.illusioner.controllable", illusionerControllable);
-+ 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.max-health", null);
-+ set("mobs.illusioner.attributes.max_health", oldValue);
-+ } else if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.illusioner.attributes.max-health", illusionerMaxHealth);
-+ set("mobs.illusioner.attributes.max-health", null);
-+ set("mobs.illusioner.attributes.max_health", oldValue);
-+ }
-+ illusionerMaxHealth = getDouble("mobs.illusioner.attributes.max_health", illusionerMaxHealth);
-+ illusionerScale = Mth.clamp(getDouble("mobs.illusioner.attributes.scale", illusionerScale), 0.0625D, 16.0D);
- }
-
- public boolean ironGolemRidable = false;
- public boolean ironGolemRidableInWater = true;
- public boolean ironGolemControllable = true;
- public boolean ironGolemCanSwim = false;
-+ public double ironGolemMaxHealth = 100.0D;
-+ public double ironGolemScale = 1.0D;
- private void ironGolemSettings() {
- ironGolemRidable = getBoolean("mobs.iron_golem.ridable", ironGolemRidable);
- ironGolemRidableInWater = getBoolean("mobs.iron_golem.ridable-in-water", ironGolemRidableInWater);
- ironGolemControllable = getBoolean("mobs.iron_golem.controllable", ironGolemControllable);
- ironGolemCanSwim = getBoolean("mobs.iron_golem.can-swim", ironGolemCanSwim);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.iron_golem.attributes.max-health", ironGolemMaxHealth);
-+ set("mobs.iron_golem.attributes.max-health", null);
-+ set("mobs.iron_golem.attributes.max_health", oldValue);
-+ }
-+ ironGolemMaxHealth = getDouble("mobs.iron_golem.attributes.max_health", ironGolemMaxHealth);
-+ ironGolemScale = Mth.clamp(getDouble("mobs.iron_golem.attributes.scale", ironGolemScale), 0.0625D, 16.0D);
- }
-
- public boolean llamaRidable = false;
- public boolean llamaRidableInWater = false;
- public boolean llamaControllable = true;
-+ public double llamaMaxHealthMin = 15.0D;
-+ public double llamaMaxHealthMax = 30.0D;
-+ public double llamaJumpStrengthMin = 0.5D;
-+ public double llamaJumpStrengthMax = 0.5D;
-+ public double llamaMovementSpeedMin = 0.175D;
-+ public double llamaMovementSpeedMax = 0.175D;
- private void llamaSettings() {
- llamaRidable = getBoolean("mobs.llama.ridable", llamaRidable);
- llamaRidableInWater = getBoolean("mobs.llama.ridable-in-water", llamaRidableInWater);
- llamaControllable = getBoolean("mobs.llama.controllable", llamaControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldMin = getDouble("mobs.llama.attributes.max-health.min", llamaMaxHealthMin);
-+ double oldMax = getDouble("mobs.llama.attributes.max-health.max", llamaMaxHealthMax);
-+ set("mobs.llama.attributes.max-health", null);
-+ set("mobs.llama.attributes.max_health.min", oldMin);
-+ set("mobs.llama.attributes.max_health.max", oldMax);
-+ }
-+ llamaMaxHealthMin = getDouble("mobs.llama.attributes.max_health.min", llamaMaxHealthMin);
-+ llamaMaxHealthMax = getDouble("mobs.llama.attributes.max_health.max", llamaMaxHealthMax);
-+ llamaJumpStrengthMin = getDouble("mobs.llama.attributes.jump_strength.min", llamaJumpStrengthMin);
-+ llamaJumpStrengthMax = getDouble("mobs.llama.attributes.jump_strength.max", llamaJumpStrengthMax);
-+ llamaMovementSpeedMin = getDouble("mobs.llama.attributes.movement_speed.min", llamaMovementSpeedMin);
-+ llamaMovementSpeedMax = getDouble("mobs.llama.attributes.movement_speed.max", llamaMovementSpeedMax);
- }
-
- public boolean magmaCubeRidable = false;
- public boolean magmaCubeRidableInWater = true;
- public boolean magmaCubeControllable = true;
-+ public String magmaCubeMaxHealth = "size * size";
-+ public String magmaCubeAttackDamage = "size";
-+ public Map magmaCubeMaxHealthCache = new HashMap<>();
-+ public Map magmaCubeAttackDamageCache = new HashMap<>();
- private void magmaCubeSettings() {
- magmaCubeRidable = getBoolean("mobs.magma_cube.ridable", magmaCubeRidable);
- magmaCubeRidableInWater = getBoolean("mobs.magma_cube.ridable-in-water", magmaCubeRidableInWater);
- magmaCubeControllable = getBoolean("mobs.magma_cube.controllable", magmaCubeControllable);
-+ if (PurpurConfig.version < 10) {
-+ String oldValue = getString("mobs.magma_cube.attributes.max-health", magmaCubeMaxHealth);
-+ set("mobs.magma_cube.attributes.max-health", null);
-+ set("mobs.magma_cube.attributes.max_health", oldValue);
-+ }
-+ magmaCubeMaxHealth = getString("mobs.magma_cube.attributes.max_health", magmaCubeMaxHealth);
-+ magmaCubeAttackDamage = getString("mobs.magma_cube.attributes.attack_damage", magmaCubeAttackDamage);
-+ magmaCubeMaxHealthCache.clear();
-+ magmaCubeAttackDamageCache.clear();
- }
-
- public boolean mooshroomRidable = false;
- public boolean mooshroomRidableInWater = true;
- public boolean mooshroomControllable = true;
-+ public double mooshroomMaxHealth = 10.0D;
-+ public double mooshroomScale = 1.0D;
- private void mooshroomSettings() {
- mooshroomRidable = getBoolean("mobs.mooshroom.ridable", mooshroomRidable);
- mooshroomRidableInWater = getBoolean("mobs.mooshroom.ridable-in-water", mooshroomRidableInWater);
- mooshroomControllable = getBoolean("mobs.mooshroom.controllable", mooshroomControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.mooshroom.attributes.max-health", mooshroomMaxHealth);
-+ set("mobs.mooshroom.attributes.max-health", null);
-+ set("mobs.mooshroom.attributes.max_health", oldValue);
-+ }
-+ mooshroomMaxHealth = getDouble("mobs.mooshroom.attributes.max_health", mooshroomMaxHealth);
-+ mooshroomScale = Mth.clamp(getDouble("mobs.mooshroom.attributes.scale", mooshroomScale), 0.0625D, 16.0D);
- }
-
- public boolean muleRidableInWater = false;
-+ public double muleMaxHealthMin = 15.0D;
-+ public double muleMaxHealthMax = 30.0D;
-+ public double muleJumpStrengthMin = 0.5D;
-+ public double muleJumpStrengthMax = 0.5D;
-+ public double muleMovementSpeedMin = 0.175D;
-+ public double muleMovementSpeedMax = 0.175D;
- private void muleSettings() {
- muleRidableInWater = getBoolean("mobs.mule.ridable-in-water", muleRidableInWater);
-+ if (PurpurConfig.version < 10) {
-+ double oldMin = getDouble("mobs.mule.attributes.max-health.min", muleMaxHealthMin);
-+ double oldMax = getDouble("mobs.mule.attributes.max-health.max", muleMaxHealthMax);
-+ set("mobs.mule.attributes.max-health", null);
-+ set("mobs.mule.attributes.max_health.min", oldMin);
-+ set("mobs.mule.attributes.max_health.max", oldMax);
-+ }
-+ muleMaxHealthMin = getDouble("mobs.mule.attributes.max_health.min", muleMaxHealthMin);
-+ muleMaxHealthMax = getDouble("mobs.mule.attributes.max_health.max", muleMaxHealthMax);
-+ muleJumpStrengthMin = getDouble("mobs.mule.attributes.jump_strength.min", muleJumpStrengthMin);
-+ muleJumpStrengthMax = getDouble("mobs.mule.attributes.jump_strength.max", muleJumpStrengthMax);
-+ muleMovementSpeedMin = getDouble("mobs.mule.attributes.movement_speed.min", muleMovementSpeedMin);
-+ muleMovementSpeedMax = getDouble("mobs.mule.attributes.movement_speed.max", muleMovementSpeedMax);
- }
-
- public boolean ocelotRidable = false;
- public boolean ocelotRidableInWater = true;
- public boolean ocelotControllable = true;
-+ public double ocelotMaxHealth = 10.0D;
-+ public double ocelotScale = 1.0D;
- private void ocelotSettings() {
- ocelotRidable = getBoolean("mobs.ocelot.ridable", ocelotRidable);
- ocelotRidableInWater = getBoolean("mobs.ocelot.ridable-in-water", ocelotRidableInWater);
- ocelotControllable = getBoolean("mobs.ocelot.controllable", ocelotControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.ocelot.attributes.max-health", ocelotMaxHealth);
-+ set("mobs.ocelot.attributes.max-health", null);
-+ set("mobs.ocelot.attributes.max_health", oldValue);
-+ }
-+ ocelotMaxHealth = getDouble("mobs.ocelot.attributes.max_health", ocelotMaxHealth);
-+ ocelotScale = Mth.clamp(getDouble("mobs.ocelot.attributes.scale", ocelotScale), 0.0625D, 16.0D);
- }
-
- public boolean pandaRidable = false;
- public boolean pandaRidableInWater = true;
- public boolean pandaControllable = true;
-+ public double pandaMaxHealth = 20.0D;
-+ public double pandaScale = 1.0D;
- private void pandaSettings() {
- pandaRidable = getBoolean("mobs.panda.ridable", pandaRidable);
- pandaRidableInWater = getBoolean("mobs.panda.ridable-in-water", pandaRidableInWater);
- pandaControllable = getBoolean("mobs.panda.controllable", pandaControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.panda.attributes.max-health", pandaMaxHealth);
-+ set("mobs.panda.attributes.max-health", null);
-+ set("mobs.panda.attributes.max_health", oldValue);
-+ }
-+ pandaMaxHealth = getDouble("mobs.panda.attributes.max_health", pandaMaxHealth);
-+ pandaScale = Mth.clamp(getDouble("mobs.panda.attributes.scale", pandaScale), 0.0625D, 16.0D);
- }
-
- public boolean parrotRidable = false;
- public boolean parrotRidableInWater = true;
- public boolean parrotControllable = true;
- public double parrotMaxY = 320D;
-+ public double parrotMaxHealth = 6.0D;
-+ public double parrotScale = 1.0D;
- private void parrotSettings() {
- parrotRidable = getBoolean("mobs.parrot.ridable", parrotRidable);
- parrotRidableInWater = getBoolean("mobs.parrot.ridable-in-water", parrotRidableInWater);
- parrotControllable = getBoolean("mobs.parrot.controllable", parrotControllable);
- parrotMaxY = getDouble("mobs.parrot.ridable-max-y", parrotMaxY);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.parrot.attributes.max-health", parrotMaxHealth);
-+ set("mobs.parrot.attributes.max-health", null);
-+ set("mobs.parrot.attributes.max_health", oldValue);
-+ }
-+ parrotMaxHealth = getDouble("mobs.parrot.attributes.max_health", parrotMaxHealth);
-+ parrotScale = Mth.clamp(getDouble("mobs.parrot.attributes.scale", parrotScale), 0.0625D, 16.0D);
- }
-
- public boolean phantomRidable = false;
-@@ -481,6 +898,10 @@ public class PurpurWorldConfig {
- public float phantomFlameDamage = 1.0F;
- public int phantomFlameFireTime = 8;
- public boolean phantomAllowGriefing = false;
-+ public String phantomMaxHealth = "20.0";
-+ public String phantomAttackDamage = "6 + size";
-+ public Map phantomMaxHealthCache = new HashMap<>();
-+ public Map phantomAttackDamageCache = new HashMap<>();
- private void phantomSettings() {
- phantomRidable = getBoolean("mobs.phantom.ridable", phantomRidable);
- phantomRidableInWater = getBoolean("mobs.phantom.ridable-in-water", phantomRidableInWater);
-@@ -489,191 +910,405 @@ public class PurpurWorldConfig {
- phantomFlameDamage = (float) getDouble("mobs.phantom.flames.damage", phantomFlameDamage);
- phantomFlameFireTime = getInt("mobs.phantom.flames.fire-time", phantomFlameFireTime);
- phantomAllowGriefing = getBoolean("mobs.phantom.allow-griefing", phantomAllowGriefing);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.phantom.attributes.max-health", Double.parseDouble(phantomMaxHealth));
-+ set("mobs.phantom.attributes.max-health", null);
-+ set("mobs.phantom.attributes.max_health", String.valueOf(oldValue));
-+ }
-+ if (PurpurConfig.version < 25) {
-+ double oldValue = getDouble("mobs.phantom.attributes.max_health", Double.parseDouble(phantomMaxHealth));
-+ set("mobs.phantom.attributes.max_health", String.valueOf(oldValue));
-+ }
-+ phantomMaxHealth = getString("mobs.phantom.attributes.max_health", phantomMaxHealth);
-+ phantomAttackDamage = getString("mobs.phantom.attributes.attack_damage", phantomAttackDamage);
-+ phantomMaxHealthCache.clear();
-+ phantomAttackDamageCache.clear();
- }
-
- public boolean pigRidable = false;
- public boolean pigRidableInWater = false;
- public boolean pigControllable = true;
-+ public double pigMaxHealth = 10.0D;
-+ public double pigScale = 1.0D;
- private void pigSettings() {
- pigRidable = getBoolean("mobs.pig.ridable", pigRidable);
- pigRidableInWater = getBoolean("mobs.pig.ridable-in-water", pigRidableInWater);
- pigControllable = getBoolean("mobs.pig.controllable", pigControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.pig.attributes.max-health", pigMaxHealth);
-+ set("mobs.pig.attributes.max-health", null);
-+ set("mobs.pig.attributes.max_health", oldValue);
-+ }
-+ pigMaxHealth = getDouble("mobs.pig.attributes.max_health", pigMaxHealth);
-+ pigScale = Mth.clamp(getDouble("mobs.pig.attributes.scale", pigScale), 0.0625D, 16.0D);
- }
-
- public boolean piglinRidable = false;
- public boolean piglinRidableInWater = true;
- public boolean piglinControllable = true;
-+ public double piglinMaxHealth = 16.0D;
-+ public double piglinScale = 1.0D;
- private void piglinSettings() {
- piglinRidable = getBoolean("mobs.piglin.ridable", piglinRidable);
- piglinRidableInWater = getBoolean("mobs.piglin.ridable-in-water", piglinRidableInWater);
- piglinControllable = getBoolean("mobs.piglin.controllable", piglinControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.piglin.attributes.max-health", piglinMaxHealth);
-+ set("mobs.piglin.attributes.max-health", null);
-+ set("mobs.piglin.attributes.max_health", oldValue);
-+ }
-+ piglinMaxHealth = getDouble("mobs.piglin.attributes.max_health", piglinMaxHealth);
-+ piglinScale = Mth.clamp(getDouble("mobs.piglin.attributes.scale", piglinScale), 0.0625D, 16.0D);
- }
-
- public boolean piglinBruteRidable = false;
- public boolean piglinBruteRidableInWater = true;
- public boolean piglinBruteControllable = true;
-+ public double piglinBruteMaxHealth = 50.0D;
-+ public double piglinBruteScale = 1.0D;
- private void piglinBruteSettings() {
- piglinBruteRidable = getBoolean("mobs.piglin_brute.ridable", piglinBruteRidable);
- piglinBruteRidableInWater = getBoolean("mobs.piglin_brute.ridable-in-water", piglinBruteRidableInWater);
- piglinBruteControllable = getBoolean("mobs.piglin_brute.controllable", piglinBruteControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.piglin_brute.attributes.max-health", piglinBruteMaxHealth);
-+ set("mobs.piglin_brute.attributes.max-health", null);
-+ set("mobs.piglin_brute.attributes.max_health", oldValue);
-+ }
-+ piglinBruteMaxHealth = getDouble("mobs.piglin_brute.attributes.max_health", piglinBruteMaxHealth);
-+ piglinBruteScale = Mth.clamp(getDouble("mobs.piglin_brute.attributes.scale", piglinBruteScale), 0.0625D, 16.0D);
- }
-
- public boolean pillagerRidable = false;
- public boolean pillagerRidableInWater = true;
- public boolean pillagerControllable = true;
-+ public double pillagerMaxHealth = 24.0D;
-+ public double pillagerScale = 1.0D;
- private void pillagerSettings() {
- pillagerRidable = getBoolean("mobs.pillager.ridable", pillagerRidable);
- pillagerRidableInWater = getBoolean("mobs.pillager.ridable-in-water", pillagerRidableInWater);
- pillagerControllable = getBoolean("mobs.pillager.controllable", pillagerControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.pillager.attributes.max-health", pillagerMaxHealth);
-+ set("mobs.pillager.attributes.max-health", null);
-+ set("mobs.pillager.attributes.max_health", oldValue);
-+ }
-+ pillagerMaxHealth = getDouble("mobs.pillager.attributes.max_health", pillagerMaxHealth);
-+ pillagerScale = Mth.clamp(getDouble("mobs.pillager.attributes.scale", pillagerScale), 0.0625D, 16.0D);
- }
-
- public boolean polarBearRidable = false;
- public boolean polarBearRidableInWater = true;
- public boolean polarBearControllable = true;
-+ public double polarBearMaxHealth = 30.0D;
-+ public double polarBearScale = 1.0D;
- private void polarBearSettings() {
- polarBearRidable = getBoolean("mobs.polar_bear.ridable", polarBearRidable);
- polarBearRidableInWater = getBoolean("mobs.polar_bear.ridable-in-water", polarBearRidableInWater);
- polarBearControllable = getBoolean("mobs.polar_bear.controllable", polarBearControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.polar_bear.attributes.max-health", polarBearMaxHealth);
-+ set("mobs.polar_bear.attributes.max-health", null);
-+ set("mobs.polar_bear.attributes.max_health", oldValue);
-+ }
-+ polarBearMaxHealth = getDouble("mobs.polar_bear.attributes.max_health", polarBearMaxHealth);
-+ polarBearScale = Mth.clamp(getDouble("mobs.polar_bear.attributes.scale", polarBearScale), 0.0625D, 16.0D);
- }
-
- public boolean pufferfishRidable = false;
- public boolean pufferfishControllable = true;
-+ public double pufferfishMaxHealth = 3.0D;
-+ public double pufferfishScale = 1.0D;
- private void pufferfishSettings() {
- pufferfishRidable = getBoolean("mobs.pufferfish.ridable", pufferfishRidable);
- pufferfishControllable = getBoolean("mobs.pufferfish.controllable", pufferfishControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.pufferfish.attributes.max-health", pufferfishMaxHealth);
-+ set("mobs.pufferfish.attributes.max-health", null);
-+ set("mobs.pufferfish.attributes.max_health", oldValue);
-+ }
-+ pufferfishMaxHealth = getDouble("mobs.pufferfish.attributes.max_health", pufferfishMaxHealth);
-+ pufferfishScale = Mth.clamp(getDouble("mobs.pufferfish.attributes.scale", pufferfishScale), 0.0625D, 16.0D);
- }
-
- public boolean rabbitRidable = false;
- public boolean rabbitRidableInWater = true;
- public boolean rabbitControllable = true;
-+ public double rabbitMaxHealth = 3.0D;
-+ public double rabbitScale = 1.0D;
- private void rabbitSettings() {
- rabbitRidable = getBoolean("mobs.rabbit.ridable", rabbitRidable);
- rabbitRidableInWater = getBoolean("mobs.rabbit.ridable-in-water", rabbitRidableInWater);
- rabbitControllable = getBoolean("mobs.rabbit.controllable", rabbitControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.rabbit.attributes.max-health", rabbitMaxHealth);
-+ set("mobs.rabbit.attributes.max-health", null);
-+ set("mobs.rabbit.attributes.max_health", oldValue);
-+ }
-+ rabbitMaxHealth = getDouble("mobs.rabbit.attributes.max_health", rabbitMaxHealth);
-+ rabbitScale = Mth.clamp(getDouble("mobs.rabbit.attributes.scale", rabbitScale), 0.0625D, 16.0D);
- }
-
- public boolean ravagerRidable = false;
- public boolean ravagerRidableInWater = false;
- public boolean ravagerControllable = true;
-+ public double ravagerMaxHealth = 100.0D;
-+ public double ravagerScale = 1.0D;
- private void ravagerSettings() {
- ravagerRidable = getBoolean("mobs.ravager.ridable", ravagerRidable);
- ravagerRidableInWater = getBoolean("mobs.ravager.ridable-in-water", ravagerRidableInWater);
- ravagerControllable = getBoolean("mobs.ravager.controllable", ravagerControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.ravager.attributes.max-health", ravagerMaxHealth);
-+ set("mobs.ravager.attributes.max-health", null);
-+ set("mobs.ravager.attributes.max_health", oldValue);
-+ }
-+ ravagerMaxHealth = getDouble("mobs.ravager.attributes.max_health", ravagerMaxHealth);
-+ ravagerScale = Mth.clamp(getDouble("mobs.ravager.attributes.scale", ravagerScale), 0.0625D, 16.0D);
- }
-
- public boolean salmonRidable = false;
- public boolean salmonControllable = true;
-+ public double salmonMaxHealth = 3.0D;
-+ public double salmonScale = 1.0D;
- private void salmonSettings() {
- salmonRidable = getBoolean("mobs.salmon.ridable", salmonRidable);
- salmonControllable = getBoolean("mobs.salmon.controllable", salmonControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.salmon.attributes.max-health", salmonMaxHealth);
-+ set("mobs.salmon.attributes.max-health", null);
-+ set("mobs.salmon.attributes.max_health", oldValue);
-+ }
-+ salmonMaxHealth = getDouble("mobs.salmon.attributes.max_health", salmonMaxHealth);
-+ salmonScale = Mth.clamp(getDouble("mobs.salmon.attributes.scale", salmonScale), 0.0625D, 16.0D);
- }
-
- public boolean sheepRidable = false;
- public boolean sheepRidableInWater = true;
- public boolean sheepControllable = true;
-+ public double sheepMaxHealth = 8.0D;
-+ public double sheepScale = 1.0D;
- private void sheepSettings() {
- sheepRidable = getBoolean("mobs.sheep.ridable", sheepRidable);
- sheepRidableInWater = getBoolean("mobs.sheep.ridable-in-water", sheepRidableInWater);
- sheepControllable = getBoolean("mobs.sheep.controllable", sheepControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.sheep.attributes.max-health", sheepMaxHealth);
-+ set("mobs.sheep.attributes.max-health", null);
-+ set("mobs.sheep.attributes.max_health", oldValue);
-+ }
-+ sheepMaxHealth = getDouble("mobs.sheep.attributes.max_health", sheepMaxHealth);
-+ sheepScale = Mth.clamp(getDouble("mobs.sheep.attributes.scale", sheepScale), 0.0625D, 16.0D);
- }
-
- public boolean shulkerRidable = false;
- public boolean shulkerRidableInWater = true;
- public boolean shulkerControllable = true;
-+ public double shulkerMaxHealth = 30.0D;
-+ public double shulkerScale = 1.0D;
- private void shulkerSettings() {
- shulkerRidable = getBoolean("mobs.shulker.ridable", shulkerRidable);
- shulkerRidableInWater = getBoolean("mobs.shulker.ridable-in-water", shulkerRidableInWater);
- shulkerControllable = getBoolean("mobs.shulker.controllable", shulkerControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.shulker.attributes.max-health", shulkerMaxHealth);
-+ set("mobs.shulker.attributes.max-health", null);
-+ set("mobs.shulker.attributes.max_health", oldValue);
-+ }
-+ shulkerMaxHealth = getDouble("mobs.shulker.attributes.max_health", shulkerMaxHealth);
-+ shulkerScale = Mth.clamp(getDouble("mobs.shulker.attributes.scale", shulkerScale), 0.0625D, Shulker.MAX_SCALE);
- }
-
- public boolean silverfishRidable = false;
- public boolean silverfishRidableInWater = true;
- public boolean silverfishControllable = true;
-+ public double silverfishMaxHealth = 8.0D;
-+ public double silverfishScale = 1.0D;
-+ public double silverfishMovementSpeed = 0.25D;
-+ public double silverfishAttackDamage = 1.0D;
- private void silverfishSettings() {
- silverfishRidable = getBoolean("mobs.silverfish.ridable", silverfishRidable);
- silverfishRidableInWater = getBoolean("mobs.silverfish.ridable-in-water", silverfishRidableInWater);
- silverfishControllable = getBoolean("mobs.silverfish.controllable", silverfishControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.silverfish.attributes.max-health", silverfishMaxHealth);
-+ set("mobs.silverfish.attributes.max-health", null);
-+ set("mobs.silverfish.attributes.max_health", oldValue);
-+ }
-+ silverfishMaxHealth = getDouble("mobs.silverfish.attributes.max_health", silverfishMaxHealth);
-+ silverfishScale = Mth.clamp(getDouble("mobs.silverfish.attributes.scale", silverfishScale), 0.0625D, 16.0D);
-+ silverfishMovementSpeed = getDouble("mobs.silverfish.attributes.movement_speed", silverfishMovementSpeed);
-+ silverfishAttackDamage = getDouble("mobs.silverfish.attributes.attack_damage", silverfishAttackDamage);
- }
-
- public boolean skeletonRidable = false;
- public boolean skeletonRidableInWater = true;
- public boolean skeletonControllable = true;
-+ public double skeletonMaxHealth = 20.0D;
-+ public double skeletonScale = 1.0D;
- private void skeletonSettings() {
- skeletonRidable = getBoolean("mobs.skeleton.ridable", skeletonRidable);
- skeletonRidableInWater = getBoolean("mobs.skeleton.ridable-in-water", skeletonRidableInWater);
- skeletonControllable = getBoolean("mobs.skeleton.controllable", skeletonControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.skeleton.attributes.max-health", skeletonMaxHealth);
-+ set("mobs.skeleton.attributes.max-health", null);
-+ set("mobs.skeleton.attributes.max_health", oldValue);
-+ }
-+ skeletonMaxHealth = getDouble("mobs.skeleton.attributes.max_health", skeletonMaxHealth);
-+ skeletonScale = Mth.clamp(getDouble("mobs.skeleton.attributes.scale", skeletonScale), 0.0625D, 16.0D);
- }
-
- public boolean skeletonHorseRidable = false;
- public boolean skeletonHorseRidableInWater = true;
- public boolean skeletonHorseCanSwim = false;
-+ public double skeletonHorseMaxHealthMin = 15.0D;
-+ public double skeletonHorseMaxHealthMax = 15.0D;
-+ public double skeletonHorseJumpStrengthMin = 0.4D;
-+ public double skeletonHorseJumpStrengthMax = 1.0D;
-+ public double skeletonHorseMovementSpeedMin = 0.2D;
-+ public double skeletonHorseMovementSpeedMax = 0.2D;
- private void skeletonHorseSettings() {
- skeletonHorseRidable = getBoolean("mobs.skeleton_horse.ridable", skeletonHorseRidable);
- skeletonHorseRidableInWater = getBoolean("mobs.skeleton_horse.ridable-in-water", skeletonHorseRidableInWater);
- skeletonHorseCanSwim = getBoolean("mobs.skeleton_horse.can-swim", skeletonHorseCanSwim);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.skeleton_horse.attributes.max-health", skeletonHorseMaxHealthMin);
-+ set("mobs.skeleton_horse.attributes.max-health", null);
-+ set("mobs.skeleton_horse.attributes.max_health.min", oldValue);
-+ set("mobs.skeleton_horse.attributes.max_health.max", oldValue);
-+ }
-+ skeletonHorseMaxHealthMin = getDouble("mobs.skeleton_horse.attributes.max_health.min", skeletonHorseMaxHealthMin);
-+ skeletonHorseMaxHealthMax = getDouble("mobs.skeleton_horse.attributes.max_health.max", skeletonHorseMaxHealthMax);
-+ skeletonHorseJumpStrengthMin = getDouble("mobs.skeleton_horse.attributes.jump_strength.min", skeletonHorseJumpStrengthMin);
-+ skeletonHorseJumpStrengthMax = getDouble("mobs.skeleton_horse.attributes.jump_strength.max", skeletonHorseJumpStrengthMax);
-+ skeletonHorseMovementSpeedMin = getDouble("mobs.skeleton_horse.attributes.movement_speed.min", skeletonHorseMovementSpeedMin);
-+ skeletonHorseMovementSpeedMax = getDouble("mobs.skeleton_horse.attributes.movement_speed.max", skeletonHorseMovementSpeedMax);
- }
-
- public boolean slimeRidable = false;
- public boolean slimeRidableInWater = true;
- public boolean slimeControllable = true;
-+ public String slimeMaxHealth = "size * size";
-+ public String slimeAttackDamage = "size";
-+ public Map slimeMaxHealthCache = new HashMap<>();
-+ public Map slimeAttackDamageCache = new HashMap<>();
- private void slimeSettings() {
- slimeRidable = getBoolean("mobs.slime.ridable", slimeRidable);
- slimeRidableInWater = getBoolean("mobs.slime.ridable-in-water", slimeRidableInWater);
- slimeControllable = getBoolean("mobs.slime.controllable", slimeControllable);
-+ if (PurpurConfig.version < 10) {
-+ String oldValue = getString("mobs.slime.attributes.max-health", slimeMaxHealth);
-+ set("mobs.slime.attributes.max-health", null);
-+ set("mobs.slime.attributes.max_health", oldValue);
-+ }
-+ slimeMaxHealth = getString("mobs.slime.attributes.max_health", slimeMaxHealth);
-+ slimeAttackDamage = getString("mobs.slime.attributes.attack_damage", slimeAttackDamage);
-+ slimeMaxHealthCache.clear();
-+ slimeAttackDamageCache.clear();
- }
-
- public boolean snowGolemRidable = false;
- public boolean snowGolemRidableInWater = true;
- public boolean snowGolemControllable = true;
- public boolean snowGolemLeaveTrailWhenRidden = false;
-+ public double snowGolemMaxHealth = 4.0D;
-+ public double snowGolemScale = 1.0D;
- private void snowGolemSettings() {
- snowGolemRidable = getBoolean("mobs.snow_golem.ridable", snowGolemRidable);
- snowGolemRidableInWater = getBoolean("mobs.snow_golem.ridable-in-water", snowGolemRidableInWater);
- snowGolemControllable = getBoolean("mobs.snow_golem.controllable", snowGolemControllable);
- snowGolemLeaveTrailWhenRidden = getBoolean("mobs.snow_golem.leave-trail-when-ridden", snowGolemLeaveTrailWhenRidden);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.snow_golem.attributes.max-health", snowGolemMaxHealth);
-+ set("mobs.snow_golem.attributes.max-health", null);
-+ set("mobs.snow_golem.attributes.max_health", oldValue);
-+ }
-+ snowGolemMaxHealth = getDouble("mobs.snow_golem.attributes.max_health", snowGolemMaxHealth);
-+ snowGolemScale = Mth.clamp(getDouble("mobs.snow_golem.attributes.scale", snowGolemScale), 0.0625D, 16.0D);
- }
-
- public boolean snifferRidable = false;
- public boolean snifferRidableInWater = true;
- public boolean snifferControllable = true;
-+ public double snifferMaxHealth = 14.0D;
-+ public double snifferScale = 1.0D;
- private void snifferSettings() {
- snifferRidable = getBoolean("mobs.sniffer.ridable", snifferRidable);
- snifferRidableInWater = getBoolean("mobs.sniffer.ridable-in-water", snifferRidableInWater);
- snifferControllable = getBoolean("mobs.sniffer.controllable", snifferControllable);
-+ snifferMaxHealth = getDouble("mobs.sniffer.attributes.max_health", snifferMaxHealth);
-+ snifferScale = Mth.clamp(getDouble("mobs.sniffer.attributes.scale", snifferScale), 0.0625D, 16.0D);
- }
-
- public boolean squidRidable = false;
- public boolean squidControllable = true;
-+ public double squidMaxHealth = 10.0D;
-+ public double squidScale = 1.0D;
- private void squidSettings() {
- squidRidable = getBoolean("mobs.squid.ridable", squidRidable);
- squidControllable = getBoolean("mobs.squid.controllable", squidControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.squid.attributes.max-health", squidMaxHealth);
-+ set("mobs.squid.attributes.max-health", null);
-+ set("mobs.squid.attributes.max_health", oldValue);
-+ }
-+ squidMaxHealth = getDouble("mobs.squid.attributes.max_health", squidMaxHealth);
-+ squidScale = Mth.clamp(getDouble("mobs.squid.attributes.scale", squidScale), 0.0625D, 16.0D);
- }
-
- public boolean spiderRidable = false;
- public boolean spiderRidableInWater = false;
- public boolean spiderControllable = true;
-+ public double spiderMaxHealth = 16.0D;
-+ public double spiderScale = 1.0D;
- private void spiderSettings() {
- spiderRidable = getBoolean("mobs.spider.ridable", spiderRidable);
- spiderRidableInWater = getBoolean("mobs.spider.ridable-in-water", spiderRidableInWater);
- spiderControllable = getBoolean("mobs.spider.controllable", spiderControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.spider.attributes.max-health", spiderMaxHealth);
-+ set("mobs.spider.attributes.max-health", null);
-+ set("mobs.spider.attributes.max_health", oldValue);
-+ }
-+ spiderMaxHealth = getDouble("mobs.spider.attributes.max_health", spiderMaxHealth);
-+ spiderScale = Mth.clamp(getDouble("mobs.spider.attributes.scale", spiderScale), 0.0625D, 16.0D);
- }
-
- public boolean strayRidable = false;
- public boolean strayRidableInWater = true;
- public boolean strayControllable = true;
-+ public double strayMaxHealth = 20.0D;
-+ public double strayScale = 1.0D;
- private void straySettings() {
- strayRidable = getBoolean("mobs.stray.ridable", strayRidable);
- strayRidableInWater = getBoolean("mobs.stray.ridable-in-water", strayRidableInWater);
- strayControllable = getBoolean("mobs.stray.controllable", strayControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.stray.attributes.max-health", strayMaxHealth);
-+ set("mobs.stray.attributes.max-health", null);
-+ set("mobs.stray.attributes.max_health", oldValue);
-+ }
-+ strayMaxHealth = getDouble("mobs.stray.attributes.max_health", strayMaxHealth);
-+ strayScale = Mth.clamp(getDouble("mobs.stray.attributes.scale", strayScale), 0.0625D, 16.0D);
- }
-
- public boolean striderRidable = false;
- public boolean striderRidableInWater = false;
- public boolean striderControllable = true;
-+ public double striderMaxHealth = 20.0D;
-+ public double striderScale = 1.0D;
- private void striderSettings() {
- striderRidable = getBoolean("mobs.strider.ridable", striderRidable);
- striderRidableInWater = getBoolean("mobs.strider.ridable-in-water", striderRidableInWater);
- striderControllable = getBoolean("mobs.strider.controllable", striderControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.strider.attributes.max-health", striderMaxHealth);
-+ set("mobs.strider.attributes.max-health", null);
-+ set("mobs.strider.attributes.max_health", oldValue);
-+ }
-+ striderMaxHealth = getDouble("mobs.strider.attributes.max_health", striderMaxHealth);
-+ striderScale = Mth.clamp(getDouble("mobs.strider.attributes.scale", striderScale), 0.0625D, 16.0D);
- }
-
- public boolean tadpoleRidable = false;
-@@ -688,64 +1323,137 @@ public class PurpurWorldConfig {
- public boolean traderLlamaRidable = false;
- public boolean traderLlamaRidableInWater = false;
- public boolean traderLlamaControllable = true;
-+ public double traderLlamaMaxHealthMin = 15.0D;
-+ public double traderLlamaMaxHealthMax = 30.0D;
-+ public double traderLlamaJumpStrengthMin = 0.5D;
-+ public double traderLlamaJumpStrengthMax = 0.5D;
-+ public double traderLlamaMovementSpeedMin = 0.175D;
-+ public double traderLlamaMovementSpeedMax = 0.175D;
- private void traderLlamaSettings() {
- traderLlamaRidable = getBoolean("mobs.trader_llama.ridable", traderLlamaRidable);
- traderLlamaRidableInWater = getBoolean("mobs.trader_llama.ridable-in-water", traderLlamaRidableInWater);
- traderLlamaControllable = getBoolean("mobs.trader_llama.controllable", traderLlamaControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldMin = getDouble("mobs.trader_llama.attributes.max-health.min", traderLlamaMaxHealthMin);
-+ double oldMax = getDouble("mobs.trader_llama.attributes.max-health.max", traderLlamaMaxHealthMax);
-+ set("mobs.trader_llama.attributes.max-health", null);
-+ set("mobs.trader_llama.attributes.max_health.min", oldMin);
-+ set("mobs.trader_llama.attributes.max_health.max", oldMax);
-+ }
-+ traderLlamaMaxHealthMin = getDouble("mobs.trader_llama.attributes.max_health.min", traderLlamaMaxHealthMin);
-+ traderLlamaMaxHealthMax = getDouble("mobs.trader_llama.attributes.max_health.max", traderLlamaMaxHealthMax);
-+ traderLlamaJumpStrengthMin = getDouble("mobs.trader_llama.attributes.jump_strength.min", traderLlamaJumpStrengthMin);
-+ traderLlamaJumpStrengthMax = getDouble("mobs.trader_llama.attributes.jump_strength.max", traderLlamaJumpStrengthMax);
-+ traderLlamaMovementSpeedMin = getDouble("mobs.trader_llama.attributes.movement_speed.min", traderLlamaMovementSpeedMin);
-+ traderLlamaMovementSpeedMax = getDouble("mobs.trader_llama.attributes.movement_speed.max", traderLlamaMovementSpeedMax);
- }
-
- public boolean tropicalFishRidable = false;
- public boolean tropicalFishControllable = true;
-+ public double tropicalFishMaxHealth = 3.0D;
-+ public double tropicalFishScale = 1.0D;
- private void tropicalFishSettings() {
- tropicalFishRidable = getBoolean("mobs.tropical_fish.ridable", tropicalFishRidable);
- tropicalFishControllable = getBoolean("mobs.tropical_fish.controllable", tropicalFishControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.tropical_fish.attributes.max-health", tropicalFishMaxHealth);
-+ set("mobs.tropical_fish.attributes.max-health", null);
-+ set("mobs.tropical_fish.attributes.max_health", oldValue);
-+ }
-+ tropicalFishMaxHealth = getDouble("mobs.tropical_fish.attributes.max_health", tropicalFishMaxHealth);
-+ tropicalFishScale = Mth.clamp(getDouble("mobs.tropical_fish.attributes.scale", tropicalFishScale), 0.0625D, 16.0D);
- }
-
- public boolean turtleRidable = false;
- public boolean turtleRidableInWater = true;
- public boolean turtleControllable = true;
-+ public double turtleMaxHealth = 30.0D;
-+ public double turtleScale = 1.0D;
- private void turtleSettings() {
- turtleRidable = getBoolean("mobs.turtle.ridable", turtleRidable);
- turtleRidableInWater = getBoolean("mobs.turtle.ridable-in-water", turtleRidableInWater);
- turtleControllable = getBoolean("mobs.turtle.controllable", turtleControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.turtle.attributes.max-health", turtleMaxHealth);
-+ set("mobs.turtle.attributes.max-health", null);
-+ set("mobs.turtle.attributes.max_health", oldValue);
-+ }
-+ turtleMaxHealth = getDouble("mobs.turtle.attributes.max_health", turtleMaxHealth);
-+ turtleScale = Mth.clamp(getDouble("mobs.turtle.attributes.scale", turtleScale), 0.0625D, 16.0D);
- }
-
- public boolean vexRidable = false;
- public boolean vexRidableInWater = true;
- public boolean vexControllable = true;
- public double vexMaxY = 320D;
-+ public double vexMaxHealth = 14.0D;
-+ public double vexScale = 1.0D;
- private void vexSettings() {
- vexRidable = getBoolean("mobs.vex.ridable", vexRidable);
- vexRidableInWater = getBoolean("mobs.vex.ridable-in-water", vexRidableInWater);
- vexControllable = getBoolean("mobs.vex.controllable", vexControllable);
- vexMaxY = getDouble("mobs.vex.ridable-max-y", vexMaxY);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.vex.attributes.max-health", vexMaxHealth);
-+ set("mobs.vex.attributes.max-health", null);
-+ set("mobs.vex.attributes.max_health", oldValue);
-+ }
-+ vexMaxHealth = getDouble("mobs.vex.attributes.max_health", vexMaxHealth);
-+ vexScale = Mth.clamp(getDouble("mobs.vex.attributes.scale", vexScale), 0.0625D, 16.0D);
- }
-
- public boolean villagerRidable = false;
- public boolean villagerRidableInWater = true;
- public boolean villagerControllable = true;
-+ public double villagerMaxHealth = 20.0D;
-+ public double villagerScale = 1.0D;
- private void villagerSettings() {
- villagerRidable = getBoolean("mobs.villager.ridable", villagerRidable);
- villagerRidableInWater = getBoolean("mobs.villager.ridable-in-water", villagerRidableInWater);
- villagerControllable = getBoolean("mobs.villager.controllable", villagerControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.villager.attributes.max-health", villagerMaxHealth);
-+ set("mobs.villager.attributes.max-health", null);
-+ set("mobs.villager.attributes.max_health", oldValue);
-+ }
-+ villagerMaxHealth = getDouble("mobs.villager.attributes.max_health", villagerMaxHealth);
-+ villagerScale = Mth.clamp(getDouble("mobs.villager.attributes.scale", villagerScale), 0.0625D, 16.0D);
- }
-
- public boolean vindicatorRidable = false;
- public boolean vindicatorRidableInWater = true;
- public boolean vindicatorControllable = true;
-+ public double vindicatorMaxHealth = 24.0D;
-+ public double vindicatorScale = 1.0D;
- private void vindicatorSettings() {
- vindicatorRidable = getBoolean("mobs.vindicator.ridable", vindicatorRidable);
- vindicatorRidableInWater = getBoolean("mobs.vindicator.ridable-in-water", vindicatorRidableInWater);
- vindicatorControllable = getBoolean("mobs.vindicator.controllable", vindicatorControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.vindicator.attributes.max-health", vindicatorMaxHealth);
-+ set("mobs.vindicator.attributes.max-health", null);
-+ set("mobs.vindicator.attributes.max_health", oldValue);
-+ }
-+ vindicatorMaxHealth = getDouble("mobs.vindicator.attributes.max_health", vindicatorMaxHealth);
-+ vindicatorScale = Mth.clamp(getDouble("mobs.vindicator.attributes.scale", vindicatorScale), 0.0625D, 16.0D);
- }
-
- public boolean wanderingTraderRidable = false;
- public boolean wanderingTraderRidableInWater = true;
- public boolean wanderingTraderControllable = true;
-+ public double wanderingTraderMaxHealth = 20.0D;
-+ public double wanderingTraderScale = 1.0D;
- private void wanderingTraderSettings() {
- wanderingTraderRidable = getBoolean("mobs.wandering_trader.ridable", wanderingTraderRidable);
- wanderingTraderRidableInWater = getBoolean("mobs.wandering_trader.ridable-in-water", wanderingTraderRidableInWater);
- wanderingTraderControllable = getBoolean("mobs.wandering_trader.controllable", wanderingTraderControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.wandering_trader.attributes.max-health", wanderingTraderMaxHealth);
-+ set("mobs.wandering_trader.attributes.max-health", null);
-+ set("mobs.wandering_trader.attributes.max_health", oldValue);
-+ }
-+ wanderingTraderMaxHealth = getDouble("mobs.wandering_trader.attributes.max_health", wanderingTraderMaxHealth);
-+ wanderingTraderScale = Mth.clamp(getDouble("mobs.wandering_trader.attributes.scale", wanderingTraderScale), 0.0625D, 16.0D);
- }
-
- public boolean wardenRidable = false;
-@@ -760,83 +1468,183 @@ public class PurpurWorldConfig {
- public boolean witchRidable = false;
- public boolean witchRidableInWater = true;
- public boolean witchControllable = true;
-+ public double witchMaxHealth = 26.0D;
-+ public double witchScale = 1.0D;
- private void witchSettings() {
- witchRidable = getBoolean("mobs.witch.ridable", witchRidable);
- witchRidableInWater = getBoolean("mobs.witch.ridable-in-water", witchRidableInWater);
- witchControllable = getBoolean("mobs.witch.controllable", witchControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.witch.attributes.max-health", witchMaxHealth);
-+ set("mobs.witch.attributes.max-health", null);
-+ set("mobs.witch.attributes.max_health", oldValue);
-+ }
-+ witchMaxHealth = getDouble("mobs.witch.attributes.max_health", witchMaxHealth);
-+ witchScale = Mth.clamp(getDouble("mobs.witch.attributes.scale", witchScale), 0.0625D, 16.0D);
- }
-
- public boolean witherRidable = false;
- public boolean witherRidableInWater = true;
- public boolean witherControllable = true;
- public double witherMaxY = 320D;
-+ public double witherMaxHealth = 300.0D;
-+ public double witherScale = 1.0D;
- private void witherSettings() {
- witherRidable = getBoolean("mobs.wither.ridable", witherRidable);
- witherRidableInWater = getBoolean("mobs.wither.ridable-in-water", witherRidableInWater);
- witherControllable = getBoolean("mobs.wither.controllable", witherControllable);
- witherMaxY = getDouble("mobs.wither.ridable-max-y", witherMaxY);
-+ if (PurpurConfig.version < 8) {
-+ double oldValue = getDouble("mobs.wither.max-health", witherMaxHealth);
-+ set("mobs.wither.max_health", null);
-+ set("mobs.wither.attributes.max-health", oldValue);
-+ } else if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.wither.attributes.max-health", witherMaxHealth);
-+ set("mobs.wither.attributes.max-health", null);
-+ set("mobs.wither.attributes.max_health", oldValue);
-+ }
-+ witherMaxHealth = getDouble("mobs.wither.attributes.max_health", witherMaxHealth);
-+ witherScale = Mth.clamp(getDouble("mobs.wither.attributes.scale", witherScale), 0.0625D, 16.0D);
- }
-
- public boolean witherSkeletonRidable = false;
- public boolean witherSkeletonRidableInWater = true;
- public boolean witherSkeletonControllable = true;
-+ public double witherSkeletonMaxHealth = 20.0D;
-+ public double witherSkeletonScale = 1.0D;
- private void witherSkeletonSettings() {
- witherSkeletonRidable = getBoolean("mobs.wither_skeleton.ridable", witherSkeletonRidable);
- witherSkeletonRidableInWater = getBoolean("mobs.wither_skeleton.ridable-in-water", witherSkeletonRidableInWater);
- witherSkeletonControllable = getBoolean("mobs.wither_skeleton.controllable", witherSkeletonControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.wither_skeleton.attributes.max-health", witherSkeletonMaxHealth);
-+ set("mobs.wither_skeleton.attributes.max-health", null);
-+ set("mobs.wither_skeleton.attributes.max_health", oldValue);
-+ }
-+ witherSkeletonMaxHealth = getDouble("mobs.wither_skeleton.attributes.max_health", witherSkeletonMaxHealth);
-+ witherSkeletonScale = Mth.clamp(getDouble("mobs.wither_skeleton.attributes.scale", witherSkeletonScale), 0.0625D, 16.0D);
- }
-
- public boolean wolfRidable = false;
- public boolean wolfRidableInWater = true;
- public boolean wolfControllable = true;
-+ public double wolfMaxHealth = 8.0D;
-+ public double wolfScale = 1.0D;
- private void wolfSettings() {
- wolfRidable = getBoolean("mobs.wolf.ridable", wolfRidable);
- wolfRidableInWater = getBoolean("mobs.wolf.ridable-in-water", wolfRidableInWater);
- wolfControllable = getBoolean("mobs.wolf.controllable", wolfControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.wolf.attributes.max-health", wolfMaxHealth);
-+ set("mobs.wolf.attributes.max-health", null);
-+ set("mobs.wolf.attributes.max_health", oldValue);
-+ }
-+ wolfMaxHealth = getDouble("mobs.wolf.attributes.max_health", wolfMaxHealth);
-+ wolfScale = Mth.clamp(getDouble("mobs.wolf.attributes.scale", wolfScale), 0.0625D, 16.0D);
- }
-
- public boolean zoglinRidable = false;
- public boolean zoglinRidableInWater = true;
- public boolean zoglinControllable = true;
-+ public double zoglinMaxHealth = 40.0D;
-+ public double zoglinScale = 1.0D;
- private void zoglinSettings() {
- zoglinRidable = getBoolean("mobs.zoglin.ridable", zoglinRidable);
- zoglinRidableInWater = getBoolean("mobs.zoglin.ridable-in-water", zoglinRidableInWater);
- zoglinControllable = getBoolean("mobs.zoglin.controllable", zoglinControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.zoglin.attributes.max-health", zoglinMaxHealth);
-+ set("mobs.zoglin.attributes.max-health", null);
-+ set("mobs.zoglin.attributes.max_health", oldValue);
-+ }
-+ zoglinMaxHealth = getDouble("mobs.zoglin.attributes.max_health", zoglinMaxHealth);
-+ zoglinScale = Mth.clamp(getDouble("mobs.zoglin.attributes.scale", zoglinScale), 0.0625D, 16.0D);
- }
-
- public boolean zombieRidable = false;
- public boolean zombieRidableInWater = true;
- public boolean zombieControllable = true;
-+ public double zombieMaxHealth = 20.0D;
-+ public double zombieScale = 1.0D;
-+ public double zombieSpawnReinforcements = 0.1D;
- private void zombieSettings() {
- zombieRidable = getBoolean("mobs.zombie.ridable", zombieRidable);
- zombieRidableInWater = getBoolean("mobs.zombie.ridable-in-water", zombieRidableInWater);
- zombieControllable = getBoolean("mobs.zombie.controllable", zombieControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.zombie.attributes.max-health", zombieMaxHealth);
-+ set("mobs.zombie.attributes.max-health", null);
-+ set("mobs.zombie.attributes.max_health", oldValue);
-+ }
-+ zombieMaxHealth = getDouble("mobs.zombie.attributes.max_health", zombieMaxHealth);
-+ zombieScale = Mth.clamp(getDouble("mobs.zombie.attributes.scale", zombieScale), 0.0625D, 16.0D);
-+ zombieSpawnReinforcements = getDouble("mobs.zombie.attributes.spawn_reinforcements", zombieSpawnReinforcements);
- }
-
- public boolean zombieHorseRidable = false;
- public boolean zombieHorseRidableInWater = false;
- public boolean zombieHorseCanSwim = false;
-+ public double zombieHorseMaxHealthMin = 15.0D;
-+ public double zombieHorseMaxHealthMax = 15.0D;
-+ public double zombieHorseJumpStrengthMin = 0.4D;
-+ public double zombieHorseJumpStrengthMax = 1.0D;
-+ public double zombieHorseMovementSpeedMin = 0.2D;
-+ public double zombieHorseMovementSpeedMax = 0.2D;
- private void zombieHorseSettings() {
- zombieHorseRidable = getBoolean("mobs.zombie_horse.ridable", zombieHorseRidable);
- zombieHorseRidableInWater = getBoolean("mobs.zombie_horse.ridable-in-water", zombieHorseRidableInWater);
- zombieHorseCanSwim = getBoolean("mobs.zombie_horse.can-swim", zombieHorseCanSwim);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.zombie_horse.attributes.max-health", zombieHorseMaxHealthMin);
-+ set("mobs.zombie_horse.attributes.max-health", null);
-+ set("mobs.zombie_horse.attributes.max_health.min", oldValue);
-+ set("mobs.zombie_horse.attributes.max_health.max", oldValue);
-+ }
-+ zombieHorseMaxHealthMin = getDouble("mobs.zombie_horse.attributes.max_health.min", zombieHorseMaxHealthMin);
-+ zombieHorseMaxHealthMax = getDouble("mobs.zombie_horse.attributes.max_health.max", zombieHorseMaxHealthMax);
-+ zombieHorseJumpStrengthMin = getDouble("mobs.zombie_horse.attributes.jump_strength.min", zombieHorseJumpStrengthMin);
-+ zombieHorseJumpStrengthMax = getDouble("mobs.zombie_horse.attributes.jump_strength.max", zombieHorseJumpStrengthMax);
-+ zombieHorseMovementSpeedMin = getDouble("mobs.zombie_horse.attributes.movement_speed.min", zombieHorseMovementSpeedMin);
-+ zombieHorseMovementSpeedMax = getDouble("mobs.zombie_horse.attributes.movement_speed.max", zombieHorseMovementSpeedMax);
- }
-
- public boolean zombieVillagerRidable = false;
- public boolean zombieVillagerRidableInWater = true;
- public boolean zombieVillagerControllable = true;
-+ public double zombieVillagerMaxHealth = 20.0D;
-+ public double zombieVillagerScale = 1.0D;
-+ public double zombieVillagerSpawnReinforcements = 0.1D;
- private void zombieVillagerSettings() {
- zombieVillagerRidable = getBoolean("mobs.zombie_villager.ridable", zombieVillagerRidable);
- zombieVillagerRidableInWater = getBoolean("mobs.zombie_villager.ridable-in-water", zombieVillagerRidableInWater);
- zombieVillagerControllable = getBoolean("mobs.zombie_villager.controllable", zombieVillagerControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.zombie_villager.attributes.max-health", zombieVillagerMaxHealth);
-+ set("mobs.zombie_villager.attributes.max-health", null);
-+ set("mobs.zombie_villager.attributes.max_health", oldValue);
-+ }
-+ zombieVillagerMaxHealth = getDouble("mobs.zombie_villager.attributes.max_health", zombieVillagerMaxHealth);
-+ zombieVillagerScale = Mth.clamp(getDouble("mobs.zombie_villager.attributes.scale", zombieVillagerScale), 0.0625D, 16.0D);
-+ zombieVillagerSpawnReinforcements = getDouble("mobs.zombie_villager.attributes.spawn_reinforcements", zombieVillagerSpawnReinforcements);
- }
-
- public boolean zombifiedPiglinRidable = false;
- public boolean zombifiedPiglinRidableInWater = true;
- public boolean zombifiedPiglinControllable = true;
-+ public double zombifiedPiglinMaxHealth = 20.0D;
-+ public double zombifiedPiglinScale = 1.0D;
-+ public double zombifiedPiglinSpawnReinforcements = 0.0D;
- private void zombifiedPiglinSettings() {
- zombifiedPiglinRidable = getBoolean("mobs.zombified_piglin.ridable", zombifiedPiglinRidable);
- zombifiedPiglinRidableInWater = getBoolean("mobs.zombified_piglin.ridable-in-water", zombifiedPiglinRidableInWater);
- zombifiedPiglinControllable = getBoolean("mobs.zombified_piglin.controllable", zombifiedPiglinControllable);
-+ if (PurpurConfig.version < 10) {
-+ double oldValue = getDouble("mobs.zombified_piglin.attributes.max-health", zombifiedPiglinMaxHealth);
-+ set("mobs.zombified_piglin.attributes.max-health", null);
-+ set("mobs.zombified_piglin.attributes.max_health", oldValue);
-+ }
-+ zombifiedPiglinMaxHealth = getDouble("mobs.zombified_piglin.attributes.max_health", zombifiedPiglinMaxHealth);
-+ zombifiedPiglinScale = Mth.clamp(getDouble("mobs.zombified_piglin.attributes.scale", zombifiedPiglinScale), 0.0625D, 16.0D);
-+ zombifiedPiglinSpawnReinforcements = getDouble("mobs.zombified_piglin.attributes.spawn_reinforcements", zombifiedPiglinSpawnReinforcements);
- }
- }
diff --git a/patches/server/0009-Barrels-and-enderchests-6-rows.patch b/patches/server/0009-Barrels-and-enderchests-6-rows.patch
deleted file mode 100644
index 49b38c91d7..0000000000
--- a/patches/server/0009-Barrels-and-enderchests-6-rows.patch
+++ /dev/null
@@ -1,298 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: William Blake Galbreath
-Date: Thu, 23 May 2019 21:50:37 -0500
-Subject: [PATCH] Barrels and enderchests 6 rows
-
-
-diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
-index 98e803eaf5ce4c773f35fd752c21c7176707427c..b9136fcec3f36549ddd572cbb8cd01ecae45590f 100644
---- a/src/main/java/net/minecraft/server/players/PlayerList.java
-+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
-@@ -1116,6 +1116,27 @@ public abstract class PlayerList {
- player.getBukkitEntity().recalculatePermissions(); // CraftBukkit
- this.server.getCommands().sendCommands(player);
- } // Paper - Add sendOpLevel API
-+
-+ // Purpur start
-+ if (org.purpurmc.purpur.PurpurConfig.enderChestSixRows && org.purpurmc.purpur.PurpurConfig.enderChestPermissionRows) {
-+ org.bukkit.craftbukkit.entity.CraftHumanEntity bukkit = player.getBukkitEntity();
-+ if (bukkit.hasPermission("purpur.enderchest.rows.six")) {
-+ player.sixRowEnderchestSlotCount = 54;
-+ } else if (bukkit.hasPermission("purpur.enderchest.rows.five")) {
-+ player.sixRowEnderchestSlotCount = 45;
-+ } else if (bukkit.hasPermission("purpur.enderchest.rows.four")) {
-+ player.sixRowEnderchestSlotCount = 36;
-+ } else if (bukkit.hasPermission("purpur.enderchest.rows.three")) {
-+ player.sixRowEnderchestSlotCount = 27;
-+ } else if (bukkit.hasPermission("purpur.enderchest.rows.two")) {
-+ player.sixRowEnderchestSlotCount = 18;
-+ } else if (bukkit.hasPermission("purpur.enderchest.rows.one")) {
-+ player.sixRowEnderchestSlotCount = 9;
-+ }
-+ } else {
-+ player.sixRowEnderchestSlotCount = -1;
-+ }
-+ //Purpur end
- }
-
- public boolean isWhiteListed(GameProfile profile) {
-diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java
-index 28a4cf814ec4b34dce883ba4f24ca55008c8f6c1..87c6378104ff47549c751e09afb6cfcd9b8dcf5d 100644
---- a/src/main/java/net/minecraft/world/entity/player/Player.java
-+++ b/src/main/java/net/minecraft/world/entity/player/Player.java
-@@ -200,6 +200,7 @@ public abstract class Player extends LivingEntity {
- private int currentImpulseContextResetGraceTime;
- public boolean affectsSpawning = true; // Paper - Affects Spawning API
- public net.kyori.adventure.util.TriState flyingFallDamage = net.kyori.adventure.util.TriState.NOT_SET; // Paper - flying fall damage
-+ public int sixRowEnderchestSlotCount = -1; // Purpur
-
- // CraftBukkit start
- public boolean fauxSleeping;
-diff --git a/src/main/java/net/minecraft/world/inventory/ChestMenu.java b/src/main/java/net/minecraft/world/inventory/ChestMenu.java
-index 48a6b6136ac3414ca735f93a14b1a8d76210603c..27321b07cd04814bc1ff720c65770d7755625bb6 100644
---- a/src/main/java/net/minecraft/world/inventory/ChestMenu.java
-+++ b/src/main/java/net/minecraft/world/inventory/ChestMenu.java
-@@ -66,10 +66,30 @@ public class ChestMenu extends AbstractContainerMenu {
- return new ChestMenu(MenuType.GENERIC_9x6, syncId, playerInventory, 6);
- }
-
-+ // Purpur start
-+ public static ChestMenu oneRow(int syncId, Inventory playerInventory, Container inventory) {
-+ return new ChestMenu(MenuType.GENERIC_9x1, syncId, playerInventory, inventory, 1);
-+ }
-+
-+ public static ChestMenu twoRows(int syncId, Inventory playerInventory, Container inventory) {
-+ return new ChestMenu(MenuType.GENERIC_9x2, syncId, playerInventory, inventory, 2);
-+ }
-+ // Purpur end
-+
- public static ChestMenu threeRows(int syncId, Inventory playerInventory, Container inventory) {
- return new ChestMenu(MenuType.GENERIC_9x3, syncId, playerInventory, inventory, 3);
- }
-
-+ // Purpur start
-+ public static ChestMenu fourRows(int syncId, Inventory playerInventory, Container inventory) {
-+ return new ChestMenu(MenuType.GENERIC_9x4, syncId, playerInventory, inventory, 4);
-+ }
-+
-+ public static ChestMenu fiveRows(int syncId, Inventory playerInventory, Container inventory) {
-+ return new ChestMenu(MenuType.GENERIC_9x5, syncId, playerInventory, inventory, 5);
-+ }
-+ // Purpur end
-+
- public static ChestMenu sixRows(int syncId, Inventory playerInventory, Container inventory) {
- return new ChestMenu(MenuType.GENERIC_9x6, syncId, playerInventory, inventory, 6);
- }
-diff --git a/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java b/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java
-index a15d5ff872dbd77f3c3145e0328f3d02e431ff8c..1dcf36d502990d32fc4cd3ea69c3ea334baed69a 100644
---- a/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java
-+++ b/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java
-@@ -31,11 +31,18 @@ public class PlayerEnderChestContainer extends SimpleContainer {
- }
-
- public PlayerEnderChestContainer(Player owner) {
-- super(27);
-+ super(org.purpurmc.purpur.PurpurConfig.enderChestSixRows ? 54 : 27); // Purpur
- this.owner = owner;
- // CraftBukkit end
- }
-
-+ // Purpur start
-+ @Override
-+ public int getContainerSize() {
-+ return owner.sixRowEnderchestSlotCount < 0 ? super.getContainerSize() : owner.sixRowEnderchestSlotCount;
-+ }
-+ // Purpur end
-+
- public void setActiveChest(EnderChestBlockEntity blockEntity) {
- this.activeChest = blockEntity;
- }
-diff --git a/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java b/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java
-index 9b6ab617ab7f503cf0b2d4e29333c706ffe95f46..bfe79431dc5707677671df5c0787817c6e14a676 100644
---- a/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java
-+++ b/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java
-@@ -84,7 +84,7 @@ public class EnderChestBlock extends AbstractChestBlock i
- // Paper start - Fix InventoryOpenEvent cancellation - moved up;
- playerEnderChestContainer.setActiveChest(enderChestBlockEntity); // Needs to happen before ChestMenu.threeRows as it is required for opening animations
- if (world instanceof ServerLevel serverLevel && player.openMenu(
-- new SimpleMenuProvider((i, inventory, playerx) -> ChestMenu.threeRows(i, inventory, playerEnderChestContainer), CONTAINER_TITLE)
-+ new SimpleMenuProvider((i, inventory, playerx) -> org.purpurmc.purpur.PurpurConfig.enderChestSixRows ? getEnderChestSixRows(i, inventory, player, playerEnderChestContainer) : ChestMenu.threeRows(i, inventory, playerEnderChestContainer), CONTAINER_TITLE) // Purpur
- ).isPresent()) {
- // Paper end - Fix InventoryOpenEvent cancellation - moved up;
- // Paper - Fix InventoryOpenEvent cancellation - moved up;
-@@ -99,6 +99,35 @@ public class EnderChestBlock extends AbstractChestBlock i
- }
- }
-
-+ // Purpur start
-+ private ChestMenu getEnderChestSixRows(int syncId, net.minecraft.world.entity.player.Inventory inventory, Player player, PlayerEnderChestContainer playerEnderChestContainer) {
-+ if (org.purpurmc.purpur.PurpurConfig.enderChestPermissionRows) {
-+ org.bukkit.craftbukkit.entity.CraftHumanEntity bukkitPlayer = player.getBukkitEntity();
-+ if (bukkitPlayer.hasPermission("purpur.enderchest.rows.six")) {
-+ player.sixRowEnderchestSlotCount = 54;
-+ return ChestMenu.sixRows(syncId, inventory, playerEnderChestContainer);
-+ } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.five")) {
-+ player.sixRowEnderchestSlotCount = 45;
-+ return ChestMenu.fiveRows(syncId, inventory, playerEnderChestContainer);
-+ } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.four")) {
-+ player.sixRowEnderchestSlotCount = 36;
-+ return ChestMenu.fourRows(syncId, inventory, playerEnderChestContainer);
-+ } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.three")) {
-+ player.sixRowEnderchestSlotCount = 27;
-+ return ChestMenu.threeRows(syncId, inventory, playerEnderChestContainer);
-+ } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.two")) {
-+ player.sixRowEnderchestSlotCount = 18;
-+ return ChestMenu.twoRows(syncId, inventory, playerEnderChestContainer);
-+ } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.one")) {
-+ player.sixRowEnderchestSlotCount = 9;
-+ return ChestMenu.oneRow(syncId, inventory, playerEnderChestContainer);
-+ }
-+ }
-+ player.sixRowEnderchestSlotCount = -1;
-+ return ChestMenu.sixRows(syncId, inventory, playerEnderChestContainer);
-+ }
-+ // Purpur end
-+
- @Override
- public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
- return new EnderChestBlockEntity(pos, state);
-diff --git a/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java
-index 618552afbdacc919c33b30a6bf4834fb71ab3d5b..7a059d20abdcc0073a314311d78f63ae4bd0365e 100644
---- a/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java
-+++ b/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java
-@@ -68,7 +68,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity {
-
- public BarrelBlockEntity(BlockPos pos, BlockState state) {
- super(BlockEntityType.BARREL, pos, state);
-- this.items = NonNullList.withSize(27, ItemStack.EMPTY);
-+ // Purpur start
-+ this.items = NonNullList.withSize(switch (org.purpurmc.purpur.PurpurConfig.barrelRows) {
-+ case 6 -> 54;
-+ case 5 -> 45;
-+ case 4 -> 36;
-+ case 2 -> 18;
-+ case 1 -> 9;
-+ default -> 27;
-+ }, ItemStack.EMPTY);
-+ // Purpur end
- this.openersCounter = new ContainerOpenersCounter() {
- @Override
- protected void onOpen(Level world, BlockPos pos, BlockState state) {
-@@ -119,7 +128,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity {
-
- @Override
- public int getContainerSize() {
-- return 27;
-+ // Purpur start
-+ return switch (org.purpurmc.purpur.PurpurConfig.barrelRows) {
-+ case 6 -> 54;
-+ case 5 -> 45;
-+ case 4 -> 36;
-+ case 2 -> 18;
-+ case 1 -> 9;
-+ default -> 27;
-+ };
-+ // Purpur end
- }
-
- @Override
-@@ -139,7 +157,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity {
-
- @Override
- protected AbstractContainerMenu createMenu(int syncId, Inventory playerInventory) {
-- return ChestMenu.threeRows(syncId, playerInventory, this);
-+ // Purpur start
-+ return switch (org.purpurmc.purpur.PurpurConfig.barrelRows) {
-+ case 6 -> ChestMenu.sixRows(syncId, playerInventory, this);
-+ case 5 -> ChestMenu.fiveRows(syncId, playerInventory, this);
-+ case 4 -> ChestMenu.fourRows(syncId, playerInventory, this);
-+ case 2 -> ChestMenu.twoRows(syncId, playerInventory, this);
-+ case 1 -> ChestMenu.oneRow(syncId, playerInventory, this);
-+ default -> ChestMenu.threeRows(syncId, playerInventory, this);
-+ };
-+ // Purpur end
- }
-
- @Override
-diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java
-index 6d3f9d5dab6c9a2860ae31cae24310aa2d62da7c..4f29c579f94efe59a8c78520d75676fc4875e2f0 100644
---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java
-+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java
-@@ -145,8 +145,19 @@ public class CraftContainer extends AbstractContainerMenu {
- case PLAYER:
- case CHEST:
- case ENDER_CHEST:
-+ // Purpur start
-+ this.delegate = new ChestMenu(org.purpurmc.purpur.PurpurConfig.enderChestSixRows ? net.minecraft.world.inventory.MenuType.GENERIC_9x6 : net.minecraft.world.inventory.MenuType.GENERIC_9x3, windowId, bottom, top, top.getContainerSize() / 9);
-+ break;
- case BARREL:
-- this.delegate = new ChestMenu(net.minecraft.world.inventory.MenuType.GENERIC_9x3, windowId, bottom, top, top.getContainerSize() / 9);
-+ this.delegate = new ChestMenu(switch (org.purpurmc.purpur.PurpurConfig.barrelRows) {
-+ case 6 -> net.minecraft.world.inventory.MenuType.GENERIC_9x6;
-+ case 5 -> net.minecraft.world.inventory.MenuType.GENERIC_9x5;
-+ case 4 -> net.minecraft.world.inventory.MenuType.GENERIC_9x4;
-+ case 2 -> net.minecraft.world.inventory.MenuType.GENERIC_9x2;
-+ case 1 -> net.minecraft.world.inventory.MenuType.GENERIC_9x1;
-+ default -> net.minecraft.world.inventory.MenuType.GENERIC_9x3;
-+ }, windowId, bottom, top, top.getContainerSize() / 9);
-+ // Purpur end
- 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 c6159c70f7a37b9bffe268b91905ce848d1d2927..d02adaaa6fbdc1c0eff44cb4a1f1642f9575a821 100644
---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java
-+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java
-@@ -84,7 +84,7 @@ public class CraftInventory implements Inventory {
-
- @Override
- public void setContents(ItemStack[] items) {
-- Preconditions.checkArgument(items.length <= this.getSize(), "Invalid inventory size (%s); expected %s or less", items.length, this.getSize());
-+ // Preconditions.checkArgument(items.length <= this.getSize(), "Invalid inventory size (%s); expected %s or less", items.length, this.getSize()); // Purpur
-
- for (int i = 0; i < this.getSize(); i++) {
- if (i >= items.length) {
-diff --git a/src/main/java/org/purpurmc/purpur/PurpurConfig.java b/src/main/java/org/purpurmc/purpur/PurpurConfig.java
-index e8c9393760108f2549b52a61d973ea2b4a64a312..1321955eb23272d96e3028c1c46e75f6c1eaade0 100644
---- a/src/main/java/org/purpurmc/purpur/PurpurConfig.java
-+++ b/src/main/java/org/purpurmc/purpur/PurpurConfig.java
-@@ -180,4 +180,39 @@ public class PurpurConfig {
- private static void messages() {
- cannotRideMob = getString("settings.messages.cannot-ride-mob", cannotRideMob);
- }
-+
-+ public static int barrelRows = 3;
-+ 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);
-+ }
-+ if (version < 20) {
-+ boolean oldValue = getBoolean("settings.blocks.barrel.six-rows", false);
-+ set("settings.blocks.barrel.rows", oldValue ? 6 : 3);
-+ set("settings.blocks.barrel.six-rows", null);
-+ }
-+ barrelRows = getInt("settings.blocks.barrel.rows", barrelRows);
-+ if (barrelRows < 1 || barrelRows > 6) {
-+ Bukkit.getLogger().severe("settings.blocks.barrel.rows must be 1-6, resetting to default");
-+ barrelRows = 3;
-+ }
-+ org.bukkit.event.inventory.InventoryType.BARREL.setDefaultSize(switch (barrelRows) {
-+ case 6 -> 54;
-+ case 5 -> 45;
-+ case 4 -> 36;
-+ case 2 -> 18;
-+ case 1 -> 9;
-+ default -> 27;
-+ });
-+ enderChestSixRows = getBoolean("settings.blocks.ender_chest.six-rows", enderChestSixRows);
-+ org.bukkit.event.inventory.InventoryType.ENDER_CHEST.setDefaultSize(enderChestSixRows ? 54 : 27);
-+ enderChestPermissionRows = getBoolean("settings.blocks.ender_chest.use-permissions-for-rows", enderChestPermissionRows);
-+ }
- }
diff --git a/patches/server/0010-Llama-API.patch b/patches/server/0010-Llama-API.patch
deleted file mode 100644
index 9f35a00aca..0000000000
--- a/patches/server/0010-Llama-API.patch
+++ /dev/null
@@ -1,91 +0,0 @@
-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/LlamaFollowCaravanGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java
-index df695b444fa2a993d381e2f197182c3e91a68502..eb0faf58fa1a408f294fc62120b140def97f998d 100644
---- a/src/main/java/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/goal/LlamaFollowCaravanGoal.java
-@@ -22,6 +22,7 @@ public class LlamaFollowCaravanGoal extends Goal {
-
- @Override
- public boolean canUse() {
-+ if (!this.llama.shouldJoinCaravan) return false; // Purpur
- if (!this.llama.isLeashed() && !this.llama.inCaravan()) {
- List list = this.llama.level().getEntities(this.llama, this.llama.getBoundingBox().inflate(9.0, 4.0, 9.0), entity -> {
- EntityType> entityType = entity.getType();
-@@ -71,6 +72,7 @@ public class LlamaFollowCaravanGoal extends Goal {
-
- @Override
- public boolean canContinueToUse() {
-+ if (!this.llama.shouldJoinCaravan) return false; // Purpur
- if (this.llama.inCaravan() && this.llama.getCaravanHead().isAlive() && this.firstIsLeashed(this.llama, 0)) {
- double d = this.llama.distanceToSqr(this.llama.getCaravanHead());
- if (d > 676.0) {
-diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java
-index 83fdbf55384a5c4429d65a88fcb788e449a8862a..cbab482bd44c0a0fa82a80f41fdfd8c124c58c43 100644
---- a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java
-@@ -72,6 +72,7 @@ public class Llama extends AbstractChestedHorse implements VariantHolder type, Level world) {
- super(type, world);
-@@ -167,6 +168,7 @@ public class Llama extends AbstractChestedHorse implements VariantHolder
-Date: Thu, 8 Aug 2019 15:29:15 -0500
-Subject: [PATCH] AFK API
-
-
-diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-index 8207208d6fb3f982e9909add9e74a0dda69e8120..5e8a5e8e4120420a170c4733ae217e7948cef46c 100644
---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
-+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-@@ -2621,8 +2621,68 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player imple
-
- public void resetLastActionTime() {
- this.lastActionTime = Util.getMillis();
-+ this.setAfk(false); // Purpur
- }
-
-+ // Purpur start - AFK API
-+ private boolean isAfk = false;
-+
-+ @Override
-+ public void setAfk(boolean afk) {
-+ if (this.isAfk == afk) {
-+ return;
-+ }
-+
-+ String msg = afk ? org.purpurmc.purpur.PurpurConfig.afkBroadcastAway : org.purpurmc.purpur.PurpurConfig.afkBroadcastBack;
-+
-+ org.purpurmc.purpur.event.PlayerAFKEvent event = new org.purpurmc.purpur.event.PlayerAFKEvent(this.getBukkitEntity(), afk, this.level().purpurConfig.idleTimeoutKick, msg, !Bukkit.isPrimaryThread());
-+ if (!event.callEvent() || event.shouldKick()) {
-+ return;
-+ }
-+
-+ this.isAfk = afk;
-+
-+ if (!afk) {
-+ resetLastActionTime();
-+ }
-+
-+ msg = event.getBroadcastMsg();
-+ if (msg != null && !msg.isEmpty()) {
-+ String playerName = this.getGameProfile().getName();
-+ if (org.purpurmc.purpur.PurpurConfig.afkBroadcastUseDisplayName) {
-+ net.kyori.adventure.text.Component playerDisplayNameComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(this.getBukkitEntity().getDisplayName());
-+ playerName = net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer.plainText().serialize(playerDisplayNameComponent);
-+ }
-+ server.getPlayerList().broadcastMiniMessage(String.format(msg, playerName), false);
-+ }
-+
-+ if (this.level().purpurConfig.idleTimeoutUpdateTabList) {
-+ String scoreboardName = getScoreboardName();
-+ String playerListName = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().serialize(getBukkitEntity().playerListName());
-+ String[] split = playerListName.split(scoreboardName);
-+ String prefix = (split.length > 0 ? split[0] : "").replace(org.purpurmc.purpur.PurpurConfig.afkTabListPrefix, "");
-+ String suffix = (split.length > 1 ? split[1] : "").replace(org.purpurmc.purpur.PurpurConfig.afkTabListSuffix, "");
-+ if (afk) {
-+ getBukkitEntity().setPlayerListName(org.purpurmc.purpur.PurpurConfig.afkTabListPrefix + prefix + scoreboardName + suffix + org.purpurmc.purpur.PurpurConfig.afkTabListSuffix, true);
-+ } else {
-+ getBukkitEntity().setPlayerListName(prefix + scoreboardName + suffix, true);
-+ }
-+ }
-+
-+ ((ServerLevel) this.level()).updateSleepingPlayerList();
-+ }
-+
-+ @Override
-+ public boolean isAfk() {
-+ return this.isAfk;
-+ }
-+
-+ @Override
-+ public boolean canBeCollidedWith() {
-+ return !this.isAfk() && super.canBeCollidedWith();
-+ }
-+ // Purpur end - AFK API
-+
- public ServerStatsCounter getStats() {
- return this.stats;
- }
-diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-index fbc59503316d566e88b037851afd74e5469c281b..830e0705f3a3d45fcf0eab6b8eea403c3023f3f9 100644
---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-@@ -344,6 +344,20 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
- private boolean justTeleported = false;
- // CraftBukkit end
-
-+ // Purpur start - AFK API
-+ private final com.google.common.cache.LoadingCache kickPermissionCache = com.google.common.cache.CacheBuilder.newBuilder()
-+ .maximumSize(1000)
-+ .expireAfterWrite(1, java.util.concurrent.TimeUnit.MINUTES)
-+ .build(
-+ new com.google.common.cache.CacheLoader<>() {
-+ @Override
-+ public Boolean load(CraftPlayer player) {
-+ return player.hasPermission("purpur.bypassIdleKick");
-+ }
-+ }
-+ );
-+ // Purpur end - AFK API
-+
- @Override
- public void tick() {
- if (this.ackBlockChangesUpTo > -1) {
-@@ -400,6 +414,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
- this.recipeSpamPackets.tick(); // Paper - auto recipe limit
- this.dropSpamThrottler.tick();
- if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L && !this.player.wonGame) { // Paper - Prevent AFK kick while watching end credits
-+ // Purpur start - AFK API
-+ this.player.setAfk(true);
-+ if (!this.player.level().purpurConfig.idleTimeoutKick || (!Boolean.parseBoolean(System.getenv("PURPUR_FORCE_IDLE_KICK")) && kickPermissionCache.getUnchecked(this.player.getBukkitEntity()))) {
-+ return;
-+ }
-+ // Purpur end - AFK API
- this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854
- this.disconnect((Component) Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause
- }
-@@ -656,6 +676,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
- 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.resetLastActionTime(); // Purpur - AFK API
-+
- Location oldTo = to.clone();
- PlayerMoveEvent event = new PlayerMoveEvent(player, from, to);
- this.cserver.getPluginManager().callEvent(event);
-@@ -1554,7 +1576,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
- movedWrongly = true;
- if (event.getLogWarning())
- // Paper end
-- ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!", this.player.getName().getString());
-+ ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!, ({})", this.player.getName().getString(), d11); // Purpur - AFK API
- } // Paper
- }
-
-@@ -1622,6 +1644,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
- 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.resetLastActionTime(); // Purpur - AFK API
-+
- Location oldTo = to.clone();
- PlayerMoveEvent event = new PlayerMoveEvent(player, from, to);
- this.cserver.getPluginManager().callEvent(event);
-diff --git a/src/main/java/net/minecraft/server/players/SleepStatus.java b/src/main/java/net/minecraft/server/players/SleepStatus.java
-index 823efad652d8ff9e96b99375b102fef6f017716e..bdf0240840d92bf95f94c6fb1125eeaa105e303b 100644
---- a/src/main/java/net/minecraft/server/players/SleepStatus.java
-+++ b/src/main/java/net/minecraft/server/players/SleepStatus.java
-@@ -19,7 +19,7 @@ public class SleepStatus {
-
- public boolean areEnoughDeepSleeping(int percentage, List players) {
- // CraftBukkit start
-- int j = (int) players.stream().filter((eh) -> { return eh.isSleepingLongEnough() || eh.fauxSleeping; }).count();
-+ int j = (int) players.stream().filter((eh) -> { return eh.isSleepingLongEnough() || eh.fauxSleeping || (eh.level().purpurConfig.idleTimeoutCountAsSleeping && eh.isAfk()); }).count(); // Purpur - AFK API
- boolean anyDeepSleep = players.stream().anyMatch(Player::isSleepingLongEnough);
-
- return anyDeepSleep && j >= this.sleepersNeeded(percentage);
-@@ -52,7 +52,7 @@ public class SleepStatus {
-
- if (!entityplayer.isSpectator()) {
- ++this.activePlayers;
-- if (entityplayer.isSleeping() || entityplayer.fauxSleeping) { // CraftBukkit
-+ if ((entityplayer.isSleeping() || entityplayer.fauxSleeping) || (entityplayer.level().purpurConfig.idleTimeoutCountAsSleeping && entityplayer.isAfk())) { // CraftBukkit // Purpur - AFK API
- ++this.sleepingPlayers;
- }
- // CraftBukkit start
-diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java
-index 6bf691fcc6486bde73bae30eff09142802c29eda..d99b223be90f0c04bb9274228ad323a7c7f218b2 100644
---- a/src/main/java/net/minecraft/world/entity/EntitySelector.java
-+++ b/src/main/java/net/minecraft/world/entity/EntitySelector.java
-@@ -39,6 +39,7 @@ public final class EntitySelector {
- return net.minecraft.util.Mth.clamp(serverPlayer.getStats().getValue(net.minecraft.stats.Stats.CUSTOM.get(net.minecraft.stats.Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE) >= playerInsomniaTicks;
- };
- // Paper end - Ability to control player's insomnia and phantoms
-+ public static Predicate notAfk = (player) -> !player.isAfk(); // Purpur - AFK API
-
- private EntitySelector() {}
- // Paper start - Affects Spawning API
-diff --git a/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java b/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java
-index 52982c1e6a4da36392569c791853279f5f9ac31a..ebb827e213a3ba5eeb2fe5b78f5dee99403097b6 100644
---- a/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/targeting/TargetingConditions.java
-@@ -64,6 +64,10 @@ public class TargetingConditions {
- return false;
- } else if (this.selector != null && !this.selector.test(target, world)) {
- return false;
-+ // Purpur start - AFK API
-+ } else if (!world.purpurConfig.idleTimeoutTargetPlayer && target instanceof net.minecraft.server.level.ServerPlayer player && player.isAfk()) {
-+ return false;
-+ // Purpur end - AFK API
- } else {
- if (tester == null) {
- if (this.isCombat && (!target.canBeSeenAsEnemy() || world.getDifficulty() == Difficulty.PEACEFUL)) {
-diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java
-index 87c6378104ff47549c751e09afb6cfcd9b8dcf5d..2f69a511db8d43fbd3a17387cded1d3573579fce 100644
---- a/src/main/java/net/minecraft/world/entity/player/Player.java
-+++ b/src/main/java/net/minecraft/world/entity/player/Player.java
-@@ -206,6 +206,13 @@ public abstract class Player extends LivingEntity {
- public boolean fauxSleeping;
- public int oldLevel = -1;
-
-+ // Purpur start - AFK API
-+ public abstract void setAfk(boolean afk);
-+
-+ public boolean isAfk() {
-+ return false;
-+ }
-+ // Purpur end - AFK API
- @Override
- public CraftHumanEntity getBukkitEntity() {
- return (CraftHumanEntity) super.getBukkitEntity();
-diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java
-index 5d7a6e4b73f032db356e7ec369b150013e940ee6..e164833de0c29eed9025dd4af3f2bb74c92d2250 100644
---- a/src/main/java/net/minecraft/world/level/EntityGetter.java
-+++ b/src/main/java/net/minecraft/world/level/EntityGetter.java
-@@ -184,7 +184,7 @@ public interface EntityGetter extends ca.spottedleaf.moonrise.patches.chunk_syst
-
- default boolean hasNearbyAlivePlayer(double x, double y, double z, double range) {
- for (Player player : this.players()) {
-- if (EntitySelector.NO_SPECTATORS.test(player) && EntitySelector.LIVING_ENTITY_STILL_ALIVE.test(player)) {
-+ if (EntitySelector.NO_SPECTATORS.test(player) && EntitySelector.LIVING_ENTITY_STILL_ALIVE.test(player) && EntitySelector.notAfk.test(player)) { // Purpur - AFK API
- double d = player.distanceToSqr(x, y, z);
- if (range < 0.0 || d < range * range) {
- return true;
-diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
-index c5bd2a45b32e8dff83c148379544db125684622a..16671636da1b6da62a0bdf0daab745fcd89143b7 100644
---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
-+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
-@@ -584,10 +584,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
-
- @Override
- public void setPlayerListName(String name) {
-+ // Purpur start - AFK API
-+ setPlayerListName(name, false);
-+ }
-+ public void setPlayerListName(String name, boolean useMM) {
-+ // Purpur end - AFK API
- if (name == null) {
- name = this.getName();
- }
-- this.getHandle().listName = name.equals(this.getName()) ? null : CraftChatMessage.fromStringOrNull(name);
-+ this.getHandle().listName = name.equals(this.getName()) ? null : useMM ? io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(name)) : CraftChatMessage.fromStringOrNull(name); // Purpur - AFK API
- if (this.getHandle().connection == null) return; // Paper - Updates are possible before the player has fully joined
- for (ServerPlayer player : (List) this.server.getHandle().players) {
- if (player.getBukkitEntity().canSee(this)) {
-@@ -3572,4 +3577,20 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
- return getHandle().purpurClient;
- }
- // Purpur end - Purpur client support
-+ // Purpur start - AFK API
-+ @Override
-+ public boolean isAfk() {
-+ return getHandle().isAfk();
-+ }
-+
-+ @Override
-+ public void setAfk(boolean setAfk) {
-+ getHandle().setAfk(setAfk);
-+ }
-+
-+ @Override
-+ public void resetIdleTimer() {
-+ getHandle().resetLastActionTime();
-+ }
-+ // Purpur end - AFK API
- }
-diff --git a/src/main/java/org/purpurmc/purpur/PurpurConfig.java b/src/main/java/org/purpurmc/purpur/PurpurConfig.java
-index 1321955eb23272d96e3028c1c46e75f6c1eaade0..82f0ae89ef3f0dc614edb4ccd3caa66cb387044c 100644
---- a/src/main/java/org/purpurmc/purpur/PurpurConfig.java
-+++ b/src/main/java/org/purpurmc/purpur/PurpurConfig.java
-@@ -177,8 +177,18 @@ public class PurpurConfig {
- }
-
- public static String cannotRideMob = "You cannot mount that mob";
-+ public static String afkBroadcastAway = "