RECEIVE_FLOWER = GoalKey.of(IronGolem.class, NamespacedKey.minecraft("receive_flower"));
-+ // Purpur end
-+
- /**
- * @deprecated removed in 1.16
- */
-diff --git a/src/main/java/com/destroystokyo/paper/util/VersionFetcher.java b/src/main/java/com/destroystokyo/paper/util/VersionFetcher.java
-index a736d7bcdc5861a01b66ba36158db1c716339346..22fc165fd9c95f0f3ae1be7a0857e48cc50fad5b 100644
---- a/src/main/java/com/destroystokyo/paper/util/VersionFetcher.java
-+++ b/src/main/java/com/destroystokyo/paper/util/VersionFetcher.java
-@@ -26,6 +26,12 @@ public interface VersionFetcher {
- @NotNull
- Component getVersionMessage(@NotNull String serverVersion);
-
-+ // Purpur start
-+ default int distance() {
-+ return 0;
-+ }
-+ // Purpur end
-+
- class DummyVersionFetcher implements VersionFetcher {
-
- @Override
-diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java
-index b5042ccfad1f99cce88b3a8878da15909333c3a0..7f21d70079a6b4006a41518f90b5f98d4067b5f6 100644
---- a/src/main/java/org/bukkit/Bukkit.java
-+++ b/src/main/java/org/bukkit/Bukkit.java
-@@ -2536,4 +2536,127 @@ 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();
-+ }
-+
-+ /**
-+ * Check if server is lagging according to laggy threshold setting
-+ *
-+ * @return True if lagging
-+ */
-+ 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);
-+ }
-+
-+ /**
-+ * 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/ChatColor.java b/src/main/java/org/bukkit/ChatColor.java
-index ea4ceb643239c26851bacbf45fc3f2efef3bb4be..3b8395dcb73e3fb251adf7438cbc7e95c4185a3a 100644
---- a/src/main/java/org/bukkit/ChatColor.java
-+++ b/src/main/java/org/bukkit/ChatColor.java
-@@ -3,6 +3,7 @@ package org.bukkit;
- import com.google.common.base.Preconditions;
- import com.google.common.collect.Maps;
- import java.util.Map;
-+import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- import org.jetbrains.annotations.Contract;
- import org.jetbrains.annotations.NotNull;
-@@ -455,4 +456,77 @@ public enum ChatColor {
- BY_CHAR.put(color.code, color);
- }
- }
-+
-+ // Purpur start
-+ /**
-+ * Convert legacy string into a string ready to be parsed by MiniMessage
-+ *
-+ * @param str Legacy string
-+ * @return MiniMessage ready string
-+ */
-+ @NotNull
-+ public static String toMM(@NotNull String str) {
-+ StringBuilder sb = new StringBuilder(str);
-+ Matcher m = STRIP_COLOR_PATTERN.matcher(sb);
-+ while (m.find()) {
-+ sb.replace(m.start(), m.end(), sb.substring(m.start(), m.end()).toLowerCase());
-+ }
-+ return sb.toString()
-+ .replace("&0", "")
-+ .replace("&1", "")
-+ .replace("&2", "")
-+ .replace("&3", "")
-+ .replace("&4", "")
-+ .replace("&5", "")
-+ .replace("&6", "")
-+ .replace("&7", "")
-+ .replace("&8", "")
-+ .replace("&9", "")
-+ .replace("&a", "")
-+ .replace("&b", "")
-+ .replace("&c", "")
-+ .replace("&d", "")
-+ .replace("&e", "")
-+ .replace("&f", "")
-+ .replace("&k", "")
-+ .replace("&l", "")
-+ .replace("&m", "")
-+ .replace("&n", "")
-+ .replace("&o", "")
-+ .replace("&r", "");
-+ }
-+
-+ @NotNull
-+ public static net.kyori.adventure.text.Component parseMM(@NotNull String string, @Nullable Object... args) {
-+ return net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(String.format(string, args));
-+ }
-+
-+ @Deprecated
-+ public static final Pattern HEX_PATTERN = Pattern.compile("(#[A-Fa-f0-9]{6})");
-+
-+ @Deprecated
-+ @Nullable
-+ public static String replaceHex(@Nullable String str) {
-+ if (str != null) {
-+ java.util.regex.Matcher matcher = HEX_PATTERN.matcher(str);
-+ while (matcher.find()) {
-+ String group = matcher.group(1);
-+ str = str.replace(group, net.md_5.bungee.api.ChatColor.of(group).toString());
-+ }
-+ }
-+ return str;
-+ }
-+
-+ @Deprecated
-+ @Nullable
-+ public static String color(@Nullable String str) {
-+ return color(str, true);
-+ }
-+
-+ @Deprecated
-+ @Nullable
-+ public static String color(@Nullable String str, boolean parseHex) {
-+ return str != null ? net.md_5.bungee.api.ChatColor.translateAlternateColorCodes('&', parseHex ? replaceHex(str) : str) : str;
-+ }
-+ // Purpur end
- }
-diff --git a/src/main/java/org/bukkit/Material.java b/src/main/java/org/bukkit/Material.java
-index 9b290969b0e60f20450cd15e3fc6f37276f12ae6..77a885fd17f280649b95df758f1096fa38fe8d69 100644
---- a/src/main/java/org/bukkit/Material.java
-+++ b/src/main/java/org/bukkit/Material.java
-@@ -11166,4 +11166,40 @@ public enum Material implements Keyed, Translatable, net.kyori.adventure.transla
- public boolean isEnabledByFeature(@NotNull World world) {
- return Bukkit.getDataPackManager().isEnabledByFeature(this, world);
- }
-+
-+ // Purpur start
-+ public boolean isArmor() {
-+ switch (this) {
-+ //
-+ case LEATHER_BOOTS:
-+ case LEATHER_CHESTPLATE:
-+ case LEATHER_HELMET:
-+ case LEATHER_LEGGINGS:
-+ case CHAINMAIL_BOOTS:
-+ case CHAINMAIL_CHESTPLATE:
-+ case CHAINMAIL_HELMET:
-+ case CHAINMAIL_LEGGINGS:
-+ case IRON_BOOTS:
-+ case IRON_CHESTPLATE:
-+ case IRON_HELMET:
-+ case IRON_LEGGINGS:
-+ case GOLDEN_BOOTS:
-+ case GOLDEN_CHESTPLATE:
-+ case GOLDEN_HELMET:
-+ case GOLDEN_LEGGINGS:
-+ case DIAMOND_BOOTS:
-+ case DIAMOND_CHESTPLATE:
-+ case DIAMOND_HELMET:
-+ case DIAMOND_LEGGINGS:
-+ case NETHERITE_BOOTS:
-+ case NETHERITE_CHESTPLATE:
-+ case NETHERITE_HELMET:
-+ case NETHERITE_LEGGINGS:
-+ case TURTLE_HELMET:
-+ return true;
-+ default:
-+ return false;
-+ }
-+ }
-+ // Purpur end
- }
-diff --git a/src/main/java/org/bukkit/OfflinePlayer.java b/src/main/java/org/bukkit/OfflinePlayer.java
-index 72175dcae49f75b494ab70958053ed994a8828f4..df642a55003517040be795b44a8bf107dd88810b 100644
---- a/src/main/java/org/bukkit/OfflinePlayer.java
-+++ b/src/main/java/org/bukkit/OfflinePlayer.java
-@@ -460,4 +460,114 @@ public interface OfflinePlayer extends ServerOperator, AnimalTamer, Configuratio
- */
- @Nullable
- public Location getLastDeathLocation();
-+
-+ // Purpur start - OfflinePlayer API
-+ /**
-+ * Determines if the OfflinePlayer is allowed to fly via jump key double-tap like
-+ * in creative mode.
-+ *
-+ * @return True if the player is allowed to fly.
-+ */
-+ public boolean getAllowFlight();
-+
-+ /**
-+ * Sets if the OfflinePlayer is allowed to fly via jump key double-tap like in
-+ * creative mode.
-+ *
-+ * @param flight If flight should be allowed.
-+ */
-+ public void setAllowFlight(boolean flight);
-+
-+ /**
-+ * Checks to see if this player is currently flying or not.
-+ *
-+ * @return True if the player is flying, else false.
-+ */
-+ public boolean isFlying();
-+
-+ /**
-+ * Makes this player start or stop flying.
-+ *
-+ * @param value True to fly.
-+ */
-+ public void setFlying(boolean value);
-+
-+ /**
-+ * Sets the speed at which a client will fly. Negative values indicate
-+ * reverse directions.
-+ *
-+ * @param value The new speed, from -1 to 1.
-+ * @throws IllegalArgumentException If new speed is less than -1 or
-+ * greater than 1
-+ */
-+ public void setFlySpeed(float value) throws IllegalArgumentException;
-+
-+ /**
-+ * Sets the speed at which a client will walk. Negative values indicate
-+ * reverse directions.
-+ *
-+ * @param value The new speed, from -1 to 1.
-+ * @throws IllegalArgumentException If new speed is less than -1 or
-+ * greater than 1
-+ */
-+ public void setWalkSpeed(float value) throws IllegalArgumentException;
-+
-+ /**
-+ * Gets the current allowed speed that a client can fly.
-+ *
-+ * @return The current allowed speed, from -1 to 1
-+ */
-+ public float getFlySpeed();
-+
-+ /**
-+ * Gets the current allowed speed that a client can walk.
-+ *
-+ * @return The current allowed speed, from -1 to 1
-+ */
-+ public float getWalkSpeed();
-+
-+ /**
-+ * Gets the entity's current position
-+ *
-+ * @return a new copy of Location containing the position of this offline player
-+ */
-+ @Nullable
-+ public Location getLocation();
-+
-+ /**
-+ * Sets OfflinePlayer's location. If player is online, it falls back to the Player#teleport implementation.
-+ *
-+ * @param destination
-+ * @return true if teleportation was successful
-+ */
-+ public boolean teleportOffline(@NotNull org.bukkit.Location destination);
-+
-+ /**
-+ * Sets OfflinePlayer's location. If player is online, it falls back to the Player#teleport implementation.
-+ *
-+ * @param destination
-+ * @param cause Teleport cause used if player is online
-+ * @return true if teleportation was successful
-+ */
-+ public boolean teleportOffline(@NotNull org.bukkit.Location destination, @NotNull org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause);
-+
-+ /**
-+ * Sets OfflinePlayer's location. If player is online, it falls back to the Player#teleportAsync implementation.
-+ *
-+ * @param destination
-+ * @return true if teleportation successful
-+ */
-+ @NotNull
-+ public java.util.concurrent.CompletableFuture teleportOfflineAsync(@NotNull Location destination);
-+
-+ /**
-+ * Sets OfflinePlayer's location. If player is online, it falls back to the Player#teleportAsync implementation.
-+ *
-+ * @param destination
-+ * @param cause Teleport cause used if player is online
-+ * @return true if teleportation successful
-+ */
-+ @NotNull
-+ public java.util.concurrent.CompletableFuture teleportOfflineAsync(@NotNull Location destination, @NotNull org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause);
-+ // Purpur end - OfflinePlayer API
- }
-diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java
-index 007e23a9383ab8eda12c6dffb385256215356040..61cd83e6797e84679063a31aff1609e1168352c5 100644
---- a/src/main/java/org/bukkit/Server.java
-+++ b/src/main/java/org/bukkit/Server.java
-@@ -2047,6 +2047,18 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
-
- // Paper end
-
-+ // Purpur start
-+ @NotNull
-+ public org.bukkit.configuration.file.YamlConfiguration getPurpurConfig() {
-+ throw new UnsupportedOperationException("Not supported yet.");
-+ }
-+
-+ @NotNull
-+ public java.util.Properties getServerProperties() {
-+ throw new UnsupportedOperationException("Not supported yet.");
-+ }
-+ // Purpur end
-+
- /**
- * Sends the component to the player
- *
-@@ -2241,4 +2253,111 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
- long getLastTickOversleepTime();
- // Gale end - YAPFA - last tick time - API
-
-+ // Purpur start
-+ /**
-+ * Get the name of this server
-+ *
-+ * @return the name of the server
-+ */
-+ @NotNull
-+ String getServerName();
-+
-+ /**
-+ * Check if server is lagging according to laggy threshold setting
-+ *
-+ * @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);
-+
-+ /**
-+ * 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 72f1576b8ce5b55b50f053f346ce42c52db4b568..adf8169d5baefa7a33c33ef066180a8116617756 100644
---- a/src/main/java/org/bukkit/World.java
-+++ b/src/main/java/org/bukkit/World.java
-@@ -3974,6 +3974,86 @@ 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);
-+
-+ /**
-+ * 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
-+
- /**
- * Get all {@link FeatureFlag} enabled in this world.
- *
-diff --git a/src/main/java/org/bukkit/command/SimpleCommandMap.java b/src/main/java/org/bukkit/command/SimpleCommandMap.java
-index ac9a28922f8a556944a4c3649d74c32c622f0cb0..5349f16136d9348c374a7dfe5b89a71dfcb0e66d 100644
---- a/src/main/java/org/bukkit/command/SimpleCommandMap.java
-+++ b/src/main/java/org/bukkit/command/SimpleCommandMap.java
-@@ -143,6 +143,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);
-@@ -150,10 +163,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, Arrays.copyOfRange(args, 1, args.length));
-- } // target.timings.stopTiming(); // Spigot // Paper
-+ target.execute(sender, sentCommandLabel, parsedArgs); // Purpur
-+ //} // 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/bukkit/command/defaults/VersionCommand.java b/src/main/java/org/bukkit/command/defaults/VersionCommand.java
-index e40f017f87d6b6b4770501b106c76dc69ec69abb..eac5830986cd0638950bbb1e6f10a30e246e09a7 100644
---- a/src/main/java/org/bukkit/command/defaults/VersionCommand.java
-+++ b/src/main/java/org/bukkit/command/defaults/VersionCommand.java
-@@ -198,7 +198,7 @@ public class VersionCommand extends BukkitCommand {
- String version = Bukkit.getVersion();
- // Paper start
- if (version.startsWith("null")) { // running from ide?
-- setVersionMessage(net.kyori.adventure.text.Component.text("Unknown version, custom build?", net.kyori.adventure.text.format.NamedTextColor.YELLOW));
-+ setVersionMessage(net.kyori.adventure.text.Component.text("* Unknown version, custom build?", net.kyori.adventure.text.format.NamedTextColor.RED)); // Purpur
- return;
- }
- setVersionMessage(getVersionFetcher().getVersionMessage(version));
-@@ -239,9 +239,11 @@ public class VersionCommand extends BukkitCommand {
- // Paper start
- private void setVersionMessage(final @NotNull net.kyori.adventure.text.Component msg) {
- lastCheck = System.currentTimeMillis();
-- final net.kyori.adventure.text.Component message = net.kyori.adventure.text.TextComponent.ofChildren(
-- net.kyori.adventure.text.Component.text(Bukkit.getVersionMessage(), net.kyori.adventure.text.format.NamedTextColor.WHITE),
-- net.kyori.adventure.text.Component.newline(),
-+ // Purpur start
-+ int distance = getVersionFetcher().distance();
-+ final net.kyori.adventure.text.Component message = net.kyori.adventure.text.Component.join(net.kyori.adventure.text.JoinConfiguration.separator(net.kyori.adventure.text.Component.newline()),
-+ ChatColor.parseMM("Current: %s%s*", distance == 0 ? "" : distance > 0 ? "" : "", Bukkit.getVersion()),
-+ // Purpur end
- msg
- );
- this.versionMessage = net.kyori.adventure.text.Component.text()
-diff --git a/src/main/java/org/bukkit/enchantments/EnchantmentTarget.java b/src/main/java/org/bukkit/enchantments/EnchantmentTarget.java
-index bea786a8be4402f9384984e48390e745f2988dd6..5eb81fcc18b8fdec5a0e4c699525281fa6ad4d78 100644
---- a/src/main/java/org/bukkit/enchantments/EnchantmentTarget.java
-+++ b/src/main/java/org/bukkit/enchantments/EnchantmentTarget.java
-@@ -228,6 +228,28 @@ public enum EnchantmentTarget {
- public boolean includes(@NotNull Material item) {
- return BREAKABLE.includes(item) || (WEARABLE.includes(item) && !item.equals(Material.ELYTRA)) || item.equals(Material.COMPASS);
- }
-+ // Purpur start
-+ },
-+
-+ /**
-+ * Allow the Enchantment to be placed on bows and crossbows.
-+ */
-+ BOW_AND_CROSSBOW {
-+ @Override
-+ public boolean includes(@NotNull Material item) {
-+ return item.equals(Material.BOW) || item.equals(Material.CROSSBOW);
-+ }
-+ },
-+
-+ /**
-+ * 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/src/main/java/org/bukkit/entity/Endermite.java b/src/main/java/org/bukkit/entity/Endermite.java
-index 138d2530de2410f4a9424dabd3e5ce0cd1c1dcd2..10a8d64ad2da0be2c14f34c3e7d1957c6f2883d1 100644
---- a/src/main/java/org/bukkit/entity/Endermite.java
-+++ b/src/main/java/org/bukkit/entity/Endermite.java
-@@ -3,25 +3,21 @@ package org.bukkit.entity;
- public interface Endermite extends Monster {
-
- /**
-- * Gets whether this Endermite was spawned by a player.
-+ * Gets whether this Endermite was spawned by a player from throwing an ender pearl
- *
-- * An Endermite spawned by a player will be attacked by nearby Enderman.
-+ * An Endermite spawned by a player might be attacked by nearby Enderman depending on purpur.yml settings
- *
- * @return player spawned status
-- * @deprecated this functionality no longer exists
- */
-- @Deprecated
- boolean isPlayerSpawned();
-
- /**
-- * Sets whether this Endermite was spawned by a player.
-+ * Sets whether this Endermite was spawned by a player from throwing an ender pearl
- *
-- * An Endermite spawned by a player will be attacked by nearby Enderman.
-+ * An Endermite spawned by a player might be attacked by nearby Enderman depending on purpur.yml settings
- *
- * @param playerSpawned player spawned status
-- * @deprecated this functionality no longer exists
- */
-- @Deprecated
- void setPlayerSpawned(boolean playerSpawned);
- // Paper start
- /**
-diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java
-index a2a423d4e4c2702ba5967223cab0432dd7d04732..cc78ce7de88a9a404ed20d5bc61b98d3107f29b3 100644
---- a/src/main/java/org/bukkit/entity/Entity.java
-+++ b/src/main/java/org/bukkit/entity/Entity.java
-@@ -954,4 +954,55 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent
- */
- boolean wouldCollideUsing(@NotNull BoundingBox boundingBox);
- // Paper End - Collision API
-+
-+ // Purpur start
-+ /**
-+ * Get the riding player
-+ *
-+ * @return Riding player
-+ */
-+ @Nullable
-+ Player getRider();
-+
-+ /**
-+ * Check if entity is being ridden
-+ *
-+ * @return True if being ridden
-+ */
-+ boolean hasRider();
-+
-+ /**
-+ * Check if entity is ridable
-+ *
-+ * @return True if ridable
-+ */
-+ boolean isRidable();
-+
-+ /**
-+ * Check if entity is ridable in water
-+ *
-+ * @return True if ridable in water
-+ */
-+ boolean isRidableInWater();
-+
-+ /**
-+ * Checks if the entity is in daylight
-+ *
-+ * @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/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/Item.java b/src/main/java/org/bukkit/entity/Item.java
-index 58017fce436cdbda255f7172fbdadb726d4b113c..05600fc8bf2a61aca8094029bc4c208a710da952 100644
---- a/src/main/java/org/bukkit/entity/Item.java
-+++ b/src/main/java/org/bukkit/entity/Item.java
-@@ -153,4 +153,62 @@ public interface Item extends Entity, io.papermc.paper.entity.Frictional { // Pa
- */
- public void setHealth(int health);
- // Paper end
-+
-+ // Purpur start
-+ /**
-+ * Set whether or not this item is immune to cactus
-+ *
-+ * @param immuneToCactus True to make immune to cactus
-+ */
-+ void setImmuneToCactus(boolean immuneToCactus);
-+
-+ /**
-+ * Check if item is immune to cactus
-+ *
-+ * @return True if immune to cactus
-+ */
-+ boolean isImmuneToCactus();
-+
-+ /**
-+ * Set whether or not this item is immune to explosions
-+ *
-+ * @param immuneToExplosion True to make immune to explosions
-+ */
-+ void setImmuneToExplosion(boolean immuneToExplosion);
-+
-+ /**
-+ * Check if item is immune to explosions
-+ *
-+ * @return True if immune to explosions
-+ */
-+ boolean isImmuneToExplosion();
-+
-+ /**
-+ * Set whether or not this item is immune to fire
-+ *
-+ * @param immuneToFire True to make immune to fire
-+ */
-+ void setImmuneToFire(boolean immuneToFire);
-+
-+ /**
-+ * Check if item is immune to fire
-+ *
-+ * @return True if immune to fire
-+ */
-+ boolean isImmuneToFire();
-+
-+ /**
-+ * Set whether or not this item is immune to lightning
-+ *
-+ * @param immuneToLightning True to make immune to lightning
-+ */
-+ void setImmuneToLightning(boolean immuneToLightning);
-+
-+ /**
-+ * Check if item is immune to lightning
-+ *
-+ * @return True if immune to lightning
-+ */
-+ boolean isImmuneToLightning();
-+ // Purpur end
- }
-diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java
-index ffca32ae2464ea5a669029079a50585ca259a4f8..654dc0c6d98b29cf45d3826aece374726e3e9802 100644
---- a/src/main/java/org/bukkit/entity/LivingEntity.java
-+++ b/src/main/java/org/bukkit/entity/LivingEntity.java
-@@ -1150,4 +1150,41 @@ public interface LivingEntity extends Attributable, Damageable, ProjectileSource
- */
- void setBodyYaw(float bodyYaw);
- // Paper end
-+
-+ // Purpur start
-+ /**
-+ * Gets the distance (in blocks) this entity can safely fall without taking damage
-+ *
-+ * @return Safe fall distance
-+ */
-+ float getSafeFallDistance();
-+
-+ /**
-+ * Set the distance (in blocks) this entity can safely fall without taking damage
-+ *
-+ * @param safeFallDistance Safe fall distance
-+ */
-+ void setSafeFallDistance(float safeFallDistance);
-+
-+ /**
-+ * Play item break animation for the item in specified equipment slot
-+ *
-+ * @param slot Equipment slot to play break animation for
-+ */
-+ void broadcastItemBreak(@NotNull org.bukkit.inventory.EquipmentSlot slot);
-+
-+ /**
-+ * If this mob will burn in the sunlight
-+ *
-+ * @return True if mob will burn in sunlight
-+ */
-+ boolean shouldBurnInDay();
-+
-+ /**
-+ * Set if this mob should burn in the sunlight
-+ *
-+ * @param shouldBurnInDay True to burn in sunlight
-+ */
-+ void setShouldBurnInDay(boolean shouldBurnInDay);
-+ // Purpur end
- }
-diff --git a/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/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
-index 930086e164c1c8cef8ff27009736e6962357bc2b..18e38e028cb44b12e4e439175f67800e83512d2f 100644
---- a/src/main/java/org/bukkit/entity/Player.java
-+++ b/src/main/java/org/bukkit/entity/Player.java
-@@ -3080,4 +3080,139 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
- @Override
- Spigot spigot();
- // Spigot end
-+
-+ // Purpur start
-+ /**
-+ * Allows you to get if player uses Purpur Client
-+ *
-+ * @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
-+ */
-+ void resetIdleTimer();
-+
-+ /**
-+ * Check if player is invulnerable from recently spawning or accepting a resource pack
-+ *
-+ * @return True if invulnerable
-+ */
-+ boolean isSpawnInvulnerable();
-+
-+ /**
-+ * Get invulnerable ticks remaining
-+ *
-+ * @return Invulnerable ticks
-+ */
-+ int getSpawnInvulnerableTicks();
-+
-+ /**
-+ * Set invulnerable ticks remaining
-+ *
-+ * @param invulnerableTicks Invulnerable ticks remaining
-+ */
-+ void setSpawnInvulnerableTicks(int invulnerableTicks);
-+
-+ /**
-+ * 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();
-+
-+ /**
-+ * 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
-+ */
-+ void sendDeathScreen(@NotNull net.kyori.adventure.text.Component message, @Nullable Entity killer);
-+ // 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/Villager.java b/src/main/java/org/bukkit/entity/Villager.java
-index 3bc24457d143449e6a338d79becf7c39b9f81054..4a5edf4e72e81b22c1abb2ade244f7f4292e993c 100644
---- a/src/main/java/org/bukkit/entity/Villager.java
-+++ b/src/main/java/org/bukkit/entity/Villager.java
-@@ -328,4 +328,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/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/src/main/java/org/bukkit/entity/Wolf.java b/src/main/java/org/bukkit/entity/Wolf.java
-index 84db38388bf7a58e66d6cd29620b4fe64b0a897e..82ebd99549ce9f9e6427a50cef424e9007735708 100644
---- a/src/main/java/org/bukkit/entity/Wolf.java
-+++ b/src/main/java/org/bukkit/entity/Wolf.java
-@@ -69,4 +69,20 @@ public interface Wolf extends Tameable, Sittable, io.papermc.paper.entity.Collar
- * @param interested Whether the wolf is interested
- */
- public void setInterested(boolean interested);
-+
-+ // 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/src/main/java/org/bukkit/event/entity/EntityPotionEffectEvent.java b/src/main/java/org/bukkit/event/entity/EntityPotionEffectEvent.java
-index c9f395064656dd0126410eb3c6e197baa450c063..13156a12e5df50cdc1e465dc0bd9d94108275629 100644
---- a/src/main/java/org/bukkit/event/entity/EntityPotionEffectEvent.java
-+++ b/src/main/java/org/bukkit/event/entity/EntityPotionEffectEvent.java
-@@ -217,6 +217,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/src/main/java/org/bukkit/event/inventory/InventoryType.java b/src/main/java/org/bukkit/event/inventory/InventoryType.java
-index a8e631315f2da68895a258cf0ba9875bc88fc48c..d5648ec745e3530aecf18c3e1f3185a5f63f3d11 100644
---- a/src/main/java/org/bukkit/event/inventory/InventoryType.java
-+++ b/src/main/java/org/bukkit/event/inventory/InventoryType.java
-@@ -155,7 +155,7 @@ public enum InventoryType {
- SMITHING_NEW(4, "Upgrade Gear"),
- ;
-
-- private final int size;
-+ private int size; public void setDefaultSize(int size) { this.size = size; } // Purpur - remove final and add setter
- private final String title;
- private final boolean isCreatable;
-
-diff --git a/src/main/java/org/bukkit/inventory/AnvilInventory.java b/src/main/java/org/bukkit/inventory/AnvilInventory.java
-index c60be4fd24c7fdf65251dd6169e5e1ac3b588d95..569deccd2f1cf21da9b5906433ac493c1f2081be 100644
---- a/src/main/java/org/bukkit/inventory/AnvilInventory.java
-+++ b/src/main/java/org/bukkit/inventory/AnvilInventory.java
-@@ -123,4 +123,14 @@ public interface AnvilInventory extends Inventory {
- setItem(2, result);
- }
- // Paper end
-+
-+ // Purpur start
-+ boolean canBypassCost();
-+
-+ void setBypassCost(boolean bypassCost);
-+
-+ boolean canDoUnsafeEnchants();
-+
-+ void setDoUnsafeEnchants(boolean canDoUnsafeEnchants);
-+ // Purpur end
- }
-diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java
-index d15a74c38576c49df61cfab02c70fc5d8c0dd5f7..64055402076b62d32ba947830d935b79bae12d95 100644
---- a/src/main/java/org/bukkit/inventory/ItemStack.java
-+++ b/src/main/java/org/bukkit/inventory/ItemStack.java
-@@ -17,6 +17,18 @@ import org.bukkit.inventory.meta.ItemMeta;
- import org.bukkit.material.MaterialData;
- import org.jetbrains.annotations.NotNull;
- import org.jetbrains.annotations.Nullable;
-+// Purpur start
-+import com.google.common.collect.Multimap;
-+import java.util.Collection;
-+import org.bukkit.attribute.Attribute;
-+import org.bukkit.attribute.AttributeModifier;
-+import org.bukkit.block.data.BlockData;
-+import org.bukkit.inventory.meta.BlockDataMeta;
-+import org.bukkit.inventory.meta.Repairable;
-+import org.bukkit.persistence.PersistentDataContainer;
-+import org.bukkit.persistence.PersistentDataHolder;
-+import com.destroystokyo.paper.Namespaced;
-+// Purpur end
-
- /**
- * Represents a stack of items.
-@@ -986,4 +998,626 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, Translat
- return livingEntity.damageItemStack(this, amount);
- }
- // Paper end
-+
-+ // Purpur start
-+ /**
-+ * Gets the display name that is set.
-+ *
-+ * Plugins should check that hasDisplayName() returns true
-+ * before calling this method.
-+ *
-+ * @return the display name that is set
-+ */
-+ @NotNull
-+ public String getDisplayName() {
-+ return getItemMeta().getDisplayName();
-+ }
-+
-+ /**
-+ * Sets the display name.
-+ *
-+ * @param name the name to set
-+ */
-+ public void setDisplayName(@Nullable String name) {
-+ ItemMeta itemMeta = getItemMeta();
-+ itemMeta.setDisplayName(name);
-+ setItemMeta(itemMeta);
-+ }
-+
-+ /**
-+ * Checks for existence of a display name.
-+ *
-+ * @return true if this has a display name
-+ */
-+ public boolean hasDisplayName() {
-+ return hasItemMeta() && getItemMeta().hasDisplayName();
-+ }
-+
-+ /**
-+ * Gets the localized display name that is set.
-+ *
-+ * Plugins should check that hasLocalizedName() returns true
-+ * before calling this method.
-+ *
-+ * @return the localized name that is set
-+ */
-+ @NotNull
-+ public String getLocalizedName() {
-+ return getItemMeta().getLocalizedName();
-+ }
-+
-+ /**
-+ * Sets the localized name.
-+ *
-+ * @param name the name to set
-+ */
-+ public void setLocalizedName(@Nullable String name) {
-+ ItemMeta itemMeta = getItemMeta();
-+ itemMeta.setLocalizedName(name);
-+ setItemMeta(itemMeta);
-+ }
-+
-+ /**
-+ * Checks for existence of a localized name.
-+ *
-+ * @return true if this has a localized name
-+ */
-+ public boolean hasLocalizedName() {
-+ return hasItemMeta() && getItemMeta().hasLocalizedName();
-+ }
-+
-+ /**
-+ * Checks for existence of lore.
-+ *
-+ * @return true if this has lore
-+ */
-+ public boolean hasLore() {
-+ return hasItemMeta() && getItemMeta().hasLore();
-+ }
-+
-+ /**
-+ * Checks for existence of the specified enchantment.
-+ *
-+ * @param ench enchantment to check
-+ * @return true if this enchantment exists for this meta
-+ */
-+ public boolean hasEnchant(@NotNull Enchantment ench) {
-+ return hasItemMeta() && getItemMeta().hasEnchant(ench);
-+ }
-+
-+ /**
-+ * Checks for the level of the specified enchantment.
-+ *
-+ * @param ench enchantment to check
-+ * @return The level that the specified enchantment has, or 0 if none
-+ */
-+ public int getEnchantLevel(@NotNull Enchantment ench) {
-+ return getItemMeta().getEnchantLevel(ench);
-+ }
-+
-+ /**
-+ * Returns a copy the enchantments in this ItemMeta.
-+ * Returns an empty map if none.
-+ *
-+ * @return An immutable copy of the enchantments
-+ */
-+ @NotNull
-+ public Map getEnchants() {
-+ return getItemMeta().getEnchants();
-+ }
-+
-+ /**
-+ * Adds the specified enchantment to this item meta.
-+ *
-+ * @param ench Enchantment to add
-+ * @param level Level for the enchantment
-+ * @param ignoreLevelRestriction this indicates the enchantment should be
-+ * applied, ignoring the level limit
-+ * @return true if the item meta changed as a result of this call, false
-+ * otherwise
-+ */
-+ public boolean addEnchant(@NotNull Enchantment ench, int level, boolean ignoreLevelRestriction) {
-+ ItemMeta itemMeta = getItemMeta();
-+ boolean result = itemMeta.addEnchant(ench, level, ignoreLevelRestriction);
-+ setItemMeta(itemMeta);
-+ return result;
-+ }
-+
-+ /**
-+ * Removes the specified enchantment from this item meta.
-+ *
-+ * @param ench Enchantment to remove
-+ * @return true if the item meta changed as a result of this call, false
-+ * otherwise
-+ */
-+ public boolean removeEnchant(@NotNull Enchantment ench) {
-+ ItemMeta itemMeta = getItemMeta();
-+ boolean result = itemMeta.removeEnchant(ench);
-+ setItemMeta(itemMeta);
-+ return result;
-+ }
-+
-+ /**
-+ * Checks for the existence of any enchantments.
-+ *
-+ * @return true if an enchantment exists on this meta
-+ */
-+ public boolean hasEnchants() {
-+ return hasItemMeta() && getItemMeta().hasEnchants();
-+ }
-+
-+ /**
-+ * Checks if the specified enchantment conflicts with any enchantments in
-+ * this ItemMeta.
-+ *
-+ * @param ench enchantment to test
-+ * @return true if the enchantment conflicts, false otherwise
-+ */
-+ public boolean hasConflictingEnchant(@NotNull Enchantment ench) {
-+ return hasItemMeta() && getItemMeta().hasConflictingEnchant(ench);
-+ }
-+
-+ /**
-+ * Sets the custom model data.
-+ *
-+ * CustomModelData is an integer that may be associated client side with a
-+ * custom item model.
-+ *
-+ * @param data the data to set, or null to clear
-+ */
-+ public void setCustomModelData(@Nullable Integer data) {
-+ ItemMeta itemMeta = getItemMeta();
-+ itemMeta.setCustomModelData(data);
-+ setItemMeta(itemMeta);
-+ }
-+
-+ /**
-+ * Gets the custom model data that is set.
-+ *
-+ * CustomModelData is an integer that may be associated client side with a
-+ * custom item model.
-+ *
-+ * Plugins should check that hasCustomModelData() returns true
-+ * before calling this method.
-+ *
-+ * @return the localized name that is set
-+ */
-+ public int getCustomModelData() {
-+ return getItemMeta().getCustomModelData();
-+ }
-+
-+ /**
-+ * Checks for existence of custom model data.
-+ *
-+ * CustomModelData is an integer that may be associated client side with a
-+ * custom item model.
-+ *
-+ * @return true if this has custom model data
-+ */
-+ public boolean hasCustomModelData() {
-+ return hasItemMeta() && getItemMeta().hasCustomModelData();
-+ }
-+
-+ /**
-+ * Returns whether the item has block data currently attached to it.
-+ *
-+ * @return whether block data is already attached
-+ */
-+ public boolean hasBlockData() {
-+ return hasItemMeta() && ((BlockDataMeta) getItemMeta()).hasBlockData();
-+ }
-+
-+ /**
-+ * Returns the currently attached block data for this item or creates a new
-+ * one if one doesn't exist.
-+ *
-+ * The state is a copy, it must be set back (or to another item) with
-+ * {@link #setBlockData(BlockData)}
-+ *
-+ * @param material the material we wish to get this data in the context of
-+ * @return the attached data or new data
-+ */
-+ @NotNull
-+ public BlockData getBlockData(@NotNull Material material) {
-+ return ((BlockDataMeta) getItemMeta()).getBlockData(material);
-+ }
-+
-+ /**
-+ * Attaches a copy of the passed block data to the item.
-+ *
-+ * @param blockData the block data to attach to the block.
-+ * @throws IllegalArgumentException if the blockData is null or invalid for
-+ * this item.
-+ */
-+ public void setBlockData(@NotNull BlockData blockData) {
-+ ItemMeta itemMeta = getItemMeta();
-+ ((BlockDataMeta) itemMeta).setBlockData(blockData);
-+ setItemMeta(itemMeta);
-+ }
-+
-+ /**
-+ * Gets the repair penalty
-+ *
-+ * @return the repair penalty
-+ */
-+ public int getRepairCost() {
-+ return ((Repairable) getItemMeta()).getRepairCost();
-+ }
-+
-+ /**
-+ * Sets the repair penalty
-+ *
-+ * @param cost repair penalty
-+ */
-+ public void setRepairCost(int cost) {
-+ ItemMeta itemMeta = getItemMeta();
-+ ((Repairable) itemMeta).setRepairCost(cost);
-+ setItemMeta(itemMeta);
-+ }
-+
-+ /**
-+ * Checks to see if this has a repair penalty
-+ *
-+ * @return true if this has a repair penalty
-+ */
-+ public boolean hasRepairCost() {
-+ return hasItemMeta() && ((Repairable) getItemMeta()).hasRepairCost();
-+ }
-+
-+ /**
-+ * Return if the unbreakable tag is true. An unbreakable item will not lose
-+ * durability.
-+ *
-+ * @return true if the unbreakable tag is true
-+ */
-+ public boolean isUnbreakable() {
-+ return hasItemMeta() && getItemMeta().isUnbreakable();
-+ }
-+
-+ /**
-+ * Sets the unbreakable tag. An unbreakable item will not lose durability.
-+ *
-+ * @param unbreakable true if set unbreakable
-+ */
-+ public void setUnbreakable(boolean unbreakable) {
-+ ItemMeta itemMeta = getItemMeta();
-+ itemMeta.setUnbreakable(unbreakable);
-+ setItemMeta(itemMeta);
-+ }
-+
-+ /**
-+ * Checks for the existence of any AttributeModifiers.
-+ *
-+ * @return true if any AttributeModifiers exist
-+ */
-+ public boolean hasAttributeModifiers() {
-+ return hasItemMeta() && getItemMeta().hasAttributeModifiers();
-+ }
-+
-+ /**
-+ * Return an immutable copy of all Attributes and
-+ * their modifiers in this ItemMeta.
-+ * Returns null if none exist.
-+ *
-+ * @return an immutable {@link Multimap} of Attributes
-+ * and their AttributeModifiers, or null if none exist
-+ */
-+ @Nullable
-+ public Multimap getAttributeModifiers() {
-+ return getItemMeta().getAttributeModifiers();
-+ }
-+
-+ /**
-+ * Return an immutable copy of all {@link Attribute}s and their
-+ * {@link AttributeModifier}s for a given {@link EquipmentSlot}.
-+ * Any {@link AttributeModifier} that does have have a given
-+ * {@link EquipmentSlot} will be returned. This is because
-+ * AttributeModifiers without a slot are active in any slot.
-+ * If there are no attributes set for the given slot, an empty map
-+ * will be returned.
-+ *
-+ * @param slot the {@link EquipmentSlot} to check
-+ * @return the immutable {@link Multimap} with the
-+ * respective Attributes and modifiers, or an empty map
-+ * if no attributes are set.
-+ */
-+ @NotNull
-+ public Multimap getAttributeModifiers(@Nullable EquipmentSlot slot) {
-+ return getItemMeta().getAttributeModifiers(slot);
-+ }
-+
-+ /**
-+ * Return an immutable copy of all {@link AttributeModifier}s
-+ * for a given {@link Attribute}
-+ *
-+ * @param attribute the {@link Attribute}
-+ * @return an immutable collection of {@link AttributeModifier}s
-+ * or null if no AttributeModifiers exist for the Attribute.
-+ * @throws NullPointerException if Attribute is null
-+ */
-+ @Nullable
-+ public Collection getAttributeModifiers(@NotNull Attribute attribute) {
-+ return getItemMeta().getAttributeModifiers(attribute);
-+ }
-+
-+ /**
-+ * Add an Attribute and it's Modifier.
-+ * AttributeModifiers can now support {@link EquipmentSlot}s.
-+ * If not set, the {@link AttributeModifier} will be active in ALL slots.
-+ *
-+ * Two {@link AttributeModifier}s that have the same {@link java.util.UUID}
-+ * cannot exist on the same Attribute.
-+ *
-+ * @param attribute the {@link Attribute} to modify
-+ * @param modifier the {@link AttributeModifier} specifying the modification
-+ * @return true if the Attribute and AttributeModifier were
-+ * successfully added
-+ * @throws NullPointerException if Attribute is null
-+ * @throws NullPointerException if AttributeModifier is null
-+ * @throws IllegalArgumentException if AttributeModifier already exists
-+ */
-+ public boolean addAttributeModifier(@NotNull Attribute attribute, @NotNull AttributeModifier modifier) {
-+ ItemMeta itemMeta = getItemMeta();
-+ boolean result = itemMeta.addAttributeModifier(attribute, modifier);
-+ setItemMeta(itemMeta);
-+ return result;
-+ }
-+
-+ /**
-+ * Set all {@link Attribute}s and their {@link AttributeModifier}s.
-+ * To clear all currently set Attributes and AttributeModifiers use
-+ * null or an empty Multimap.
-+ * If not null nor empty, this will filter all entries that are not-null
-+ * and add them to the ItemStack.
-+ *
-+ * @param attributeModifiers the new Multimap containing the Attributes
-+ * and their AttributeModifiers
-+ */
-+ public void setAttributeModifiers(@Nullable Multimap attributeModifiers) {
-+ ItemMeta itemMeta = getItemMeta();
-+ itemMeta.setAttributeModifiers(attributeModifiers);
-+ setItemMeta(itemMeta);
-+ }
-+
-+ /**
-+ * Remove all {@link AttributeModifier}s associated with the given
-+ * {@link Attribute}.
-+ * This will return false if nothing was removed.
-+ *
-+ * @param attribute attribute to remove
-+ * @return true if all modifiers were removed from a given
-+ * Attribute. Returns false if no attributes were
-+ * removed.
-+ * @throws NullPointerException if Attribute is null
-+ */
-+ public boolean removeAttributeModifier(@NotNull Attribute attribute) {
-+ ItemMeta itemMeta = getItemMeta();
-+ boolean result = itemMeta.removeAttributeModifier(attribute);
-+ setItemMeta(itemMeta);
-+ return result;
-+ }
-+
-+ /**
-+ * Remove all {@link Attribute}s and {@link AttributeModifier}s for a
-+ * given {@link EquipmentSlot}.
-+ * If the given {@link EquipmentSlot} is null, this will remove all
-+ * {@link AttributeModifier}s that do not have an EquipmentSlot set.
-+ *
-+ * @param slot the {@link EquipmentSlot} to clear all Attributes and
-+ * their modifiers for
-+ * @return true if all modifiers were removed that match the given
-+ * EquipmentSlot.
-+ */
-+ public boolean removeAttributeModifier(@Nullable EquipmentSlot slot) {
-+ ItemMeta itemMeta = getItemMeta();
-+ boolean result = itemMeta.removeAttributeModifier(slot);
-+ setItemMeta(itemMeta);
-+ return result;
-+ }
-+
-+ /**
-+ * Remove a specific {@link Attribute} and {@link AttributeModifier}.
-+ * AttributeModifiers are matched according to their {@link java.util.UUID}.
-+ *
-+ * @param attribute the {@link Attribute} to remove
-+ * @param modifier the {@link AttributeModifier} to remove
-+ * @return if any attribute modifiers were remove
-+ *
-+ * @throws NullPointerException if the Attribute is null
-+ * @throws NullPointerException if the AttributeModifier is null
-+ *
-+ * @see AttributeModifier#getUniqueId()
-+ */
-+ public boolean removeAttributeModifier(@NotNull Attribute attribute, @NotNull AttributeModifier modifier) {
-+ ItemMeta itemMeta = getItemMeta();
-+ boolean result = itemMeta.removeAttributeModifier(attribute, modifier);
-+ setItemMeta(itemMeta);
-+ return result;
-+ }
-+
-+ /**
-+ * Returns a custom tag container capable of storing tags on the object.
-+ *
-+ * Note that the tags stored on this container are all stored under their
-+ * own custom namespace therefore modifying default tags using this
-+ * {@link PersistentDataHolder} is impossible.
-+ *
-+ * @return the persistent metadata container
-+ */
-+ @NotNull
-+ public PersistentDataContainer getPersistentDataContainer() {
-+ return getItemMeta().getPersistentDataContainer();
-+ }
-+
-+ /**
-+ * Checks to see if this item has damage
-+ *
-+ * @return true if this has damage
-+ */
-+ public boolean hasDamage() {
-+ return hasItemMeta() && ((Damageable) getItemMeta()).hasDamage();
-+ }
-+
-+ /**
-+ * Gets the damage
-+ *
-+ * @return the damage
-+ */
-+ public int getDamage() {
-+ return ((Damageable) getItemMeta()).getDamage();
-+ }
-+
-+ /**
-+ * Sets the damage
-+ *
-+ * @param damage item damage
-+ */
-+ public void setDamage(int damage) {
-+ ItemMeta itemMeta = getItemMeta();
-+ ((Damageable) itemMeta).setDamage(damage);
-+ setItemMeta(itemMeta);
-+ }
-+
-+ /**
-+ * Gets the collection of namespaced keys that the item can destroy in {@link org.bukkit.GameMode#ADVENTURE}
-+ *
-+ * @return Set of {@link com.destroystokyo.paper.Namespaced}
-+ */
-+ @NotNull
-+ public java.util.Set getDestroyableKeys() {
-+ return getItemMeta().getDestroyableKeys();
-+ }
-+
-+ /**
-+ * Sets the collection of namespaced keys that the item can destroy in {@link org.bukkit.GameMode#ADVENTURE}
-+ *
-+ * @param canDestroy Collection of {@link com.destroystokyo.paper.Namespaced}
-+ */
-+ public void setDestroyableKeys(@NotNull Collection canDestroy) {
-+ ItemMeta itemMeta = getItemMeta();
-+ itemMeta.setDestroyableKeys(canDestroy);
-+ setItemMeta(itemMeta);
-+ }
-+
-+ /**
-+ * Gets the collection of namespaced keys that the item can be placed on in {@link org.bukkit.GameMode#ADVENTURE}
-+ *
-+ * @return Set of {@link com.destroystokyo.paper.Namespaced}
-+ */
-+ @NotNull
-+ public java.util.Set getPlaceableKeys() {
-+ return getItemMeta().getPlaceableKeys();
-+ }
-+
-+ /**
-+ * Sets the set of namespaced keys that the item can be placed on in {@link org.bukkit.GameMode#ADVENTURE}
-+ *
-+ * @param canPlaceOn Collection of {@link com.destroystokyo.paper.Namespaced}
-+ */
-+ @NotNull
-+ public void setPlaceableKeys(@NotNull Collection canPlaceOn) {
-+ ItemMeta itemMeta = getItemMeta();
-+ itemMeta.setPlaceableKeys(canPlaceOn);
-+ setItemMeta(itemMeta);
-+ }
-+
-+ /**
-+ * Checks for the existence of any keys that the item can be placed on
-+ *
-+ * @return true if this item has placeable keys
-+ */
-+ public boolean hasPlaceableKeys() {
-+ return hasItemMeta() && getItemMeta().hasPlaceableKeys();
-+ }
-+
-+ /**
-+ * Checks for the existence of any keys that the item can destroy
-+ *
-+ * @return true if this item has destroyable keys
-+ */
-+ public boolean hasDestroyableKeys() {
-+ return hasItemMeta() && getItemMeta().hasDestroyableKeys();
-+ }
-+
-+ /**
-+ * Repairs this item by 1 durability
-+ */
-+ public void repair() {
-+ repair(1);
-+ }
-+
-+ /**
-+ * Damages this item by 1 durability
-+ *
-+ * @return True if damage broke the item
-+ */
-+ public boolean damage() {
-+ return damage(1);
-+ }
-+
-+ /**
-+ * Repairs this item's durability by amount
-+ *
-+ * @param amount Amount of durability to repair
-+ */
-+ public void repair(int amount) {
-+ damage(-amount);
-+ }
-+
-+ /**
-+ * Damages this item's durability by amount
-+ *
-+ * @param amount Amount of durability to damage
-+ * @return True if damage broke the item
-+ */
-+ public boolean damage(int amount) {
-+ return damage(amount, false);
-+ }
-+
-+ /**
-+ * Damages this item's durability by amount
-+ *
-+ * @param amount Amount of durability to damage
-+ * @param ignoreUnbreaking Ignores unbreaking enchantment
-+ * @return True if damage broke the item
-+ */
-+ public boolean damage(int amount, boolean ignoreUnbreaking) {
-+ Damageable damageable = (Damageable) getItemMeta();
-+ if (amount > 0) {
-+ int unbreaking = getEnchantLevel(Enchantment.DURABILITY);
-+ int reduce = 0;
-+ for (int i = 0; unbreaking > 0 && i < amount; ++i) {
-+ if (reduceDamage(java.util.concurrent.ThreadLocalRandom.current(), unbreaking)) {
-+ ++reduce;
-+ }
-+ }
-+ amount -= reduce;
-+ if (amount <= 0) {
-+ return isBroke(damageable.getDamage());
-+ }
-+ }
-+ int damage = damageable.getDamage() + amount;
-+ damageable.setDamage(damage);
-+ setItemMeta((ItemMeta) damageable);
-+ return isBroke(damage);
-+ }
-+
-+ public boolean isBroke(int damage) {
-+ if (damage > getType().getMaxDurability()) {
-+ if (getAmount() > 0) {
-+ // ensure it "breaks"
-+ setAmount(0);
-+ }
-+ return true;
-+ }
-+ return false;
-+ }
-+
-+ private boolean reduceDamage(java.util.Random random, int unbreaking) {
-+ if (getType().isArmor()) {
-+ return random.nextFloat() < 0.6F;
-+ }
-+ return random.nextInt(unbreaking + 1) > 0;
-+ }
-+ // Purpur end
- }
-diff --git a/src/main/java/org/bukkit/inventory/RecipeChoice.java b/src/main/java/org/bukkit/inventory/RecipeChoice.java
-index 523818cbb0d6c90481ec97123e7fe0e2ff4eea14..bfeb8171a723d84b94bfaacd8aaf7d4d48ecd051 100644
---- a/src/main/java/org/bukkit/inventory/RecipeChoice.java
-+++ b/src/main/java/org/bukkit/inventory/RecipeChoice.java
-@@ -10,6 +10,7 @@ import java.util.function.Predicate;
- import org.bukkit.Material;
- import org.bukkit.Tag;
- import org.jetbrains.annotations.NotNull;
-+import org.jetbrains.annotations.Nullable; // Purpur
-
- /**
- * Represents a potential item match within a recipe. All choices within a
-@@ -150,6 +151,7 @@ public interface RecipeChoice extends Predicate, Cloneable {
- public static class ExactChoice implements RecipeChoice {
-
- private List choices;
-+ private Predicate predicate; // Purpur
-
- public ExactChoice(@NotNull ItemStack stack) {
- this(Arrays.asList(stack));
-@@ -194,6 +196,7 @@ public interface RecipeChoice extends Predicate, Cloneable {
-
- @Override
- public boolean test(@NotNull ItemStack t) {
-+ if (predicate != null) return predicate.test(t); // Purpur
- for (ItemStack match : choices) {
- if (t.isSimilar(match)) {
- return true;
-@@ -203,6 +206,17 @@ public interface RecipeChoice extends Predicate, Cloneable {
- return false;
- }
-
-+ // Purpur start
-+ @Nullable
-+ public Predicate getPredicate() {
-+ return predicate;
-+ }
-+
-+ public void setPredicate(@Nullable Predicate predicate) {
-+ this.predicate = predicate;
-+ }
-+ // Purpur end
-+
- @Override
- public int hashCode() {
- int hash = 7;
-diff --git a/src/main/java/org/bukkit/permissions/PermissibleBase.java b/src/main/java/org/bukkit/permissions/PermissibleBase.java
-index cd3296fea01648592d2af89b3d80135acb6d0958..45797a6fbae1d8edc4211cb30def24ad4f59bd49 100644
---- a/src/main/java/org/bukkit/permissions/PermissibleBase.java
-+++ b/src/main/java/org/bukkit/permissions/PermissibleBase.java
-@@ -168,7 +168,7 @@ public class PermissibleBase implements Permissible {
-
- for (Permission perm : defaults) {
- String name = perm.getName().toLowerCase(java.util.Locale.ENGLISH);
-- 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);
- }
-@@ -196,7 +196,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(java.util.Locale.ENGLISH);
-
- permissions.put(lname, new PermissionAttachmentInfo(parent, lname, attachment, value));
-diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
-index 301e82369603f3dd6e6c1bd380da4bacacd7ef6c..0c6ca7588fb3d6b6497ddf032fe75e5c6c9719e5 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 e4b6f278a811acbb0070e311c5c3bdaff7b00474..ee83ecb054099cb85168a9499dfe967a0a9ec796 100644
---- a/src/main/java/org/bukkit/plugin/java/LibraryLoader.java
-+++ b/src/main/java/org/bukkit/plugin/java/LibraryLoader.java
-@@ -65,6 +65,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() );
- }
- } );
-@@ -80,6 +81,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
-@@ -118,6 +120,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/src/main/java/org/bukkit/potion/PotionEffect.java b/src/main/java/org/bukkit/potion/PotionEffect.java
-index ccdca0d75868135dc7b96daeff2236b225c4add1..cad9f4ddc6be23c595e79419872f8f026703cb80 100644
---- a/src/main/java/org/bukkit/potion/PotionEffect.java
-+++ b/src/main/java/org/bukkit/potion/PotionEffect.java
-@@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableMap;
- import java.util.Map;
- import java.util.NoSuchElementException;
- import org.bukkit.Color;
-+import org.bukkit.NamespacedKey;
- import org.bukkit.configuration.serialization.ConfigurationSerializable;
- import org.bukkit.configuration.serialization.SerializableAs;
- import org.bukkit.entity.LivingEntity;
-@@ -31,12 +32,14 @@ public class PotionEffect implements ConfigurationSerializable {
- private static final String AMBIENT = "ambient";
- private static final String PARTICLES = "has-particles";
- private static final String ICON = "has-icon";
-+ private static final String KEY = "namespacedKey"; // Purpur
- private final int amplifier;
- private final int duration;
- private final PotionEffectType type;
- private final boolean ambient;
- private final boolean particles;
- private final boolean icon;
-+ @Nullable private final NamespacedKey key; // Purpur
-
- /**
- * Creates a potion effect.
-@@ -49,6 +52,36 @@ public class PotionEffect implements ConfigurationSerializable {
- * @param icon the icon status, see {@link PotionEffect#hasIcon()}
- */
- public PotionEffect(@NotNull PotionEffectType type, int duration, int amplifier, boolean ambient, boolean particles, boolean icon) {
-+ // Purpur start
-+ this(type, duration, amplifier, ambient, particles, icon, null);
-+ }
-+
-+ /**
-+ * Create a potion effect.
-+ * @param duration measured in ticks, see {@link
-+ * PotionEffect#getDuration()}
-+ * @param amplifier the amplifier, see {@link PotionEffect#getAmplifier()}
-+ * @param ambient the ambient status, see {@link PotionEffect#isAmbient()}
-+ * @param particles the particle status, see {@link PotionEffect#hasParticles()}
-+ * @param key the namespacedKey, see {@link PotionEffect#getKey()}
-+ */
-+ public PotionEffect(@NotNull PotionEffectType type, int duration, int amplifier, boolean ambient, boolean particles, @Nullable NamespacedKey key) {
-+ this(type, duration, amplifier, ambient, particles, particles, key);
-+ }
-+
-+ /**
-+ * Creates a potion effect.
-+ * @param type effect type
-+ * @param duration measured in ticks, see {@link
-+ * PotionEffect#getDuration()}
-+ * @param amplifier the amplifier, see {@link PotionEffect#getAmplifier()}
-+ * @param ambient the ambient status, see {@link PotionEffect#isAmbient()}
-+ * @param particles the particle status, see {@link PotionEffect#hasParticles()}
-+ * @param icon the icon status, see {@link PotionEffect#hasIcon()}
-+ * @param key the namespacedKey, see {@link PotionEffect#getKey()}
-+ */
-+ public PotionEffect(@NotNull PotionEffectType type, int duration, int amplifier, boolean ambient, boolean particles, boolean icon, @Nullable NamespacedKey key) {
-+ // Purpur end
- Preconditions.checkArgument(type != null, "effect type cannot be null");
- this.type = type;
- this.duration = duration;
-@@ -56,6 +89,7 @@ public class PotionEffect implements ConfigurationSerializable {
- this.ambient = ambient;
- this.particles = particles;
- this.icon = icon;
-+ this.key = key; // Purpur - add key
- }
-
- /**
-@@ -103,36 +137,43 @@ public class PotionEffect implements ConfigurationSerializable {
- * @param map the map to deserialize from
- */
- public PotionEffect(@NotNull Map map) {
-- this(getEffectType(map), getInt(map, DURATION), getInt(map, AMPLIFIER), getBool(map, AMBIENT, false), getBool(map, PARTICLES, true), getBool(map, ICON, getBool(map, PARTICLES, true)));
-+ this(getEffectType(map), getInt(map, DURATION), getInt(map, AMPLIFIER), getBool(map, AMBIENT, false), getBool(map, PARTICLES, true), getBool(map, ICON, getBool(map, PARTICLES, true)), getKey(map)); // Purpur - getKey
- }
-
- // Paper start
- @NotNull
- public PotionEffect withType(@NotNull PotionEffectType type) {
-- return new PotionEffect(type, duration, amplifier, ambient, particles, icon);
-+ return new PotionEffect(type, duration, amplifier, ambient, particles, icon, key); // Purpur - add key
- }
- @NotNull
- public PotionEffect withDuration(int duration) {
-- return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon);
-+ return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon, key); // Purpur - add key
- }
- @NotNull
- public PotionEffect withAmplifier(int amplifier) {
-- return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon);
-+ return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon, key); // Purpur - add key
- }
- @NotNull
- public PotionEffect withAmbient(boolean ambient) {
-- return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon);
-+ return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon, key); // Purpur - add key
- }
- @NotNull
- public PotionEffect withParticles(boolean particles) {
-- return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon);
-+ return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon, key); // Purpur - add key
- }
- @NotNull
- public PotionEffect withIcon(boolean icon) {
-- return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon);
-+ return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon, key); // Purpur - add key
- }
- // Paper end
-
-+ // Purpur start
-+ @NotNull
-+ public PotionEffect withKey(@Nullable NamespacedKey key) {
-+ return new PotionEffect(this.type, duration, amplifier, ambient, particles, icon, key);
-+ }
-+ // Purpur end
-+
- @NotNull
- private static PotionEffectType getEffectType(@NotNull Map, ?> map) {
- int type = getInt(map, TYPE);
-@@ -159,17 +200,33 @@ public class PotionEffect implements ConfigurationSerializable {
- return def;
- }
-
-+ // Purpur start
-+ @Nullable
-+ private static NamespacedKey getKey(@NotNull Map, ?> map) {
-+ Object key = map.get(KEY);
-+ if (key instanceof String stringKey) {
-+ return NamespacedKey.fromString(stringKey);
-+ }
-+ return null;
-+ }
-+ // Purpur end
-+
- @Override
- @NotNull
- public Map serialize() {
-- return ImmutableMap.builder()
-- .put(TYPE, type.getId())
-- .put(DURATION, duration)
-- .put(AMPLIFIER, amplifier)
-- .put(AMBIENT, ambient)
-- .put(PARTICLES, particles)
-- .put(ICON, icon)
-- .build();
-+ // Purpur start - add key, don't serialize if null.
-+ ImmutableMap.Builder builder = ImmutableMap.builder()
-+ .put(TYPE, type.getId())
-+ .put(DURATION, duration)
-+ .put(AMPLIFIER, amplifier)
-+ .put(AMBIENT, ambient)
-+ .put(PARTICLES, particles)
-+ .put(ICON, icon);
-+ if(key != null) {
-+ builder.put(KEY, key.toString());
-+ }
-+ return builder.build();
-+ // Purpur end
- }
-
- /**
-@@ -193,7 +250,7 @@ public class PotionEffect implements ConfigurationSerializable {
- return false;
- }
- PotionEffect that = (PotionEffect) obj;
-- return this.type.equals(that.type) && this.ambient == that.ambient && this.amplifier == that.amplifier && this.duration == that.duration && this.particles == that.particles && this.icon == that.icon;
-+ return this.type.equals(that.type) && this.ambient == that.ambient && this.amplifier == that.amplifier && this.duration == that.duration && this.particles == that.particles && this.icon == that.icon && this.key == that.key; // Purpur - add key
- }
-
- /**
-@@ -289,6 +346,24 @@ public class PotionEffect implements ConfigurationSerializable {
- return icon;
- }
-
-+
-+ // Purpur start
-+ /**
-+ * @return if the key isn't the default namespacedKey
-+ */
-+ public boolean hasKey() {
-+ return key != null;
-+ }
-+
-+ /**
-+ * @return the key attached to the potion
-+ */
-+ @Nullable
-+ public NamespacedKey getKey() {
-+ return key;
-+ }
-+ // Purpur end
-+
- @Override
- public int hashCode() {
- int hash = 1;
-@@ -303,6 +378,6 @@ public class PotionEffect implements ConfigurationSerializable {
-
- @Override
- public String toString() {
-- return type.getName() + (ambient ? ":(" : ":") + duration + "t-x" + amplifier + (ambient ? ")" : "");
-+ return type.getName() + (ambient ? ":(" : ":") + duration + "t-x" + amplifier + (ambient ? ")" : "") + (hasKey() ? "(" + key + ")" : ""); // Purpur - add key if not null
- }
- }
-diff --git a/src/main/java/org/bukkit/util/permissions/CommandPermissions.java b/src/main/java/org/bukkit/util/permissions/CommandPermissions.java
-index 7763d6101ac61900db1e2310966b99584539fd0e..d5a42707d365ffd72532bbb1a59a1ca7145f9918 100644
---- a/src/main/java/org/bukkit/util/permissions/CommandPermissions.java
-+++ b/src/main/java/org/bukkit/util/permissions/CommandPermissions.java
-@@ -18,6 +18,7 @@ public final class CommandPermissions {
- DefaultPermissions.registerPermission(PREFIX + "plugins", "Allows the user to view the list of plugins running on this server", PermissionDefault.TRUE, commands);
- DefaultPermissions.registerPermission(PREFIX + "reload", "Allows the user to reload the server settings", PermissionDefault.OP, commands);
- DefaultPermissions.registerPermission(PREFIX + "version", "Allows the user to view the version of the server", PermissionDefault.TRUE, commands);
-+ DefaultPermissions.registerPermission(PREFIX + "purpur", "Allows the user to use the purpur command", PermissionDefault.OP, commands); // Purpur
-
- commands.recalculatePermissibles();
- return commands;
-diff --git a/src/main/java/org/bukkit/util/permissions/DefaultPermissions.java b/src/main/java/org/bukkit/util/permissions/DefaultPermissions.java
-index e1a4ddf2c07cdd242fa8054a0152522fe4039e85..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;
- }
-
-@@ -89,6 +89,8 @@ public final class DefaultPermissions {
- CommandPermissions.registerPermissions(parent);
- BroadcastPermissions.registerPermissions(parent);
-
-+ PurpurPermissions.registerPermissions(); // Purpur
-+
- parent.recalculatePermissibles();
- }
- }
-diff --git a/src/main/java/org/bukkit/util/permissions/PurpurPermissions.java b/src/main/java/org/bukkit/util/permissions/PurpurPermissions.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..baec4c87d7ea4d54934ca22fd1eb7b46dd69061b
---- /dev/null
-+++ b/src/main/java/org/bukkit/util/permissions/PurpurPermissions.java
-@@ -0,0 +1,87 @@
-+package org.bukkit.util.permissions;
-+
-+import org.bukkit.entity.Entity;
-+import org.bukkit.entity.EntityType;
-+import org.bukkit.entity.Mob;
-+import org.bukkit.permissions.Permission;
-+import org.bukkit.permissions.PermissionDefault;
-+import org.jetbrains.annotations.NotNull;
-+
-+import java.util.HashSet;
-+import java.util.Set;
-+
-+public final class PurpurPermissions {
-+ private static final String ROOT = "purpur";
-+ private static final String PREFIX = ROOT + ".";
-+ private static final Set mobs = new HashSet<>();
-+
-+ static {
-+ for (EntityType mob : EntityType.values()) {
-+ Class extends Entity> clazz = mob.getEntityClass();
-+ if (clazz != null && Mob.class.isAssignableFrom(clazz)) {
-+ mobs.add(mob.getName());
-+ }
-+ }
-+ }
-+
-+ @NotNull
-+ public static Permission registerPermissions() {
-+ Permission purpur = DefaultPermissions.registerPermission(ROOT, "Gives the user the ability to use all Purpur utilities and commands", PermissionDefault.FALSE);
-+
-+ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.six", "Gives the user six rows of enderchest space", PermissionDefault.FALSE, purpur);
-+ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.five", "Gives the user five rows of enderchest space", PermissionDefault.FALSE, purpur);
-+ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.four", "Gives the user four rows of enderchest space", PermissionDefault.FALSE, purpur);
-+ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.three", "Gives the user three rows of enderchest space", PermissionDefault.FALSE, purpur);
-+ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.two", "Gives the user two rows of enderchest space", PermissionDefault.FALSE, purpur);
-+ DefaultPermissions.registerPermission(PREFIX + "enderchest.rows.one", "Gives the user one row of enderchest space", PermissionDefault.FALSE, purpur);
-+
-+ DefaultPermissions.registerPermission(PREFIX + "debug.f3n", "Allows the user to use F3+N keybind to swap gamemodes", PermissionDefault.FALSE, purpur);
-+ DefaultPermissions.registerPermission(PREFIX + "joinfullserver", "Allows the user to join a full server", PermissionDefault.OP, purpur);
-+
-+ DefaultPermissions.registerPermission(PREFIX + "drop.spawners", "Allows the user to drop spawner cage when broken with diamond pickaxe with silk touch", PermissionDefault.FALSE, purpur);
-+ DefaultPermissions.registerPermission(PREFIX + "place.spawners", "Allows the user to place spawner cage in the world", PermissionDefault.FALSE, purpur);
-+
-+ DefaultPermissions.registerPermission(PREFIX + "mending_shift_click", "Allows the user to use shift-right-click to mend items", PermissionDefault.FALSE, purpur);
-+ DefaultPermissions.registerPermission(PREFIX + "inventory_totem", "Uses a totem from anywhere in the user's inventory on death", PermissionDefault.FALSE, purpur);
-+
-+ Permission anvil = DefaultPermissions.registerPermission(PREFIX + "anvil", "Allows the user to use all anvil color and format abilities", PermissionDefault.FALSE, purpur);
-+ DefaultPermissions.registerPermission(PREFIX + "anvil.color", "Allows the user to use color codes in an anvil", PermissionDefault.FALSE, anvil);
-+ DefaultPermissions.registerPermission(PREFIX + "anvil.minimessage", "Allows the user to use minimessage tags in an anvil", PermissionDefault.FALSE, anvil);
-+ DefaultPermissions.registerPermission(PREFIX + "anvil.remove_italics", "Allows the user to remove italics in an anvil", PermissionDefault.FALSE, anvil);
-+ DefaultPermissions.registerPermission(PREFIX + "anvil.format", "Allows the user to use format codes in an anvil", PermissionDefault.FALSE, anvil);
-+ anvil.recalculatePermissibles();
-+
-+ Permission book = DefaultPermissions.registerPermission(PREFIX + "book", "Allows the user to use color codes on books", PermissionDefault.FALSE, purpur);
-+ DefaultPermissions.registerPermission(PREFIX + "book.color.edit", "Allows the user to use color codes on books when editing", PermissionDefault.FALSE, book);
-+ DefaultPermissions.registerPermission(PREFIX + "book.color.sign", "Allows the user to use color codes on books when signing", PermissionDefault.FALSE, book);
-+ book.recalculatePermissibles();
-+
-+ Permission sign = DefaultPermissions.registerPermission(PREFIX + "sign", "Allows the user to use all sign abilities", PermissionDefault.FALSE, purpur);
-+ DefaultPermissions.registerPermission(PREFIX + "sign.edit", "Allows the user to click signs to open sign editor", PermissionDefault.FALSE, sign);
-+ DefaultPermissions.registerPermission(PREFIX + "sign.color", "Allows the user to use color codes on signs", PermissionDefault.FALSE, sign);
-+ DefaultPermissions.registerPermission(PREFIX + "sign.style", "Allows the user to use style codes on signs", PermissionDefault.FALSE, sign);
-+ DefaultPermissions.registerPermission(PREFIX + "sign.magic", "Allows the user to use magic/obfuscate code on signs", PermissionDefault.FALSE, sign);
-+ sign.recalculatePermissibles();
-+
-+ Permission ride = DefaultPermissions.registerPermission("allow.ride", "Allows the user to ride all mobs", PermissionDefault.FALSE, purpur);
-+ for (String mob : mobs) {
-+ DefaultPermissions.registerPermission("allow.ride." + mob, "Allows the user to ride " + mob, PermissionDefault.FALSE, ride);
-+ }
-+ ride.recalculatePermissibles();
-+
-+ Permission special = DefaultPermissions.registerPermission("allow.special", "Allows the user to use all mobs special abilities", PermissionDefault.FALSE, purpur);
-+ for (String mob : mobs) {
-+ DefaultPermissions.registerPermission("allow.special." + mob, "Allows the user to use " + mob + " special ability", PermissionDefault.FALSE, special);
-+ }
-+ special.recalculatePermissibles();
-+
-+ Permission powered = DefaultPermissions.registerPermission("allow.powered", "Allows the user to toggle all mobs powered state", PermissionDefault.FALSE, purpur);
-+ DefaultPermissions.registerPermission("allow.powered.creeper", "Allows the user to toggle creeper powered state", PermissionDefault.FALSE, powered);
-+ powered.recalculatePermissibles();
-+
-+ DefaultPermissions.registerPermission(PREFIX + "portal.instant", "Allows the user to bypass portal wait time", PermissionDefault.FALSE, purpur);
-+
-+ purpur.recalculatePermissibles();
-+ return purpur;
-+ }
-+}
-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..bc590c4d49d32f4365a50ceb5785e798702a8179
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/ExecuteCommandEvent.java
-@@ -0,0 +1,130 @@
-+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.NotNull;
-+import org.jetbrains.annotations.Nullable;
-+
-+/**
-+ * This event is called whenever someone runs a command
-+ */
-+public class ExecuteCommandEvent extends Event implements Cancellable {
-+ private static final HandlerList handlers = new HandlerList();
-+ private boolean cancel = false;
-+ private CommandSender sender;
-+ private Command command;
-+ private String label;
-+ private String[] args;
-+
-+ public ExecuteCommandEvent(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @Nullable String[] args) {
-+ this.sender = sender;
-+ this.command = command;
-+ this.label = label;
-+ this.args = args;
-+ }
-+
-+ /**
-+ * Gets the command that the player is attempting to execute.
-+ *
-+ * @return Command the player is attempting to execute
-+ */
-+ @NotNull
-+ public Command getCommand() {
-+ return command;
-+ }
-+
-+ /**
-+ * Sets the command that the player will execute.
-+ *
-+ * @param command New command that the player will execute
-+ * @throws IllegalArgumentException if command is null or empty
-+ */
-+ public void setCommand(@NotNull Command command) throws IllegalArgumentException {
-+ 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
-+ */
-+ @NotNull
-+ public CommandSender getSender() {
-+ return sender;
-+ }
-+
-+ /**
-+ * Sets the sender that this command will be executed as.
-+ *
-+ * @param sender New sender which this event will execute as
-+ * @throws IllegalArgumentException if the sender provided is null
-+ */
-+ public void setSender(@NotNull final CommandSender sender) throws IllegalArgumentException {
-+ 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
-+ */
-+ @NotNull
-+ public String getLabel() {
-+ return label;
-+ }
-+
-+ /**
-+ * Set the label used to execute this command
-+ *
-+ * @param label Label used
-+ */
-+ public void setLabel(@NotNull String label) {
-+ this.label = label;
-+ }
-+
-+ /**
-+ * Get the args passed to the command
-+ *
-+ * @return Args passed to the command
-+ */
-+ @NotNull
-+ public String[] getArgs() {
-+ return args;
-+ }
-+
-+ /**
-+ * Set the args passed to the command
-+ *
-+ * @param args Args passed to the command
-+ */
-+ public void setArgs(@NotNull String[] args) {
-+ this.args = args;
-+ }
-+
-+ @Override
-+ public boolean isCancelled() {
-+ return cancel;
-+ }
-+
-+ @Override
-+ public void setCancelled(boolean cancel) {
-+ this.cancel = cancel;
-+ }
-+
-+ @NotNull
-+ @Override
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ @NotNull
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
-diff --git a/src/main/java/org/purpurmc/purpur/event/PlayerAFKEvent.java b/src/main/java/org/purpurmc/purpur/event/PlayerAFKEvent.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..25e92af7710316ed2afedf846a59dbd672869b51
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/PlayerAFKEvent.java
-@@ -0,0 +1,70 @@
-+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.NotNull;
-+import org.jetbrains.annotations.Nullable;
-+
-+public class PlayerAFKEvent extends PlayerEvent implements Cancellable {
-+ private static final HandlerList handlers = new HandlerList();
-+ private final boolean setAfk;
-+ private boolean shouldKick;
-+ private String broadcast;
-+ private boolean cancel;
-+
-+ public PlayerAFKEvent(@NotNull Player player, boolean setAfk, boolean shouldKick, @Nullable String broadcast, boolean async) {
-+ super(player, async);
-+ this.setAfk = setAfk;
-+ this.shouldKick = shouldKick;
-+ this.broadcast = broadcast;
-+ }
-+
-+ /**
-+ * Whether player is going afk or coming back
-+ *
-+ * @return True if going afk. False is coming back
-+ */
-+ public boolean isGoingAfk() {
-+ return setAfk;
-+ }
-+
-+ public boolean shouldKick() {
-+ return shouldKick;
-+ }
-+
-+ public void setShouldKick(boolean shouldKick) {
-+ this.shouldKick = shouldKick;
-+ }
-+
-+ @Nullable
-+ public String getBroadcastMsg() {
-+ return broadcast;
-+ }
-+
-+ public void setBroadcastMsg(@Nullable String broadcast) {
-+ this.broadcast = broadcast;
-+ }
-+
-+ @Override
-+ public boolean isCancelled() {
-+ return cancel;
-+ }
-+
-+ @Override
-+ public void setCancelled(boolean cancel) {
-+ this.cancel = cancel;
-+ }
-+
-+ @Override
-+ @NotNull
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ @NotNull
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
-diff --git a/src/main/java/org/purpurmc/purpur/event/PlayerSetSpawnerTypeWithEggEvent.java b/src/main/java/org/purpurmc/purpur/event/PlayerSetSpawnerTypeWithEggEvent.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..519809eab5d926dc7b0a7bad5d446d0defc099dc
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/PlayerSetSpawnerTypeWithEggEvent.java
-@@ -0,0 +1,85 @@
-+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.NotNull;
-+
-+public class PlayerSetSpawnerTypeWithEggEvent extends PlayerEvent implements Cancellable {
-+ private static final HandlerList handlers = new HandlerList();
-+ private final Block block;
-+ private final CreatureSpawner spawner;
-+ private EntityType type;
-+ private boolean cancel;
-+
-+ public PlayerSetSpawnerTypeWithEggEvent(@NotNull Player player, @NotNull Block block, @NotNull CreatureSpawner spawner, @NotNull EntityType type) {
-+ super(player);
-+ this.block = block;
-+ this.spawner = spawner;
-+ this.type = type;
-+ }
-+
-+ /**
-+ * Get the spawner Block in the world
-+ *
-+ * @return Spawner Block
-+ */
-+ @NotNull
-+ public Block getBlock() {
-+ return block;
-+ }
-+
-+ /**
-+ * Get the spawner state
-+ *
-+ * @return Spawner state
-+ */
-+ @NotNull
-+ public CreatureSpawner getSpawner() {
-+ return spawner;
-+ }
-+
-+ /**
-+ * Gets the EntityType being set on the spawner
-+ *
-+ * @return EntityType being set
-+ */
-+ @NotNull
-+ public EntityType getEntityType() {
-+ return type;
-+ }
-+
-+ /**
-+ * Sets the EntityType being set on the spawner
-+ *
-+ * @param type EntityType to set
-+ */
-+ public void setEntityType(@NotNull EntityType type) {
-+ this.type = type;
-+ }
-+
-+ @Override
-+ public boolean isCancelled() {
-+ return cancel;
-+ }
-+
-+ @Override
-+ public void setCancelled(boolean cancel) {
-+ this.cancel = cancel;
-+ }
-+
-+ @Override
-+ @NotNull
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ @NotNull
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
-diff --git a/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..b7db0db7f3afbccdb07390d1bcada109e9e6b30b
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/PreBlockExplodeEvent.java
-@@ -0,0 +1,52 @@
-+package org.purpurmc.purpur.event;
-+
-+import org.bukkit.block.Block;
-+import org.bukkit.event.Cancellable;
-+import org.bukkit.event.HandlerList;
-+import org.bukkit.event.block.BlockExplodeEvent;
-+import org.jetbrains.annotations.NotNull;
-+import java.util.Collections;
-+
-+/**
-+ * Called before a block's explosion is processed
-+ */
-+public class PreBlockExplodeEvent extends BlockExplodeEvent implements Cancellable {
-+ private static final HandlerList handlers = new HandlerList();
-+ private boolean cancelled;
-+ private final float yield;
-+
-+ public PreBlockExplodeEvent(@NotNull final Block what, final float yield) {
-+ super(what, Collections.emptyList(), yield);
-+ 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 @NotNull HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ @NotNull
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
-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..833f46d1941f377765132fc528c45567ee0290d2
---- /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.NotNull;
-+import org.jetbrains.annotations.Nullable;
-+
-+/**
-+ * Called when a bee targets a flower
-+ */
-+public class BeeFoundFlowerEvent extends EntityEvent {
-+ private static final HandlerList handlers = new HandlerList();
-+ private final Location location;
-+
-+ public BeeFoundFlowerEvent(@NotNull Bee bee, @Nullable Location location) {
-+ super(bee);
-+ this.location = location;
-+ }
-+
-+ @Override
-+ @NotNull
-+ 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
-+ @NotNull
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ @NotNull
-+ 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..ae0bb654745724889c67fae9072ae90ea3778ba4
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/entity/BeeStartedPollinatingEvent.java
-@@ -0,0 +1,47 @@
-+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.NotNull;
-+
-+/**
-+ * Called when a bee starts pollinating
-+ */
-+public class BeeStartedPollinatingEvent extends EntityEvent {
-+ private static final HandlerList handlers = new HandlerList();
-+ private final Location location;
-+
-+ public BeeStartedPollinatingEvent(@NotNull Bee bee, @NotNull Location location) {
-+ super(bee);
-+ this.location = location;
-+ }
-+
-+ @Override
-+ @NotNull
-+ public Bee getEntity() {
-+ return (Bee) super.getEntity();
-+ }
-+
-+ /**
-+ * Returns the location of the flower that the bee pollinates
-+ *
-+ * @return The location of the flower
-+ */
-+ @NotNull
-+ public Location getLocation() {
-+ return this.location;
-+ }
-+
-+ @Override
-+ @NotNull
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ @NotNull
-+ 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..ff3c9f075be2f624af8b0ce5fffc5ea69a41f32e
---- /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.NotNull;
-+import org.jetbrains.annotations.Nullable;
-+
-+/**
-+ * Called when a bee stops pollinating
-+ */
-+public class BeeStopPollinatingEvent extends EntityEvent {
-+ private static final HandlerList handlers = new HandlerList();
-+ private final Location location;
-+ private final boolean success;
-+
-+ public BeeStopPollinatingEvent(@NotNull Bee bee, @Nullable Location location, boolean success) {
-+ super(bee);
-+ this.location = location;
-+ this.success = success;
-+ }
-+
-+ @Override
-+ @NotNull
-+ 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
-+ @NotNull
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ @NotNull
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
-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..c66eb163877e872f234d86dc244cab7efeb818cd
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/entity/EntityTeleportHinderedEvent.java
-@@ -0,0 +1,117 @@
-+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.NotNull;
-+import org.jetbrains.annotations.Nullable;
-+
-+/**
-+ * Fired when an entity is hindered from teleporting.
-+ */
-+public class EntityTeleportHinderedEvent extends EntityEvent {
-+ private static final HandlerList handlers = new HandlerList();
-+
-+ @NotNull
-+ private final Reason reason;
-+
-+ @Nullable
-+ private final TeleportCause teleportCause;
-+
-+ private boolean retry = false;
-+
-+ public EntityTeleportHinderedEvent(@NotNull Entity what, @NotNull Reason reason,
-+ @Nullable TeleportCause teleportCause) {
-+ super(what);
-+ this.reason = reason;
-+ this.teleportCause = teleportCause;
-+ }
-+
-+ /**
-+ * @return why the teleport was hindered.
-+ */
-+ @NotNull
-+ public Reason getReason() {
-+ return reason;
-+ }
-+
-+ /**
-+ * @return why the teleport occurred if cause was given, otherwise {@code null}.
-+ */
-+ @Nullable
-+ public TeleportCause getTeleportCause() {
-+ return teleportCause;
-+ }
-+
-+ /**
-+ * Whether the teleport should be retried.
-+ *
-+ * Note that this can put the server in a never-ending loop of trying to teleport someone resulting in a stack
-+ * overflow. Do not retry more than necessary.
-+ *
-+ *
-+ * @return whether the teleport should be retried.
-+ */
-+ public boolean shouldRetry() {
-+ return retry;
-+ }
-+
-+ /**
-+ * Sets whether the teleport should be retried.
-+ *
-+ * Note that this can put the server in a never-ending loop of trying to teleport someone resulting in a stack
-+ * overflow. Do not retry more than necessary.
-+ *
-+ *
-+ * @param retry whether the teleport should be retried.
-+ */
-+ public void setShouldRetry(boolean retry) {
-+ this.retry = retry;
-+ }
-+
-+ /**
-+ * Calls the event and tests if should retry.
-+ *
-+ * @return whether the teleport should be retried.
-+ */
-+ @Override
-+ public boolean callEvent() {
-+ super.callEvent();
-+ return shouldRetry();
-+ }
-+
-+ @Override
-+ @NotNull
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ @NotNull
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+
-+ /**
-+ * Reason for hindrance in teleports.
-+ */
-+ public enum Reason {
-+ /**
-+ * The teleported entity is a passenger of another entity.
-+ */
-+ IS_PASSENGER,
-+
-+ /**
-+ * The teleported entity has passengers.
-+ */
-+ IS_VEHICLE,
-+
-+ /**
-+ * The teleport event was cancelled.
-+ *
-+ * This is only caused by players teleporting.
-+ *
-+ */
-+ EVENT_CANCELLED,
-+ }
-+}
-diff --git a/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..f62c14f3d4999e9112c1c73642aa337d97b94b5a
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/entity/GoatRamEntityEvent.java
-@@ -0,0 +1,59 @@
-+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.NotNull;
-+
-+/**
-+ * Called when a goat rams an entity
-+ */
-+public class GoatRamEntityEvent extends EntityEvent implements Cancellable {
-+ private static final HandlerList handlers = new HandlerList();
-+ private final LivingEntity rammedEntity;
-+ private boolean cancelled;
-+
-+ public GoatRamEntityEvent(@NotNull Goat goat, @NotNull LivingEntity rammedEntity) {
-+ super(goat);
-+ this.rammedEntity = rammedEntity;
-+ }
-+
-+ /**
-+ * Returns the entity that was rammed by the goat
-+ *
-+ * @return The rammed entity
-+ */
-+ @NotNull
-+ public LivingEntity getRammedEntity() {
-+ return this.rammedEntity;
-+ }
-+
-+ @Override
-+ @NotNull
-+ public Goat getEntity() {
-+ return (Goat) super.getEntity();
-+ }
-+
-+ @Override
-+ @NotNull
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ @NotNull
-+ 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/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..8849bb0becb16db907fa648cca2e98ab9d957c75
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/entity/LlamaJoinCaravanEvent.java
-@@ -0,0 +1,61 @@
-+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.NotNull;
-+
-+/**
-+ * Called when a Llama tries to join a caravan.
-+ *
-+ * Cancelling the event will not let the Llama join. To prevent future attempts
-+ * at joining a caravan use {@link Llama#setShouldJoinCaravan(boolean)}.
-+ */
-+public class LlamaJoinCaravanEvent extends EntityEvent implements Cancellable {
-+ private static final HandlerList handlers = new HandlerList();
-+ private boolean canceled;
-+ private final Llama head;
-+
-+ public LlamaJoinCaravanEvent(@NotNull Llama llama, @NotNull Llama head) {
-+ super(llama);
-+ this.head = head;
-+ }
-+
-+ @Override
-+ @NotNull
-+ public Llama getEntity() {
-+ return (Llama) entity;
-+ }
-+
-+ /**
-+ * Get the Llama that this Llama is about to follow
-+ *
-+ * @return Llama about to be followed
-+ */
-+ @NotNull
-+ public Llama getHead() {
-+ return head;
-+ }
-+
-+ @Override
-+ public boolean isCancelled() {
-+ return canceled;
-+ }
-+
-+ @Override
-+ public void setCancelled(boolean cancel) {
-+ canceled = cancel;
-+ }
-+
-+ @Override
-+ @NotNull
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ @NotNull
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
-diff --git a/src/main/java/org/purpurmc/purpur/event/entity/LlamaLeaveCaravanEvent.java b/src/main/java/org/purpurmc/purpur/event/entity/LlamaLeaveCaravanEvent.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..c268c35b541a222d50875c29770c846a8ffcc4f8
---- /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.NotNull;
-+
-+/**
-+ * Called when a Llama leaves a caravan
-+ */
-+public class LlamaLeaveCaravanEvent extends EntityEvent {
-+ private static final HandlerList handlers = new HandlerList();
-+
-+ public LlamaLeaveCaravanEvent(@NotNull Llama llama) {
-+ super(llama);
-+ }
-+
-+ @Override
-+ @NotNull
-+ public Llama getEntity() {
-+ return (Llama) entity;
-+ }
-+
-+ @Override
-+ @NotNull
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ @NotNull
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
-diff --git a/src/main/java/org/purpurmc/purpur/event/entity/MonsterEggSpawnEvent.java b/src/main/java/org/purpurmc/purpur/event/entity/MonsterEggSpawnEvent.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..82f8a0ea22f07954d516935fc9f73f6aa0f65aa6
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/entity/MonsterEggSpawnEvent.java
-@@ -0,0 +1,67 @@
-+package org.purpurmc.purpur.event.entity;
-+
-+import org.bukkit.entity.Entity;
-+import org.bukkit.entity.HumanEntity;
-+import org.bukkit.entity.Player;
-+import org.bukkit.event.Cancellable;
-+import org.bukkit.event.Event;
-+import org.bukkit.event.HandlerList;
-+import org.bukkit.inventory.ItemStack;
-+import org.jetbrains.annotations.NotNull;
-+import org.jetbrains.annotations.Nullable;
-+
-+public class MonsterEggSpawnEvent extends Event implements Cancellable {
-+ private static final HandlerList handlers = new HandlerList();
-+ private boolean canceled;
-+
-+ private final Player player;
-+ private Entity entity;
-+ private final ItemStack item;
-+
-+ public MonsterEggSpawnEvent(@Nullable HumanEntity player, @NotNull Entity entity, @NotNull ItemStack item) {
-+ this.player = (Player) player;
-+ this.entity = entity;
-+ this.item = item;
-+ }
-+
-+ @Nullable
-+ public Player getPlayer() {
-+ return player;
-+ }
-+
-+ @NotNull
-+ public Entity getEntity() {
-+ return entity;
-+ }
-+
-+ public void setEntity(@Nullable Entity entity) {
-+ if (entity == null) {
-+ canceled = true;
-+ return;
-+ }
-+ this.entity = entity;
-+ }
-+
-+ @NotNull
-+ public ItemStack getItem() {
-+ return item;
-+ }
-+
-+ public boolean isCancelled() {
-+ return canceled;
-+ }
-+
-+ public void setCancelled(boolean cancel) {
-+ canceled = cancel;
-+ }
-+
-+ @NotNull
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ @NotNull
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
-diff --git a/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..2d4f68228861492baaea0bcc604dfef623b337ba
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/entity/PreEntityExplodeEvent.java
-@@ -0,0 +1,64 @@
-+package org.purpurmc.purpur.event.entity;
-+
-+import org.bukkit.Location;
-+import org.bukkit.event.Cancellable;
-+import org.bukkit.event.HandlerList;
-+import org.bukkit.event.entity.EntityExplodeEvent;
-+import org.jetbrains.annotations.NotNull;
-+import java.util.Collections;
-+
-+/**
-+ * Called before an entity's explosion is processed
-+ */
-+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;
-+
-+ public PreEntityExplodeEvent(@NotNull org.bukkit.entity.Entity what, @NotNull final Location location, final float yield) {
-+ super(what, location, Collections.emptyList(), yield);
-+ 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
-+ */
-+ @NotNull
-+ public Location getLocation() {
-+ return location;
-+ }
-+
-+ @Override
-+ public boolean isCancelled() {
-+ return this.cancelled;
-+ }
-+
-+ @Override
-+ public void setCancelled(boolean cancel) {
-+ this.cancelled = cancel;
-+ }
-+
-+ @Override
-+ public @NotNull HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ @NotNull
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
-diff --git a/src/main/java/org/purpurmc/purpur/event/entity/RidableMoveEvent.java b/src/main/java/org/purpurmc/purpur/event/entity/RidableMoveEvent.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..a037df01b07af9ffb98b67aca412c1d34fade03b
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/entity/RidableMoveEvent.java
-@@ -0,0 +1,103 @@
-+package org.purpurmc.purpur.event.entity;
-+
-+import com.google.common.base.Preconditions;
-+import org.bukkit.Location;
-+import org.bukkit.entity.Mob;
-+import org.bukkit.entity.Player;
-+import org.bukkit.event.Cancellable;
-+import org.bukkit.event.HandlerList;
-+import org.bukkit.event.entity.EntityEvent;
-+import org.jetbrains.annotations.NotNull;
-+
-+/**
-+ * Triggered when a ridable mob moves with a rider
-+ */
-+public class RidableMoveEvent extends EntityEvent implements Cancellable {
-+ private static final HandlerList handlers = new HandlerList();
-+ private boolean canceled;
-+ private final Player rider;
-+ private Location from;
-+ private Location to;
-+
-+ public RidableMoveEvent(@NotNull Mob entity, @NotNull Player rider, @NotNull Location from, @NotNull Location to) {
-+ super(entity);
-+ this.rider = rider;
-+ this.from = from;
-+ this.to = to;
-+ }
-+
-+ @Override
-+ @NotNull
-+ public Mob getEntity() {
-+ return (Mob) entity;
-+ }
-+
-+ @NotNull
-+ public Player getRider() {
-+ return rider;
-+ }
-+
-+ public boolean isCancelled() {
-+ return canceled;
-+ }
-+
-+ public void setCancelled(boolean cancel) {
-+ canceled = cancel;
-+ }
-+
-+ /**
-+ * Gets the location this entity moved from
-+ *
-+ * @return Location the entity moved from
-+ */
-+ @NotNull
-+ public Location getFrom() {
-+ return from;
-+ }
-+
-+ /**
-+ * Sets the location to mark as where the entity moved from
-+ *
-+ * @param from New location to mark as the entity's previous location
-+ */
-+ public void setFrom(@NotNull Location from) {
-+ validateLocation(from);
-+ this.from = from;
-+ }
-+
-+ /**
-+ * Gets the location this entity moved to
-+ *
-+ * @return Location the entity moved to
-+ */
-+ @NotNull
-+ public Location getTo() {
-+ return to;
-+ }
-+
-+ /**
-+ * Sets the location that this entity will move to
-+ *
-+ * @param to New Location this entity will move to
-+ */
-+ public void setTo(@NotNull Location to) {
-+ validateLocation(to);
-+ this.to = to;
-+ }
-+
-+ private void validateLocation(@NotNull Location loc) {
-+ Preconditions.checkArgument(loc != null, "Cannot use null location!");
-+ Preconditions.checkArgument(loc.getWorld() != null, "Cannot use null location with null world!");
-+ }
-+
-+ @Override
-+ @NotNull
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ @NotNull
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
-diff --git a/src/main/java/org/purpurmc/purpur/event/entity/RidableSpacebarEvent.java b/src/main/java/org/purpurmc/purpur/event/entity/RidableSpacebarEvent.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..3d3a7d898e3278ce998d713dafbb4b354dad7fc7
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/entity/RidableSpacebarEvent.java
-@@ -0,0 +1,37 @@
-+package org.purpurmc.purpur.event.entity;
-+
-+import org.bukkit.entity.Entity;
-+import org.bukkit.event.Cancellable;
-+import org.bukkit.event.HandlerList;
-+import org.bukkit.event.entity.EntityEvent;
-+import org.jetbrains.annotations.NotNull;
-+
-+public class RidableSpacebarEvent extends EntityEvent implements Cancellable {
-+ private static final HandlerList handlers = new HandlerList();
-+ private boolean cancelled;
-+
-+ public RidableSpacebarEvent(@NotNull Entity entity) {
-+ super(entity);
-+ }
-+
-+ @Override
-+ public boolean isCancelled() {
-+ return cancelled;
-+ }
-+
-+ @Override
-+ public void setCancelled(boolean cancel) {
-+ cancelled = cancel;
-+ }
-+
-+ @Override
-+ @NotNull
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ @NotNull
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
-diff --git a/src/main/java/org/purpurmc/purpur/event/inventory/AnvilTakeResultEvent.java b/src/main/java/org/purpurmc/purpur/event/inventory/AnvilTakeResultEvent.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..b363c91a29f826910db22f2643decf996a067ab5
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/inventory/AnvilTakeResultEvent.java
-@@ -0,0 +1,52 @@
-+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.NotNull;
-+
-+/**
-+ * Called when a player takes the result item out of an anvil
-+ */
-+public class AnvilTakeResultEvent extends InventoryEvent {
-+ private static final HandlerList handlers = new HandlerList();
-+ private final Player player;
-+ private final ItemStack result;
-+
-+ public AnvilTakeResultEvent(@NotNull HumanEntity player, @NotNull InventoryView view, @NotNull ItemStack result) {
-+ super(view);
-+ this.player = (Player) player;
-+ this.result = result;
-+ }
-+
-+ @NotNull
-+ public Player getPlayer() {
-+ return player;
-+ }
-+
-+ @NotNull
-+ public ItemStack getResult() {
-+ return result;
-+ }
-+
-+ @NotNull
-+ @Override
-+ public AnvilInventory getInventory() {
-+ return (AnvilInventory) super.getInventory();
-+ }
-+
-+ @NotNull
-+ @Override
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ @NotNull
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
-diff --git a/src/main/java/org/purpurmc/purpur/event/inventory/AnvilUpdateResultEvent.java b/src/main/java/org/purpurmc/purpur/event/inventory/AnvilUpdateResultEvent.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..fd6a5a3589d436c2aaf988fd305899695799d3bb
---- /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.NotNull;
-+
-+/**
-+ * Called when anvil slots change, triggering the result slot to be updated
-+ */
-+public class AnvilUpdateResultEvent extends InventoryEvent {
-+ private static final HandlerList handlers = new HandlerList();
-+
-+ public AnvilUpdateResultEvent(@NotNull InventoryView view) {
-+ super(view);
-+ }
-+
-+ @NotNull
-+ @Override
-+ public AnvilInventory getInventory() {
-+ return (AnvilInventory) super.getInventory();
-+ }
-+
-+ @NotNull
-+ @Override
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ @NotNull
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
-diff --git a/src/main/java/org/purpurmc/purpur/event/inventory/GrindstoneTakeResultEvent.java b/src/main/java/org/purpurmc/purpur/event/inventory/GrindstoneTakeResultEvent.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..eebb5d124456b8209d1b8e8cc4cb772dd3714f04
---- /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.NotNull;
-+
-+/**
-+ * Called when a player takes the result item out of a Grindstone
-+ */
-+public class GrindstoneTakeResultEvent extends InventoryEvent {
-+ private static final HandlerList handlers = new HandlerList();
-+ private final Player player;
-+ private final ItemStack result;
-+ private int experienceAmount;
-+
-+ public GrindstoneTakeResultEvent(@NotNull HumanEntity player, @NotNull InventoryView view, @NotNull ItemStack result, int experienceAmount) {
-+ super(view);
-+ this.player = (Player) player;
-+ this.result = result;
-+ this.experienceAmount = experienceAmount;
-+ }
-+
-+ @NotNull
-+ public Player getPlayer() {
-+ return player;
-+ }
-+
-+ @NotNull
-+ public ItemStack getResult() {
-+ return result;
-+ }
-+
-+ @NotNull
-+ @Override
-+ public GrindstoneInventory getInventory() {
-+ return (GrindstoneInventory) super.getInventory();
-+ }
-+
-+ /**
-+ * Get the amount of experience this transaction will give
-+ *
-+ * @return Amount of experience to give
-+ */
-+ public int getExperienceAmount() {
-+ return this.experienceAmount;
-+ }
-+
-+ /**
-+ * Set the amount of experience this transaction will give
-+ *
-+ * @param experienceAmount Amount of experience to give
-+ */
-+ public void setExperienceAmount(int experienceAmount) {
-+ this.experienceAmount = experienceAmount;
-+ }
-+
-+ @NotNull
-+ @Override
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ @NotNull
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
-diff --git a/src/main/java/org/purpurmc/purpur/event/packet/NetworkItemSerializeEvent.java b/src/main/java/org/purpurmc/purpur/event/packet/NetworkItemSerializeEvent.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..c0da73d2ea83a6055e34894ba1c7506fc8667712
---- /dev/null
-+++ b/src/main/java/org/purpurmc/purpur/event/packet/NetworkItemSerializeEvent.java
-@@ -0,0 +1,48 @@
-+package org.purpurmc.purpur.event.packet;
-+
-+import org.bukkit.event.Event;
-+import org.bukkit.event.HandlerList;
-+import org.bukkit.inventory.ItemStack;
-+import org.jetbrains.annotations.NotNull;
-+import org.jetbrains.annotations.Nullable;
-+
-+/**
-+ * Called when an item is about to be written to a packet.
-+ */
-+public class NetworkItemSerializeEvent extends Event {
-+ private ItemStack itemStack;
-+
-+ public NetworkItemSerializeEvent(@NotNull ItemStack itemStack) {
-+ super(!org.bukkit.Bukkit.isPrimaryThread());
-+ this.itemStack = itemStack;
-+ }
-+
-+ /**
-+ * @return The item that is about to be serialized. Not mutable
-+ */
-+ @NotNull
-+ public ItemStack getItemStack() {
-+ return itemStack;
-+ }
-+
-+ /**
-+ * Sets the item that will be serialized.
-+ *
-+ * @param itemStack The item
-+ */
-+ public void setItemStack(@Nullable ItemStack itemStack) {
-+ this.itemStack = itemStack;
-+ }
-+
-+ private static final HandlerList handlers = new HandlerList();
-+
-+ @NotNull
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ @NotNull
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
-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..c88394336bc9ab0f66a2af24d393f4a176a234d5
---- /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.NotNull;
-+
-+/**
-+ * Called when a player tries to bypass book limitations
-+ */
-+public class PlayerBookTooLargeEvent extends PlayerEvent {
-+ private static final HandlerList handlers = new HandlerList();
-+ private final ItemStack book;
-+ private boolean kickPlayer = true;
-+
-+ /**
-+ * @param player The player
-+ * @param book The book
-+ */
-+ public PlayerBookTooLargeEvent(@NotNull Player player, @NotNull ItemStack book) {
-+ super(player, !Bukkit.isPrimaryThread());
-+ this.book = book;
-+ }
-+
-+ /**
-+ * Get the book containing the wanted edits
-+ *
-+ * @return The book
-+ */
-+ @NotNull
-+ public ItemStack getBook() {
-+ return book;
-+ }
-+
-+ /**
-+ * Whether server should kick the player or not
-+ *
-+ * @return True to kick player
-+ */
-+ public boolean shouldKickPlayer() {
-+ return kickPlayer;
-+ }
-+
-+ /**
-+ * Whether server should kick the player or not
-+ *
-+ * @param kickPlayer True to kick player
-+ */
-+ public void setShouldKickPlayer(boolean kickPlayer) {
-+ this.kickPlayer = kickPlayer;
-+ }
-+
-+ @Override
-+ @NotNull
-+ public HandlerList getHandlers() {
-+ return handlers;
-+ }
-+
-+ @NotNull
-+ public static HandlerList getHandlerList() {
-+ return handlers;
-+ }
-+}
-diff --git a/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..38483d908ed830e97883733bee2370f87060f4c7
---- /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.jetbrains.annotations.NotNull;
-+
-+/**
-+ * Represents a language that can translate translation keys
-+ */
-+public abstract class Language {
-+ private static Language language;
-+
-+ /**
-+ * Returns the default language of the server
-+ */
-+ @NotNull
-+ public static Language getLanguage() {
-+ return language;
-+ }
-+
-+ public static void setLanguage(@NotNull 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(@NotNull 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(@NotNull 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
-+ */
-+ @NotNull
-+ abstract public String getOrDefault(@NotNull 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
-+ */
-+ @NotNull
-+ public String getOrDefault(@NotNull Translatable key) {
-+ return getOrDefault(key.translationKey());
-+ }
-+}
-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/src/test/java/org/bukkit/AnnotationTest.java b/src/test/java/org/bukkit/AnnotationTest.java
-index 06e96e5c98f1a7a68c8b4b5e527314c1aa774e38..49bba9a7a02b9cf3a552583315eff2b7dbe060c0 100644
---- a/src/test/java/org/bukkit/AnnotationTest.java
-+++ b/src/test/java/org/bukkit/AnnotationTest.java
-@@ -47,6 +47,10 @@ public class AnnotationTest {
- "org/bukkit/plugin/java/PluginClassLoader",
- // Generic functional interface
- "org/bukkit/util/Consumer",
-+ // Purpur start
-+ "gg/pufferfish/pufferfish/sentry/SentryContext",
-+ "gg/pufferfish/pufferfish/sentry/SentryContext$State",
-+ // Purpur end
- // Paper start
- "io/papermc/paper/util/TransformingRandomAccessList",
- "io/papermc/paper/util/TransformingRandomAccessList$TransformedListIterator",
diff --git a/patches/api/0004-Bump-Dependencies.patch b/patches/api/0003-Bump-Dependencies.patch
similarity index 66%
rename from patches/api/0004-Bump-Dependencies.patch
rename to patches/api/0003-Bump-Dependencies.patch
index 3149280f..014f09c7 100644
--- a/patches/api/0004-Bump-Dependencies.patch
+++ b/patches/api/0003-Bump-Dependencies.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] Bump Dependencies
diff --git a/build.gradle.kts b/build.gradle.kts
-index 3e148458cc78a3225e8d6572b43e1d358791eec2..8813d67ff64f4f213de223c17cd16ef21aa9c916 100644
+index b1c11eaee0d53ed9ece3ddf768bded98c92dde98..6885418db2deec33622fac66428b432b690d5621 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
-@@ -24,15 +24,17 @@ configurations.api {
+@@ -26,15 +26,17 @@ val annotationsVersion = "24.0.1"
dependencies {
// api dependencies are listed transitively to API consumers
api("com.google.guava:guava:31.1-jre")
@@ -18,7 +18,7 @@ index 3e148458cc78a3225e8d6572b43e1d358791eec2..8813d67ff64f4f213de223c17cd16ef2
+ api("com.google.code.gson:gson:2.10.1")
+ api("net.md-5:bungeecord-chat:1.19-R0.1-SNAPSHOT") // Paper
+ // Leaf end
- api("org.yaml:snakeyaml:1.33")
+ api("org.yaml:snakeyaml:2.0")
api("org.joml:joml:1.10.5")
// Paper start
api("com.googlecode.json-simple:json-simple:1.1.1") {
@@ -29,7 +29,7 @@ index 3e148458cc78a3225e8d6572b43e1d358791eec2..8813d67ff64f4f213de223c17cd16ef2
apiAndDocs(platform("net.kyori:adventure-bom:$adventureVersion"))
apiAndDocs("net.kyori:adventure-api")
apiAndDocs("net.kyori:adventure-text-minimessage")
-@@ -40,34 +42,36 @@ dependencies {
+@@ -42,34 +44,37 @@ dependencies {
apiAndDocs("net.kyori:adventure-text-serializer-legacy")
apiAndDocs("net.kyori:adventure-text-serializer-plain")
apiAndDocs("net.kyori:adventure-text-logger-slf4j")
@@ -46,17 +46,13 @@ index 3e148458cc78a3225e8d6572b43e1d358791eec2..8813d67ff64f4f213de223c17cd16ef2
+ implementation("org.ow2.asm:asm-commons:9.5")
// Paper end
-- api("org.apache.maven:maven-resolver-provider:3.8.5") // Paper, expose
-- compileOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.7.3")
-- compileOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.7.3")
+ api("org.apache.maven:maven-resolver-provider:3.9.2")
+ compileOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.10")
+ compileOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.9.10")
- compileOnly("com.google.code.findbugs:jsr305:1.3.9") // Paper
-+ compileOnly("org.apache.maven:maven-resolver-provider:3.9.1")
-+ compileOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.7")
-+ compileOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.9.7")
+ compileOnly("com.google.code.findbugs:jsr305:3.0.2") // Paper
-- val annotations = "org.jetbrains:annotations:23.0.0" // Paper - we don't want Java 5 annotations...
-+ val annotations = "org.jetbrains:annotations:24.0.1" // Paper - we don't want Java 5 annotations...
+ val annotations = "org.jetbrains:annotations:$annotationsVersion" // Paper - we don't want Java 5 annotations...
compileOnly(annotations)
testCompileOnly(annotations)
@@ -72,27 +68,24 @@ index 3e148458cc78a3225e8d6572b43e1d358791eec2..8813d67ff64f4f213de223c17cd16ef2
testImplementation("org.apache.commons:commons-lang3:3.12.0")
testImplementation("junit:junit:4.13.2")
- testImplementation("org.hamcrest:hamcrest-library:1.3")
-- testImplementation("org.ow2.asm:asm-tree:9.4")
++
+ testImplementation("org.hamcrest:hamcrest-library:2.2")
-+ testImplementation("org.ow2.asm:asm-tree:9.5")
+ testImplementation("org.ow2.asm:asm-tree:9.5")
+ // Leaf end
}
configure {
-@@ -115,9 +119,11 @@ tasks.withType {
+@@ -115,7 +120,9 @@ tasks.withType {
options.use()
options.isDocFilesSubDirs = true
options.links(
+ // Leaf - Bump Dependencies
"https://guava.dev/releases/31.1-jre/api/docs/",
- "https://javadoc.io/doc/org.yaml/snakeyaml/1.33/",
-- "https://javadoc.io/doc/org.jetbrains/annotations/23.0.0/", // Paper - we don't want Java 5 annotations
-+ "https://javadoc.io/doc/org.jetbrains/annotations/24.0.1/", // Paper - we don't want Java 5 annotations
-+ // Leaf end
++ // Leaf - end
+ "https://javadoc.io/doc/org.yaml/snakeyaml/2.0/",
+ "https://javadoc.io/doc/org.jetbrains/annotations/$annotationsVersion/", // Paper - we don't want Java 5 annotations
// Paper start
- //"https://javadoc.io/doc/net.md-5/bungeecord-chat/1.16-R0.4/", // don't link to bungee chat
- "https://jd.advntr.dev/api/$adventureVersion/",
-@@ -158,6 +164,9 @@ val scanJar = tasks.register("scanJarForBadCalls", io.papermc.paperweight.tasks.
+@@ -158,6 +165,9 @@ val scanJar = tasks.register("scanJarForBadCalls", io.papermc.paperweight.tasks.
jarToScan.set(tasks.jar.flatMap { it.archiveFile })
classpath.from(configurations.compileClasspath)
}
diff --git a/patches/api/0005-KTP-Optimize-Spigot-event-bus.patch b/patches/api/0004-KTP-Optimize-Spigot-event-bus.patch
similarity index 100%
rename from patches/api/0005-KTP-Optimize-Spigot-event-bus.patch
rename to patches/api/0004-KTP-Optimize-Spigot-event-bus.patch
diff --git a/patches/api/0006-KeYi-Player-Skull-API.patch b/patches/api/0005-KeYi-Player-Skull-API.patch
similarity index 82%
rename from patches/api/0006-KeYi-Player-Skull-API.patch
rename to patches/api/0005-KeYi-Player-Skull-API.patch
index 6002c404..7e74e2cd 100644
--- a/patches/api/0006-KeYi-Player-Skull-API.patch
+++ b/patches/api/0005-KeYi-Player-Skull-API.patch
@@ -7,7 +7,7 @@ Original license: MIT
Original project: https://github.com/KeYiMC/KeYi
diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
-index 18e38e028cb44b12e4e439175f67800e83512d2f..db8f06deb894a2d986c2abf28aab7be17b44466c 100644
+index e378c91155b4c70d89e5ee2af59828051454e192..5662c72e0b76b3e0aa2d7a78305637fd36245d5e 100644
--- a/src/main/java/org/bukkit/entity/Player.java
+++ b/src/main/java/org/bukkit/entity/Player.java
@@ -4,7 +4,10 @@ import java.net.InetSocketAddress;
@@ -22,10 +22,10 @@ index 18e38e028cb44b12e4e439175f67800e83512d2f..db8f06deb894a2d986c2abf28aab7be1
import org.bukkit.DyeColor;
import org.bukkit.Effect;
import org.bukkit.GameMode;
-@@ -3215,4 +3218,22 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
- */
- void sendDeathScreen(@NotNull net.kyori.adventure.text.Component message, @Nullable Entity killer);
- // Purpur end
+@@ -3086,4 +3089,22 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+ @Override
+ Spigot spigot();
+ // Spigot end
+
+ // KeYi start
+ /**
diff --git a/patches/api/0007-Slice-Smooth-Teleports.patch b/patches/api/0006-Slice-Smooth-Teleports.patch
similarity index 89%
rename from patches/api/0007-Slice-Smooth-Teleports.patch
rename to patches/api/0006-Slice-Smooth-Teleports.patch
index 3851cbb5..5813f2c6 100644
--- a/patches/api/0007-Slice-Smooth-Teleports.patch
+++ b/patches/api/0006-Slice-Smooth-Teleports.patch
@@ -7,10 +7,10 @@ Original license: MIT
Original project: https://github.com/Cryptite/Slice
diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
-index db8f06deb894a2d986c2abf28aab7be17b44466c..2b909bf048af82d4ff97b01b5473911bf3a99c29 100644
+index 5662c72e0b76b3e0aa2d7a78305637fd36245d5e..c7232a047106efa998512c45ea200d6320432289 100644
--- a/src/main/java/org/bukkit/entity/Player.java
+++ b/src/main/java/org/bukkit/entity/Player.java
-@@ -2965,6 +2965,19 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+@@ -2971,6 +2971,19 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
String getClientBrandName();
// Paper end
diff --git a/patches/server/0001-Rebrand.patch b/patches/server/0001-Rebrand.patch
index 5c1ef13b..564e81d8 100644
--- a/patches/server/0001-Rebrand.patch
+++ b/patches/server/0001-Rebrand.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Rebrand
diff --git a/build.gradle.kts b/build.gradle.kts
-index 539f11b179e0b6473ca9b38ba971bd0b659bbdb4..dcccde28176c8800257d338117b87c83cc2286ca 100644
+index 3097e021e89bf04e9cf18dae1f9be8a089d29743..d209ac7c8fe639a23904e4ebd5bade0637fb8eab 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -9,7 +9,7 @@ plugins {
@@ -15,9 +15,9 @@ index 539f11b179e0b6473ca9b38ba971bd0b659bbdb4..dcccde28176c8800257d338117b87c83
- implementation(project(":gale-api"))
+ implementation(project(":leaf-api")) // Leaf
// Depend on Paper MojangAPI
- implementation("io.papermc.paper:paper-mojangapi:1.19.3-R0.1-SNAPSHOT") {
+ implementation("io.papermc.paper:paper-mojangapi:1.20-R0.1-SNAPSHOT") {
exclude("io.papermc.paper", "paper-api")
-@@ -80,7 +80,7 @@ tasks.jar {
+@@ -79,7 +79,7 @@ tasks.jar {
attributes(
"Main-Class" to "org.bukkit.craftbukkit.Main",
"Implementation-Title" to "CraftBukkit",
@@ -40,23 +40,23 @@ index e45e6b44b2a8f2cdae6e0048a812b92126aa17ca..b5f3f213da8a40d5184098af017c8e26
.completer(new ConsoleCommandCompleter(this.server))
.option(LineReader.Option.COMPLETE_IN_WORD, true);
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
-index 5db4312ed1973a2395af66975a43abe5beffa1cd..1c671eb71254172fe023eb81cda21976d861a81f 100644
+index ecb42a8f9a8eb18d7fd3f828ece4ac8ebf4de0e0..66f284345fe011bd317019bc035e009e90c58fdc 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
-@@ -1030,7 +1030,7 @@ public abstract class MinecraftServer extends MinecraftServerBlockableEventLoop
+@@ -907,7 +907,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop S spin(Function serverFactory) { // Gale - base thread pool
+ public static S spin(Function serverFactory) {
AtomicReference atomicreference = new AtomicReference();
- OriginalServerThread thread = new OriginalServerThread(() -> { // Paper - rewrite chunk system // Gale - base thread pool
+ Thread thread = new io.papermc.paper.util.TickThread(() -> { // Paper - rewrite chunk system
diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
-index b104a38d2c879b649a3862876c389564c69e83b4..1704f5f29c4e36580101e760ee003d608ca3550e 100644
+index 8ba7f4aad1ff28d5f38b895e8eb47e141390e5ea..69c594bbd52335d6779b06f9273a1ef8e8138a67 100644
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
@@ -47,6 +47,7 @@ import net.minecraft.world.level.GameRules;
@@ -175,8 +174,8 @@ index b104a38d2c879b649a3862876c389564c69e83b4..1704f5f29c4e36580101e760ee003d60
+import org.dreeam.leaf.LeafConfig;
import org.galemc.gale.command.GaleCommands;
import org.galemc.gale.configuration.GaleGlobalConfiguration;
- import org.galemc.gale.executor.thread.OriginalServerThread;
-@@ -366,6 +367,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
+ import org.slf4j.Logger;
+@@ -358,6 +359,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
DedicatedServer.LOGGER.info("JMX monitoring enabled");
}
@@ -185,18 +184,18 @@ index b104a38d2c879b649a3862876c389564c69e83b4..1704f5f29c4e36580101e760ee003d60
}
}
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-index d4f99270c62cef94cc5ad5fc00f155c480722516..9eeb139d36429353a7ae94e1587c205a20d2e800 100644
+index 3c9ff57cf8cf7e7bfca234e460ff869165bd40d3..9e743d20e9ff979ebad152f209a6ca97a533ea47 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-@@ -45,6 +45,7 @@ import net.minecraft.world.level.storage.DimensionDataStorage;
+@@ -48,6 +48,7 @@ import net.minecraft.world.level.storage.DimensionDataStorage;
import net.minecraft.world.level.storage.LevelData;
import net.minecraft.world.level.storage.LevelStorageSource;
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; // Paper
+import org.dreeam.leaf.LeafConfig;
- import org.galemc.gale.executor.ClosestChunkBlockableEventLoop;
- import org.galemc.gale.executor.lock.YieldingLock;
- import org.galemc.gale.executor.queue.BaseTaskQueues;
-@@ -76,6 +77,9 @@ public class ServerChunkCache extends ChunkSource {
+
+ public class ServerChunkCache extends ChunkSource {
+
+@@ -75,6 +76,9 @@ public class ServerChunkCache extends ChunkSource {
final it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap loadedChunkMap = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>(8192, 0.5f);
private final LevelChunk[] lastLoadedChunks = new LevelChunk[4 * 4];
@@ -206,7 +205,7 @@ index d4f99270c62cef94cc5ad5fc00f155c480722516..9eeb139d36429353a7ae94e1587c205a
private static int getChunkCacheKey(int x, int z) {
return x & 3 | ((z & 3) << 2);
-@@ -781,18 +785,25 @@ public class ServerChunkCache extends ChunkSource {
+@@ -553,18 +557,25 @@ public class ServerChunkCache extends ChunkSource {
int l = this.distanceManager.getNaturalSpawnChunkCount();
// Paper start - per player mob spawning
if ((this.spawnFriendlies || this.spawnEnemies) && this.chunkMap.playerMobDistanceMap != null) { // don't count mobs when animals and monsters are disabled
@@ -238,7 +237,7 @@ index d4f99270c62cef94cc5ad5fc00f155c480722516..9eeb139d36429353a7ae94e1587c205a
// Gale start - MultiPaper - skip unnecessary mob spawning computations
} else {
spawnercreature_d = null;
-@@ -831,8 +842,8 @@ public class ServerChunkCache extends ChunkSource {
+@@ -604,8 +615,8 @@ public class ServerChunkCache extends ChunkSource {
if ((true || this.level.isNaturalSpawningAllowed(chunkcoordintpair)) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning // Paper - the chunk is known ticking
chunk1.incrementInhabitedTime(j);
@@ -249,7 +248,7 @@ index d4f99270c62cef94cc5ad5fc00f155c480722516..9eeb139d36429353a7ae94e1587c205a
}
if (true || this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { // Paper - the chunk is known ticking
-@@ -890,6 +901,29 @@ public class ServerChunkCache extends ChunkSource {
+@@ -663,6 +674,29 @@ public class ServerChunkCache extends ChunkSource {
}
// Paper end - controlled flush for entity tracker packets
}
diff --git a/patches/server/0005-Pufferfish-Dynamic-Activation-of-Brain.patch b/patches/server/0005-Pufferfish-Dynamic-Activation-of-Brain.patch
index 3f7be609..5bbad873 100644
--- a/patches/server/0005-Pufferfish-Dynamic-Activation-of-Brain.patch
+++ b/patches/server/0005-Pufferfish-Dynamic-Activation-of-Brain.patch
@@ -30,11 +30,11 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see .
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index d3b8df0766d34e557806a7578f856184c0b3d437..ccedeb88e4cbf0e1088412e72ee317c2182cb277 100644
+index 1462f9d4f2cdf4071fb002d602783866a5a3d285..5d6c0f5d2d993ae3a044a1a02716a2662e9080d0 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -934,6 +934,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
- // Gale end - split tick steps
+@@ -836,6 +836,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
+ org.spigotmc.ActivationRange.activateEntities(this); // Spigot
timings.entityTick.startTiming(); // Spigot
this.entityTickList.forEach((entity) -> {
+ entity.activatedPriorityReset = false; // Pufferfish - DAB
@@ -42,7 +42,7 @@ index d3b8df0766d34e557806a7578f856184c0b3d437..ccedeb88e4cbf0e1088412e72ee317c2
if (false && this.shouldDiscardEntity(entity)) { // CraftBukkit - We prevent spawning in general, so this butchering is not needed
entity.discard();
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
-index 804c1f0377b66a9fa0eebd9ab80945e4ff6929d2..2e3ab61998d4174f231118e7a4bdc9e25563715f 100644
+index 9d5a1d6141414d5a886891867e2062ee11de0fd2..223086fa230346e7212051710758560f874cb3b0 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -125,6 +125,7 @@ import net.minecraft.world.phys.shapes.Shapes;
@@ -53,7 +53,7 @@ index 804c1f0377b66a9fa0eebd9ab80945e4ff6929d2..2e3ab61998d4174f231118e7a4bdc9e2
import org.slf4j.Logger;
import org.bukkit.Bukkit;
import org.bukkit.Location;
-@@ -419,6 +420,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
+@@ -423,6 +424,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
}
// Paper end
@@ -66,7 +66,7 @@ index 804c1f0377b66a9fa0eebd9ab80945e4ff6929d2..2e3ab61998d4174f231118e7a4bdc9e2
return this.yRot;
}
diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java
-index ceacc0d383e2ee674783d3c0a7df0a951595faca..559bce4bbcc03117f52dc2810bad5becddf14a8e 100644
+index 9afc81ccb237c3655d64cdbe8a0db9a4d7791043..1679f0a3d095a7b758b468c77b6d3a4c078b7962 100644
--- a/src/main/java/net/minecraft/world/entity/EntityType.java
+++ b/src/main/java/net/minecraft/world/entity/EntityType.java
@@ -300,6 +300,7 @@ public class EntityType implements FeatureElement, EntityTypeT
@@ -78,10 +78,10 @@ index ceacc0d383e2ee674783d3c0a7df0a951595faca..559bce4bbcc03117f52dc2810bad5bec
private String descriptionId;
@Nullable
diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java
-index f2eca869a9301c8e6536396f55fd5dc871a3dfbc..7c6332171eae9e06588dd6ff9f023cb4e3e6079b 100644
+index 035a0a8620fc46dbf026c65ccf2542d9f49e22b0..979b76d58d05c9d83dfae45d3052eea9431dfc65 100644
--- a/src/main/java/net/minecraft/world/entity/Mob.java
+++ b/src/main/java/net/minecraft/world/entity/Mob.java
-@@ -221,10 +221,10 @@ public abstract class Mob extends LivingEntity implements Targeting {
+@@ -224,10 +224,10 @@ public abstract class Mob extends LivingEntity implements Targeting {
@Override
public void inactiveTick() {
super.inactiveTick();
@@ -94,8 +94,8 @@ index f2eca869a9301c8e6536396f55fd5dc871a3dfbc..7c6332171eae9e06588dd6ff9f023cb4
this.targetSelector.tick();
}
}
-@@ -900,10 +900,14 @@ public abstract class Mob extends LivingEntity implements Targeting {
- int i = this.level.getServer().getTickCount() + this.getId();
+@@ -903,10 +903,14 @@ public abstract class Mob extends LivingEntity implements Targeting {
+ int i = this.level().getServer().getTickCount() + this.getId();
if (i % 2 != 0 && this.tickCount > 1) {
+ if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking
@@ -154,10 +154,10 @@ index 86fc528551c2c90c78783d4d46a4a2c52e4efe41..67fa9b4fb37d20a808378f00002d00b0
public boolean hasTasks() {
for (WrappedGoal task : this.availableGoals) {
diff --git a/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java b/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java
-index 068c6904599f57b70b9cf166a5fe949362a7b23f..5b5d3adacda11bb0d38a4a8aebd40a570dfe692f 100644
+index 0014951b6e33ce72b4e0184946cf8bd6d6d2e5b0..047780d1cdbe3f3ffb5f03d03733fb486f28cf98 100644
--- a/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java
+++ b/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java
-@@ -223,8 +223,10 @@ public class Allay extends PathfinderMob implements InventoryCarrier {
+@@ -222,8 +222,10 @@ public class Allay extends PathfinderMob implements InventoryCarrier, VibrationS
return 0.4F;
}
@@ -165,11 +165,11 @@ index 068c6904599f57b70b9cf166a5fe949362a7b23f..5b5d3adacda11bb0d38a4a8aebd40a57
@Override
protected void customServerAiStep() {
+ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
- this.getBrain().tick((ServerLevel) this.level, this);
+ this.getBrain().tick((ServerLevel) this.level(), this);
AllayAi.updateActivity(this);
super.customServerAiStep();
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 c513138be4b343ee1868a9ef541130a637fa0744..77d7c15d328b8dcf7b458a4e4083018bb6aeae46 100644
+index 634e884978094c48eaa8b1943ad0fb5cfc943f2c..e54e873169a822844b87adc6c4703974bf89e379 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
@@ -285,8 +285,10 @@ public class Axolotl extends Animal implements LerpingModel, VariantHolder {
+@@ -162,8 +162,10 @@ public class Frog extends Animal implements VariantHolder {
return true;
}
@@ -195,11 +195,11 @@ index c7b08018dbfb7d210ea4102518ea7e592ad01452..de05b61f460cc2352f73c8eb3b40a18a
@Override
protected void customServerAiStep() {
+ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
- this.getBrain().tick((ServerLevel)this.level, this);
+ this.getBrain().tick((ServerLevel)this.level(), this);
FrogAi.updateActivity(this);
super.customServerAiStep();
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 09240841cd9216c06da8dc4059f8a60ef9022d39..561ccb25a5c50a51879f0a2b8e4cb90c29ac6d0b 100644
+index 0f3a11203dd0353d74626a273e9003131356f5e1..c83dabddf93249a6477c10725622119c939db4d5 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
@@ -77,8 +77,10 @@ public class Tadpole extends AbstractFish {
@@ -210,11 +210,11 @@ index 09240841cd9216c06da8dc4059f8a60ef9022d39..561ccb25a5c50a51879f0a2b8e4cb90c
@Override
protected void customServerAiStep() {
+ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
- this.getBrain().tick((ServerLevel) this.level, this);
+ this.getBrain().tick((ServerLevel) this.level(), this);
TadpoleAi.updateActivity(this);
super.customServerAiStep();
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 ef584de97ba678176ab9bf61365d97ca61ff07cf..b8134f660dc0678db6106e7d69a8f1451e3bff2c 100644
+index 61144e8c2ad1543816be16002b43622d1578ec73..c88c2312509906dbf45118b4a82fed2fc3e0d14d 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
@@ -188,8 +188,10 @@ public class Goat extends Animal {
@@ -225,11 +225,11 @@ index ef584de97ba678176ab9bf61365d97ca61ff07cf..b8134f660dc0678db6106e7d69a8f145
@Override
protected void customServerAiStep() {
+ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
- this.getBrain().tick((ServerLevel) this.level, this);
+ this.getBrain().tick((ServerLevel) this.level(), this);
GoatAi.updateActivity(this);
super.customServerAiStep();
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 739f4f86af7951ea42a3b248b93989de8b8f4f54..2fac1ded7ad16a186dd2c1ebef3ad70715ddffa3 100644
+index a4b9c36e4fa3f499493436219a1dfd18cda2162f..7387979e5b17994a48a86f37f81b170695b6ad8e 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
@@ -126,8 +126,10 @@ public class Hoglin extends Animal implements Enemy, HoglinBase {
@@ -240,14 +240,14 @@ index 739f4f86af7951ea42a3b248b93989de8b8f4f54..2fac1ded7ad16a186dd2c1ebef3ad707
@Override
protected void customServerAiStep() {
+ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
- this.getBrain().tick((ServerLevel)this.level, this);
+ this.getBrain().tick((ServerLevel)this.level(), this);
HoglinAi.updateActivity(this);
if (this.isConverting()) {
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 a2246ab400545284cb65c292012eaf8bb3376ad7..fb78f9bff486bf8aba17f02f2378a4852b1f7cca 100644
+index 93dc4f2ac5a4302337de8ae3440a9fded2437c72..f041a2b6b330692316e7c5385651d9370377d5e0 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
-@@ -308,8 +308,10 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento
+@@ -305,8 +305,10 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento
return !this.cannotHunt;
}
@@ -255,31 +255,31 @@ index a2246ab400545284cb65c292012eaf8bb3376ad7..fb78f9bff486bf8aba17f02f2378a485
@Override
protected void customServerAiStep() {
+ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
- this.getBrain().tick((ServerLevel) this.level, this);
+ this.getBrain().tick((ServerLevel) this.level(), this);
PiglinAi.updateActivity(this);
super.customServerAiStep();
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 4544ce7e0f33b13a56cd4d4a3c905f71d370bbe6..d33a240b9dbd3cebe179096407c3da69f2df884a 100644
+index 9ca38f97f5d0d533187cdcd549b1accebc93bc95..55d5aad6ee98bc61dac415b106d0b6d1048dae7e 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
-@@ -270,10 +270,12 @@ public class Warden extends Monster implements VibrationListener.VibrationListen
+@@ -271,10 +271,12 @@ public class Warden extends Monster implements VibrationSystem {
}
+ private int behaviorTick = 0; // Pufferfish
@Override
protected void customServerAiStep() {
- ServerLevel worldserver = (ServerLevel) this.level;
+ ServerLevel worldserver = (ServerLevel) this.level();
+ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
this.getBrain().tick(worldserver, this);
super.customServerAiStep();
if ((this.tickCount + this.getId()) % 120 == 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 583f8bbb242305bf1bf825e254209108fd323ad4..474de6f93631c07c75aa78ad95b71a4f89b0dcc5 100644
+index ae3628efe7628427c53bb7d0f7fc6e457a511b94..ffe93e11c1ab6986ea73f486fbc475aca51eca4a 100644
--- a/src/main/java/net/minecraft/world/entity/npc/Villager.java
+++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java
-@@ -142,6 +142,8 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
+@@ -143,6 +143,8 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
return holder.is(PoiTypes.MEETING);
});
@@ -288,19 +288,22 @@ index 583f8bbb242305bf1bf825e254209108fd323ad4..474de6f93631c07c75aa78ad95b71a4f
public Villager(EntityType extends Villager> entityType, Level world) {
this(entityType, world, VillagerType.PLAINS);
}
-@@ -245,10 +247,17 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
+@@ -246,6 +248,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
}
// Spigot End
+ private int behaviorTick = 0; // Pufferfish
@Override
- protected void customServerAiStep() { mobTick(false); }
- protected void mobTick(boolean inactive) {
- if (!inactive) this.getBrain().tick((ServerLevel) this.level, this); // Paper
+ @Deprecated // Paper
+ protected void customServerAiStep() {
+@@ -255,6 +258,12 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
+ protected void customServerAiStep(final boolean inactive) {
+ // Paper end
+ if (!inactive) this.getBrain().tick((ServerLevel) this.level(), this); // Paper
+ // Pufferfish start
+ if (!inactive) {
+ if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
-+ this.getBrain().tick((ServerLevel) this.level, this); // Paper
++ this.getBrain().tick((ServerLevel) this.level(), this); // Paper
+ }
+ // Pufferfish end
if (this.assignProfessionWhenSpawned) {
@@ -369,7 +372,7 @@ index 00fc9278a61b963382c03c36bc11917361c55612..51738097440030b33579b483ebac7587
private static void network() {
diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java
-index 754c8ab99b908b017b9ef4ceaa3ae67c7266ac44..ff247e28fef12d7cd18175b65811fb97a2828dad 100644
+index 4dc59a82f12a727f6db4a68bc1f5bd65c8cb08cc..aa6969641168f56e4e147ab211424b1eaeb70075 100644
--- a/src/main/java/org/spigotmc/ActivationRange.java
+++ b/src/main/java/org/spigotmc/ActivationRange.java
@@ -38,7 +38,11 @@ import co.aikar.timings.MinecraftTimings;
diff --git a/patches/server/0006-Pufferfish-Throttle-goal-selector-during-inactive-ti.patch b/patches/server/0006-Pufferfish-Throttle-goal-selector-during-inactive-ti.patch
index eddfea2a..af60545b 100644
--- a/patches/server/0006-Pufferfish-Throttle-goal-selector-during-inactive-ti.patch
+++ b/patches/server/0006-Pufferfish-Throttle-goal-selector-during-inactive-ti.patch
@@ -7,10 +7,10 @@ Original license: GPL v3
Original project: https://github.com/pufferfish-gg/Pufferfish
diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java
-index 7c6332171eae9e06588dd6ff9f023cb4e3e6079b..23f9b67ed0f9ec357d2ef8220f6f21c74248dcd2 100644
+index 979b76d58d05c9d83dfae45d3052eea9431dfc65..1674f9accbbbb9ecdd99f05da6032398c4d82b38 100644
--- a/src/main/java/net/minecraft/world/entity/Mob.java
+++ b/src/main/java/net/minecraft/world/entity/Mob.java
-@@ -84,6 +84,7 @@ import org.bukkit.event.entity.EntityTargetEvent;
+@@ -83,6 +83,7 @@ import org.bukkit.event.entity.EntityTargetEvent;
import org.bukkit.event.entity.EntityTransformEvent;
import org.bukkit.event.entity.EntityUnleashEvent;
import org.bukkit.event.entity.EntityUnleashEvent.UnleashReason;
@@ -18,7 +18,7 @@ index 7c6332171eae9e06588dd6ff9f023cb4e3e6079b..23f9b67ed0f9ec357d2ef8220f6f21c7
// CraftBukkit end
public abstract class Mob extends LivingEntity implements Targeting {
-@@ -217,11 +218,13 @@ public abstract class Mob extends LivingEntity implements Targeting {
+@@ -220,11 +221,13 @@ public abstract class Mob extends LivingEntity implements Targeting {
return this.lookControl;
}
diff --git a/patches/server/0007-Pufferfish-Entity-TTL.patch b/patches/server/0007-Pufferfish-Entity-TTL.patch
index 28c7db5b..3b534fdf 100644
--- a/patches/server/0007-Pufferfish-Entity-TTL.patch
+++ b/patches/server/0007-Pufferfish-Entity-TTL.patch
@@ -7,10 +7,10 @@ Original license: GPL v3
Original project: https://github.com/pufferfish-gg/Pufferfish
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
-index 2e3ab61998d4174f231118e7a4bdc9e25563715f..101ce131804492779e34218c65879398794e2d7f 100644
+index 223086fa230346e7212051710758560f874cb3b0..c6069b70d188a45950be27f9f6c63c8218dea7fa 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
-@@ -822,6 +822,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
+@@ -828,6 +828,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
// CraftBukkit end
public void baseTick() {
@@ -24,7 +24,7 @@ index 2e3ab61998d4174f231118e7a4bdc9e25563715f..101ce131804492779e34218c65879398
this.feetBlockState = null;
if (this.isPassenger() && this.getVehicle().isRemoved()) {
diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java
-index 559bce4bbcc03117f52dc2810bad5becddf14a8e..8af0918d3a62de58a4b2af55022c812bb0e46092 100644
+index 1679f0a3d095a7b758b468c77b6d3a4c078b7962..aa5cec6d56d7a8e80861aa4c9b4a74ca3e64be8c 100644
--- a/src/main/java/net/minecraft/world/entity/EntityType.java
+++ b/src/main/java/net/minecraft/world/entity/EntityType.java
@@ -301,6 +301,7 @@ public class EntityType implements FeatureElement, EntityTypeT
diff --git a/patches/server/0010-Bump-Dependencies.patch b/patches/server/0008-Bump-Dependencies.patch
similarity index 69%
rename from patches/server/0010-Bump-Dependencies.patch
rename to patches/server/0008-Bump-Dependencies.patch
index 61b5308d..f0fc2895 100644
--- a/patches/server/0010-Bump-Dependencies.patch
+++ b/patches/server/0008-Bump-Dependencies.patch
@@ -5,27 +5,19 @@ Subject: [PATCH] Bump Dependencies
diff --git a/build.gradle.kts b/build.gradle.kts
-index 586d7998918fdb8b1db3c433d10234b6d7943115..c068173108818b8b042e1c1f518cf60bbf25c9b3 100644
+index d18f72faf22e7cabee833525f08e75012471eed8..d4593da8d4cf893847705e394264dabf303c7912 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
-@@ -11,13 +11,13 @@ dependencies {
- // Depend on own API
- implementation(project(":leaf-api")) // Leaf
- // Depend on Paper MojangAPI
-- implementation("io.papermc.paper:paper-mojangapi:1.19.3-R0.1-SNAPSHOT") {
-+ implementation("io.papermc.paper:paper-mojangapi:1.19.4-R0.1-SNAPSHOT") { // Leaf - Bump Dependencies
- exclude("io.papermc.paper", "paper-api")
+@@ -16,7 +16,7 @@ dependencies {
}
// Gale end - project setup
-- implementation("io.projectreactor.tools:blockhound:1.0.7.RELEASE") // Gale - base thread pool - watch for blocking base threads
-+ implementation("io.projectreactor.tools:blockhound:1.0.8.RELEASE") // Gale - base thread pool - watch for blocking base threads // Leaf - Bump Dependencies
// Paper start
- implementation("org.jline:jline-terminal-jansi:3.21.0")
+ implementation("org.jline:jline-terminal-jansi:3.23.0") // Leaf - Bump Dependencies
implementation("net.minecrell:terminalconsoleappender:1.3.0")
/*
Required to add the missing Log4j2Plugins.dat file from log4j-core
-@@ -25,14 +25,16 @@ dependencies {
+@@ -24,14 +24,16 @@ dependencies {
all its classes to check if they are plugins.
Scanning takes about 1-2 seconds so adding this speeds up the server start.
*/
@@ -49,8 +41,8 @@ index 586d7998918fdb8b1db3c433d10234b6d7943115..c068173108818b8b042e1c1f518cf60b
implementation("org.spongepowered:configurate-yaml:4.1.2") // Paper - config files
implementation("commons-lang:commons-lang:2.6")
implementation("net.fabricmc:mapping-io:0.3.0") // Paper - needed to read mappings for stacktrace deobfuscation
-@@ -40,7 +42,7 @@ dependencies {
- runtimeOnly("com.mysql:mysql-connector-j:8.0.32")
+@@ -39,7 +41,7 @@ dependencies {
+ runtimeOnly("com.mysql:mysql-connector-j:8.0.33")
runtimeOnly("com.lmax:disruptor:3.4.4") // Paper
// Paper start - Use Velocity cipher
- implementation("com.velocitypowered:velocity-native:3.1.2-SNAPSHOT") {
@@ -58,17 +50,7 @@ index 586d7998918fdb8b1db3c433d10234b6d7943115..c068173108818b8b042e1c1f518cf60b
isTransitive = false
}
// Paper end
-@@ -49,22 +51,25 @@ dependencies {
- implementation("org.mozilla:rhino-engine:1.7.14") // Purpur
- implementation("dev.omega24:upnp4j:1.0") // Purpur
-
-- runtimeOnly("org.apache.maven:maven-resolver-provider:3.8.5")
-- runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.7.3")
-- runtimeOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.7.3")
-+ runtimeOnly("org.apache.maven:maven-resolver-provider:3.9.1")
-+ runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.7")
-+ runtimeOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.9.7")
-+ // Leaf end
+@@ -50,16 +52,18 @@ dependencies {
// Pufferfish start
implementation("org.yaml:snakeyaml:1.33")
@@ -90,8 +72,8 @@ index 586d7998918fdb8b1db3c433d10234b6d7943115..c068173108818b8b042e1c1f518cf60b
+ // Leaf end
}
- val craftbukkitPackageVersion = "1_19_R3" // Paper
-@@ -231,3 +236,6 @@ tasks.registerRunTask("runDev") {
+ val craftbukkitPackageVersion = "1_20_R1" // Paper
+@@ -225,3 +229,6 @@ tasks.registerRunTask("runDev") {
description = "Spin up a non-relocated Mojang-mapped test server"
classpath(sourceSets.main.map { it.runtimeClasspath })
}
diff --git a/patches/server/0008-Purpur-Base.patch b/patches/server/0008-Purpur-Base.patch
deleted file mode 100644
index baafd36a..00000000
--- a/patches/server/0008-Purpur-Base.patch
+++ /dev/null
@@ -1,98 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Github Actions
-Date: Wed, 24 May 2023 06:00:03 +0800
-Subject: [PATCH] Purpur Base
-
-Original license: MIT
-Original project: https://github.com/PurpurMC/Purpur
-
-diff --git a/build.gradle.kts b/build.gradle.kts
-index 493acdeeb56ed13a0c30d9d10b2f9171df296d98..3c1e97fb031f7cdf73ecb6cf8ec662e08b78f96f 100644
---- a/build.gradle.kts
-+++ b/build.gradle.kts
-@@ -45,6 +45,10 @@ dependencies {
- }
- // Paper end
-
-+ implementation("org.mozilla:rhino-runtime:1.7.14") // Purpur
-+ implementation("org.mozilla:rhino-engine:1.7.14") // Purpur
-+ implementation("dev.omega24:upnp4j:1.0") // Purpur
-+
- runtimeOnly("org.apache.maven:maven-resolver-provider:3.8.5")
- runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.7.3")
- runtimeOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.7.3")
-@@ -168,7 +172,7 @@ fun TaskContainer.registerRunTask(
- }
- }
- // Gale end - use default Java installation for development runs
-- group = "paper"
-+ group = "paperweight" // Purpur
- mainClass.set("org.bukkit.craftbukkit.Main")
- standardInput = System.`in`
- workingDir = rootProject.layout.projectDirectory
-diff --git a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java
-index 4e56018b64d11f76c8da43fd8f85c6de72204e36..aa8212432825db65cf485cd93f734ccd9eefcb5a 100644
---- a/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java
-+++ b/src/main/java/org/bukkit/craftbukkit/command/CraftConsoleCommandSender.java
-@@ -21,7 +21,12 @@ public class CraftConsoleCommandSender extends ServerCommandSender implements Co
-
- @Override
- public void sendMessage(String message) {
-- this.sendRawMessage(message);
-+ // Purpur start
-+ String[] parts = message.split("\n");
-+ for (String part : parts) {
-+ this.sendRawMessage(part);
-+ }
-+ // Purpur end
- }
-
- @Override
-@@ -91,7 +96,7 @@ public class CraftConsoleCommandSender extends ServerCommandSender implements Co
- // Paper start
- @Override
- public void sendMessage(final net.kyori.adventure.identity.Identity identity, final net.kyori.adventure.text.Component message, final net.kyori.adventure.audience.MessageType type) {
-- this.sendRawMessage(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(message));
-+ this.sendMessage(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(message)); // Purpur
- }
-
- @Override
-diff --git a/src/main/java/org/galemc/gale/version/AbstractPaperVersionFetcher.java b/src/main/java/org/galemc/gale/version/AbstractPaperVersionFetcher.java
-index 7fee1c2779ab390586b2d3f75f56890846323500..5fdb227acfd1d8f55b770c8a66e97494c36db33c 100644
---- a/src/main/java/org/galemc/gale/version/AbstractPaperVersionFetcher.java
-+++ b/src/main/java/org/galemc/gale/version/AbstractPaperVersionFetcher.java
-@@ -68,7 +68,7 @@ public abstract class AbstractPaperVersionFetcher implements VersionFetcher {
- // Gale end - branding changes - version fetcher
- final Component history = getHistory();
-
-- return history != null ? TextComponent.ofChildren(updateMessage, Component.newline(), history) : updateMessage;
-+ return history != null ? Component.join(net.kyori.adventure.text.JoinConfiguration.separator(Component.newline()), history, updateMessage) : updateMessage; // Purpur
- }
-
- protected @Nullable String getMinecraftVersion() { // Gale - branding changes - version fetcher
-@@ -120,13 +120,13 @@ public abstract class AbstractPaperVersionFetcher implements VersionFetcher {
-
- switch (distance) {
- case -1:
-- return Component.text("Error obtaining version information", NamedTextColor.YELLOW);
-+ return Component.text("* Error obtaining version information", NamedTextColor.RED); // Purpur
- case 0:
-- return Component.text("You are running the latest version", NamedTextColor.GREEN);
-+ return Component.text("* You are running the latest version", NamedTextColor.GREEN); // Purpur
- case -2:
-- return Component.text("Unknown version", NamedTextColor.YELLOW);
-+ return Component.text("* Unknown version", NamedTextColor.YELLOW); // Purpur
- default:
-- return Component.text("You are " + distance + " version(s) behind", NamedTextColor.YELLOW)
-+ return Component.text("* You are " + distance + " version(s) behind", NamedTextColor.YELLOW) // Purpur
- .append(Component.newline())
- .append(Component.text("Download the new version at: ")
- .append(Component.text(this.downloadPage, NamedTextColor.GOLD) // Gale - branding changes - version fetcher
-@@ -174,6 +174,6 @@ public abstract class AbstractPaperVersionFetcher implements VersionFetcher {
- return null;
- }
-
-- return Component.text("Previous version: " + oldVersion, NamedTextColor.GRAY, TextDecoration.ITALIC);
-+ return org.bukkit.ChatColor.parseMM("Previous: %s", oldVersion); // Purpur
- }
- }
diff --git a/patches/server/0009-Purpur-Server-Changes.patch b/patches/server/0009-Purpur-Server-Changes.patch
deleted file mode 100644
index 46b2d8b9..00000000
--- a/patches/server/0009-Purpur-Server-Changes.patch
+++ /dev/null
@@ -1,24687 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Github Actions
-Date: Tue, 23 May 2023 21:25:22 +0000
-Subject: [PATCH] Purpur Server Changes
-
-
-diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java
-index a08c00b8c0488d18be5e182f7892e5ab71d12247..338f693d098b6ab507c30f6411c9a952c34ba8e3 100644
---- a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java
-+++ b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java
-@@ -136,6 +136,10 @@ public class MobGoalHelper {
- static {
- // TODO these kinda should be checked on each release, in case obfuscation changes
- deobfuscationMap.put("abstract_skeleton_1", "abstract_skeleton_melee");
-+ // Purpur start
-+ deobfuscationMap.put("zombie_1", "zombie_attack_villager");
-+ deobfuscationMap.put("drowned_1", "drowned_attack_villager");
-+ // Purpur end
-
- ignored.add("goal_selector_1");
- ignored.add("goal_selector_2");
-diff --git a/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java b/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java
-index f0fce4113fb07c64adbec029d177c236cbdcbae8..e94224ed280247ee69dfdff8dc960f2b8729be33 100644
---- a/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java
-+++ b/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java
-@@ -78,10 +78,10 @@ public class PaperPluginsCommand extends BukkitCommand {
- this.setAliases(Arrays.asList("pl"));
- }
-
-- private static List formatProviders(TreeMap> plugins) {
-+ private static List formatProviders(TreeMap> plugins, @NotNull CommandSender sender) { // Purpur
- List components = new ArrayList<>(plugins.size());
- for (PluginProvider entry : plugins.values()) {
-- components.add(formatProvider(entry));
-+ components.add(formatProvider(entry, sender)); // Purpur
- }
-
- boolean isFirst = true;
-@@ -109,7 +109,7 @@ public class PaperPluginsCommand extends BukkitCommand {
- return formattedSublists;
- }
-
-- private static Component formatProvider(PluginProvider> provider) {
-+ private static Component formatProvider(PluginProvider> provider, @NotNull CommandSender sender) { // Purpur
- TextComponent.Builder builder = Component.text();
- if (provider instanceof SpigotPluginProvider spigotPluginProvider && CraftMagicNumbers.isLegacy(spigotPluginProvider.getMeta())) {
- builder.append(LEGACY_PLUGIN_STAR);
-@@ -117,12 +117,64 @@ public class PaperPluginsCommand extends BukkitCommand {
-
- String name = provider.getMeta().getName();
- Component pluginName = Component.text(name, fromStatus(provider))
-- .clickEvent(ClickEvent.runCommand("/version " + name));
-+ // Purpur start
-+ .clickEvent(ClickEvent.suggestCommand("/version " + name));
-+
-+ if (sender instanceof org.bukkit.entity.Player && sender.hasPermission("bukkit.command.version")) {
-+ // Event components
-+ String description = provider.getMeta().getDescription();
-+ TextComponent.Builder hover = Component.text();
-+ hover.append(Component.text("Version: ", NamedTextColor.WHITE)).append(Component.text(provider.getMeta().getVersion(), NamedTextColor.GREEN));
-+
-+ if (description != null) {
-+ hover.append(Component.newline())
-+ .append(Component.text("Description: ", NamedTextColor.WHITE))
-+ .append(Component.text(description, NamedTextColor.GREEN));
-+ }
-+
-+ if (provider.getMeta().getWebsite() != null) {
-+ hover.append(Component.newline())
-+ .append(Component.text("Website: ", NamedTextColor.WHITE))
-+ .append(Component.text(provider.getMeta().getWebsite(), NamedTextColor.GREEN));
-+ }
-+
-+ if (!provider.getMeta().getAuthors().isEmpty()) {
-+ hover.append(Component.newline());
-+ if (provider.getMeta().getAuthors().size() == 1) {
-+ hover.append(Component.text("Author: "));
-+ } else {
-+ hover.append(Component.text("Authors: "));
-+ }
-+
-+ hover.append(getAuthors(provider.getMeta()));
-+ }
-+
-+ pluginName.hoverEvent(hover.build());
-+ }
-
- builder.append(pluginName);
-+ // Purpur end
-+
-+ return builder.build();
-+ }
-+
-+ // Purpur start
-+ @NotNull
-+ private static TextComponent getAuthors(@NotNull final PluginMeta pluginMeta) {
-+ TextComponent.Builder builder = Component.text();
-+ List authors = pluginMeta.getAuthors();
-+
-+ for (int i = 0; i < authors.size(); i++) {
-+ if (i > 0) {
-+ builder.append(Component.text(i < authors.size() - 1 ? ", " : " and ", NamedTextColor.WHITE));
-+ }
-+
-+ builder.append(Component.text(authors.get(i), NamedTextColor.GREEN));
-+ }
-
- return builder.build();
- }
-+ // Purpur end
-
- private static Component asPlainComponents(String strings) {
- net.kyori.adventure.text.TextComponent.Builder builder = Component.text();
-@@ -182,24 +234,24 @@ public class PaperPluginsCommand extends BukkitCommand {
- }
- }
-
-- Component infoMessage = Component.text("Server Plugins (%s):".formatted(paperPlugins.size() + spigotPlugins.size()), NamedTextColor.WHITE);
-+ //Component infoMessage = Component.text("Server Plugins (%s):".formatted(paperPlugins.size() + spigotPlugins.size()), NamedTextColor.WHITE);
- //.append(INFO_ICON_START.hoverEvent(SERVER_PLUGIN_INFO)); TODO: Add docs
-
-- sender.sendMessage(infoMessage);
-+ //sender.sendMessage(infoMessage); // Purpur
-
- if (!paperPlugins.isEmpty()) {
-- sender.sendMessage(PAPER_HEADER);
-+ sender.sendMessage(PAPER_HEADER.append(Component.text(" (%s):".formatted(paperPlugins.size())))); // Purpur
- }
-
-- for (Component component : formatProviders(paperPlugins)) {
-+ for (Component component : formatProviders(paperPlugins, sender)) { // Purpur
- sender.sendMessage(component);
- }
-
- if (!spigotPlugins.isEmpty()) {
-- sender.sendMessage(BUKKIT_HEADER);
-+ sender.sendMessage(BUKKIT_HEADER.append(Component.text(" (%s):".formatted(spigotPlugins.size())))); // Purpur
- }
-
-- for (Component component : formatProviders(spigotPlugins)) {
-+ for (Component component : formatProviders(spigotPlugins, sender)) { // Purpur
- sender.sendMessage(component);
- }
-
-diff --git a/src/main/java/io/papermc/paper/logging/SysoutCatcher.java b/src/main/java/io/papermc/paper/logging/SysoutCatcher.java
-index a8e813ca89b033f061e695288b3383bdcf128531..1ab65af9359d19530bba7f985a604d2a430ee234 100644
---- a/src/main/java/io/papermc/paper/logging/SysoutCatcher.java
-+++ b/src/main/java/io/papermc/paper/logging/SysoutCatcher.java
-@@ -54,9 +54,9 @@ public final class SysoutCatcher {
- final JavaPlugin plugin = JavaPlugin.getProvidingPlugin(clazz);
-
- // Instead of just printing the message, send it to the plugin's logger
-- plugin.getLogger().log(this.level, this.prefix + line);
-+ plugin.getLogger().log(this.level, /*this.prefix +*/ line); // Purpur - prefix not needed
-
-- if (SysoutCatcher.SUPPRESS_NAGS) {
-+ if (true || SysoutCatcher.SUPPRESS_NAGS) { // Purpur - nagging is annoying
- return;
- }
- if (SysoutCatcher.NAG_INTERVAL > 0 || SysoutCatcher.NAG_TIMEOUT > 0) {
-diff --git a/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java b/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java
-index 89bf48fd581ee6580b91e2eb31dd532cb622df5e..e35da199be67e04c34df6bc09afd8d8122cb0487 100644
---- a/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java
-+++ b/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java
-@@ -102,6 +102,7 @@ public class PluginInitializerManager {
- java.util.List files = (java.util.List) optionSet.valuesOf("add-plugin");
- // Register plugins from the flag
- io.papermc.paper.plugin.util.EntrypointUtil.registerProvidersFromSource(io.papermc.paper.plugin.provider.source.PluginFlagProviderSource.INSTANCE, files);
-+ io.papermc.paper.plugin.util.EntrypointUtil.registerProvidersFromSource(io.papermc.paper.plugin.provider.source.SparkProviderSource.INSTANCE, new File("cache", "spark.jar").toPath()); // Purpur
- }
-
- // This will be the end of me...
-diff --git a/src/main/java/io/papermc/paper/plugin/provider/source/SparkProviderSource.java b/src/main/java/io/papermc/paper/plugin/provider/source/SparkProviderSource.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..a7d1ae53eac94bc2dcf8bc78ef1da0d3b8554736
---- /dev/null
-+++ b/src/main/java/io/papermc/paper/plugin/provider/source/SparkProviderSource.java
-@@ -0,0 +1,102 @@
-+package io.papermc.paper.plugin.provider.source;
-+
-+import com.mojang.logging.LogUtils;
-+import io.papermc.paper.plugin.entrypoint.Entrypoint;
-+import io.papermc.paper.plugin.entrypoint.EntrypointHandler;
-+import io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler;
-+import io.papermc.paper.plugin.provider.PluginProvider;
-+import java.io.BufferedReader;
-+import java.io.File;
-+import java.io.InputStreamReader;
-+import java.math.BigInteger;
-+import java.net.URL;
-+import java.net.URLConnection;
-+import java.nio.file.Files;
-+import java.nio.file.Path;
-+import java.nio.file.StandardCopyOption;
-+import java.security.MessageDigest;
-+import java.util.stream.Collectors;
-+import org.bukkit.plugin.java.JavaPlugin;
-+import org.slf4j.Logger;
-+
-+public class SparkProviderSource extends FileProviderSource {
-+ public static final SparkProviderSource INSTANCE = new SparkProviderSource();
-+
-+ private static final Logger LOGGER = LogUtils.getClassLogger();
-+
-+ public SparkProviderSource() {
-+ super("File '%s' specified by Purpur"::formatted);
-+ }
-+
-+ @Override
-+ public void registerProviders(EntrypointHandler entrypointHandler, Path context) throws Exception {
-+ // first, check if user doesn't want spark at all
-+ if (Boolean.getBoolean("Purpur.IReallyDontWantSpark")) {
-+ return; // boo!
-+ }
-+
-+ // second, check if user has their own spark
-+ if (hasSpark()) {
-+ LOGGER.info("Purpur: Using user-provided spark plugin instead of our own.");
-+ return; // let's hope it's at least the modern version :3
-+ }
-+
-+ // you can't have errors in your code if you wrap the entire codebase in a try/catch block
-+ try {
-+
-+ // make sure the directory exists where we want to keep spark
-+ File file = context.toFile();
-+ file.getParentFile().mkdirs();
-+
-+ boolean shouldDownload;
-+
-+ // check if our spark exists
-+ if (!file.exists()) {
-+ // it does not, so let's download it
-+ shouldDownload = true;
-+ } else {
-+ // we have a spark file, let's see if it's up-to-date by comparing shas
-+ String fileSha1 = String.format("%040x", new BigInteger(1, MessageDigest.getInstance("SHA-1").digest(Files.readAllBytes(file.toPath()))));
-+ String sparkSha1;
-+
-+ // luck has a nifty endpoint containing the sha of the newest version
-+ URLConnection urlConnection = new URL("https://sparkapi.lucko.me/download/bukkit/sha1").openConnection();
-+
-+ // set a reasonable timeout to prevent servers without internet from hanging for 60+ seconds on startup
-+ urlConnection.setReadTimeout(5000);
-+ urlConnection.setConnectTimeout(5000);
-+
-+ // read it
-+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()))) {
-+ sparkSha1 = reader.lines().collect(Collectors.joining(""));
-+ }
-+
-+ // compare; we only download a new spark if the shas don't match
-+ shouldDownload = !fileSha1.equals(sparkSha1);
-+ }
-+
-+ // ok, finally we can download spark if we need it
-+ if (shouldDownload) {
-+ URLConnection urlConnection = new URL("https://sparkapi.lucko.me/download/bukkit").openConnection();
-+ urlConnection.setReadTimeout(5000);
-+ urlConnection.setConnectTimeout(5000);
-+ Files.copy(urlConnection.getInputStream(), file.toPath(), StandardCopyOption.REPLACE_EXISTING);
-+ }
-+
-+ // register the spark, newly downloaded or existing
-+ super.registerProviders(entrypointHandler, context);
-+
-+ } catch (Throwable e) {
-+ LOGGER.error("Purpur: Failed to download and install spark plugin", e);
-+ }
-+ }
-+
-+ private static boolean hasSpark() {
-+ for (PluginProvider provider : LaunchEntryPointHandler.INSTANCE.get(Entrypoint.PLUGIN).getRegisteredProviders()) {
-+ if (provider.getMeta().getName().equalsIgnoreCase("spark")) {
-+ return true;
-+ }
-+ }
-+ return false;
-+ }
-+}
-diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java
-index 7b6b51392b123d34382233adcf4c3d4867bdaa32..941f3a0d50329658a9380500ef039d7f10a284e2 100644
---- a/src/main/java/net/minecraft/commands/CommandSourceStack.java
-+++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java
-@@ -212,6 +212,21 @@ public class CommandSourceStack implements SharedSuggestionProvider, com.destroy
- }
- // CraftBukkit end
-
-+ // Purpur start
-+ public boolean testPermission(int i, String bukkitPermission) {
-+ if (hasPermission(i, bukkitPermission)) {
-+ return true;
-+ }
-+ String permissionMessage = getLevel().getServer().server.getPermissionMessage();
-+ if (!permissionMessage.isBlank()) {
-+ for (String line : permissionMessage.replace("", bukkitPermission).split("\n")) {
-+ sendFailure(Component.literal(line));
-+ }
-+ }
-+ return false;
-+ }
-+ // Purpur end
-+
- public Vec3 getPosition() {
- return this.worldPosition;
- }
-@@ -317,6 +332,30 @@ public class CommandSourceStack implements SharedSuggestionProvider, com.destroy
- }
- }
-
-+ // Purpur start
-+ public void sendSuccess(@Nullable String message) {
-+ sendSuccess(message, false);
-+ }
-+
-+ public void sendSuccess(@Nullable String message, boolean broadcastToOps) {
-+ if (message == null) {
-+ return;
-+ }
-+ sendSuccess(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(message), broadcastToOps);
-+ }
-+
-+ public void sendSuccess(@Nullable net.kyori.adventure.text.Component message) {
-+ sendSuccess(message, false);
-+ }
-+
-+ public void sendSuccess(@Nullable net.kyori.adventure.text.Component message, boolean broadcastToOps) {
-+ if (message == null) {
-+ return;
-+ }
-+ sendSuccess(io.papermc.paper.adventure.PaperAdventure.asVanilla(message), broadcastToOps);
-+ }
-+ // Purpur end
-+
- public void sendSuccess(Component message, boolean broadcastToOps) {
- if (this.source.acceptsSuccess() && !this.silent) {
- this.source.sendSystemMessage(message);
-diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java
-index 1a28f9b348a24448bd4a327e1bf0dfab4dc301f5..b4509750d2171fa09d62b45c9ea4ad7b87aaab29 100644
---- a/src/main/java/net/minecraft/commands/Commands.java
-+++ b/src/main/java/net/minecraft/commands/Commands.java
-@@ -221,6 +221,14 @@ public class Commands {
- SetPlayerIdleTimeoutCommand.register(this.dispatcher);
- StopCommand.register(this.dispatcher);
- WhitelistCommand.register(this.dispatcher);
-+ org.purpurmc.purpur.command.CreditsCommand.register(this.dispatcher); // Purpur
-+ org.purpurmc.purpur.command.DemoCommand.register(this.dispatcher); // Purpur
-+ org.purpurmc.purpur.command.PingCommand.register(this.dispatcher); // Purpur
-+ org.purpurmc.purpur.command.UptimeCommand.register(this.dispatcher); // Purpur
-+ org.purpurmc.purpur.command.TPSBarCommand.register(this.dispatcher); // Purpur
-+ org.purpurmc.purpur.command.CompassCommand.register(this.dispatcher); // Purpur
-+ org.purpurmc.purpur.command.RamBarCommand.register(this.dispatcher); // Purpur
-+ org.purpurmc.purpur.command.RamCommand.register(this.dispatcher); // Purpur
- }
-
- if (environment.includeIntegrated) {
-diff --git a/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java b/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java
-index 64d957ba23d306327a26605e1e42f32fa741e2cb..9ce64b6db3c9d1211fd6a1fb644d3cae9630a149 100644
---- a/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java
-+++ b/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java
-@@ -197,10 +197,10 @@ public class EntitySelector {
-
- if (this.playerName != null) {
- entityplayer = source.getServer().getPlayerList().getPlayerByName(this.playerName);
-- return (List) (entityplayer == null ? Collections.emptyList() : Lists.newArrayList(new ServerPlayer[]{entityplayer}));
-+ return entityplayer == null || !canSee(source, entityplayer) ? Collections.emptyList() : Lists.newArrayList(entityplayer); // Purpur
- } else if (this.entityUUID != null) {
- entityplayer = source.getServer().getPlayerList().getPlayer(this.entityUUID);
-- return (List) (entityplayer == null ? Collections.emptyList() : Lists.newArrayList(new ServerPlayer[]{entityplayer}));
-+ return entityplayer == null || !canSee(source, entityplayer) ? Collections.emptyList() : Lists.newArrayList(entityplayer); // Purpur
- } else {
- Vec3 vec3d = (Vec3) this.position.apply(source.getPosition());
- Predicate predicate = this.getPredicate(vec3d);
-@@ -210,7 +210,7 @@ public class EntitySelector {
- ServerPlayer entityplayer1 = (ServerPlayer) source.getEntity();
-
- if (predicate.test(entityplayer1)) {
-- return Lists.newArrayList(new ServerPlayer[]{entityplayer1});
-+ return !canSee(source, entityplayer1) ? Collections.emptyList() : Lists.newArrayList(entityplayer1); // Purpur
- }
- }
-
-@@ -221,6 +221,7 @@ public class EntitySelector {
-
- if (this.isWorldLimited()) {
- object = source.getLevel().getPlayers(predicate, i);
-+ ((List) object).removeIf(entityplayer3 -> !canSee(source, (ServerPlayer) entityplayer3)); // Purpur
- } else {
- object = Lists.newArrayList();
- Iterator iterator = source.getServer().getPlayerList().getPlayers().iterator();
-@@ -228,7 +229,7 @@ public class EntitySelector {
- while (iterator.hasNext()) {
- ServerPlayer entityplayer2 = (ServerPlayer) iterator.next();
-
-- if (predicate.test(entityplayer2)) {
-+ if (predicate.test(entityplayer2) && canSee(source, entityplayer2)) { // Purpur
- ((List) object).add(entityplayer2);
- if (((List) object).size() >= i) {
- return (List) object;
-@@ -273,4 +274,10 @@ public class EntitySelector {
- public static Component joinNames(List extends Entity> entities) {
- return ComponentUtils.formatList(entities, Entity::getDisplayName);
- }
-+
-+ // Purpur start
-+ private boolean canSee(CommandSourceStack sender, ServerPlayer target) {
-+ return !org.purpurmc.purpur.PurpurConfig.hideHiddenPlayersFromEntitySelector || !(sender.getEntity() instanceof ServerPlayer player) || player.getBukkitEntity().canSee(target.getBukkitEntity());
-+ }
-+ // Purpur end
- }
-diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java
-index b37e0ff164a894d2033fb94bbbc2f630a0e66bcd..ac335ec4f70830c7687ac4e0aa2a6cba9cb04ae1 100644
---- a/src/main/java/net/minecraft/core/BlockPos.java
-+++ b/src/main/java/net/minecraft/core/BlockPos.java
-@@ -41,6 +41,12 @@ public class BlockPos extends Vec3i {
- private static final int X_OFFSET = 38;
- // Paper end
-
-+ // Purpur start
-+ public BlockPos(net.minecraft.world.entity.Entity entity) {
-+ super(entity.getBlockX(), entity.getBlockY(), entity.getBlockZ());
-+ }
-+ // Purpur end
-+
- public BlockPos(int x, int y, int z) {
- super(x, y, z);
- }
-diff --git a/src/main/java/net/minecraft/core/Direction.java b/src/main/java/net/minecraft/core/Direction.java
-index 82bce6109d59cba30178a446f0ff129da6f3692f..eaa620ad86abfb151b43f697973cbc731e2e5e92 100644
---- a/src/main/java/net/minecraft/core/Direction.java
-+++ b/src/main/java/net/minecraft/core/Direction.java
-@@ -248,6 +248,12 @@ public enum Direction implements StringRepresentable {
- case EAST:
- var10000 = SOUTH;
- break;
-+ // Purpur start
-+ case UP:
-+ return UP;
-+ case DOWN:
-+ return DOWN;
-+ // Purpur end
- default:
- throw new IllegalStateException("Unable to get Y-rotated facing of " + this);
- }
-@@ -360,6 +366,12 @@ public enum Direction implements StringRepresentable {
- case EAST:
- var10000 = NORTH;
- break;
-+ // Purpur start
-+ case UP:
-+ return UP;
-+ case DOWN:
-+ return DOWN;
-+ // Purpur end
- default:
- throw new IllegalStateException("Unable to get CCW facing of " + this);
- }
-diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java
-index 88d18d18d69876c98e199acb647c6cca9448d55d..da9cc93f560269a00f0093ad76aba3a05eedb046 100644
---- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java
-+++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java
-@@ -52,6 +52,7 @@ import net.minecraft.world.item.SpawnEggItem;
- import net.minecraft.world.item.alchemy.PotionUtils;
- import net.minecraft.world.item.alchemy.Potions;
- import net.minecraft.world.level.Level;
-+import net.minecraft.world.level.block.AnvilBlock;
- import net.minecraft.world.level.block.BaseFireBlock;
- import net.minecraft.world.level.block.BeehiveBlock;
- import net.minecraft.world.level.block.Block;
-@@ -1168,6 +1169,23 @@ public interface DispenseItemBehavior {
- }
- }
- });
-+ // Purpur start
-+ DispenserBlock.registerBehavior(Items.ANVIL, (new OptionalDispenseItemBehavior() {
-+ @Override
-+ public ItemStack execute(BlockSource dispenser, ItemStack stack) {
-+ Level level = dispenser.getLevel();
-+ if (!level.purpurConfig.dispenserPlaceAnvils) return super.execute(dispenser, stack);
-+ Direction facing = dispenser.getBlockState().getValue(DispenserBlock.FACING);
-+ BlockPos pos = dispenser.getPos().relative(facing);
-+ BlockState state = level.getBlockState(pos);
-+ if (state.isAir()) {
-+ level.setBlockAndUpdate(pos, Blocks.ANVIL.defaultBlockState().setValue(AnvilBlock.FACING, facing.getAxis() == Direction.Axis.Y ? Direction.NORTH : facing.getClockWise()));
-+ stack.shrink(1);
-+ }
-+ return stack;
-+ }
-+ }));
-+ // Purpur end
- }
-
- static void setEntityPokingOutOfBlock(BlockSource pointer, Entity entity, Direction direction) {
-diff --git a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java
-index d1127d93a85a837933d0d73c24cacac4adc3a5b9..d9a6d273108165f59b995b1fd7748cb5c12b8b1f 100644
---- a/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java
-+++ b/src/main/java/net/minecraft/core/dispenser/ShearsDispenseItemBehavior.java
-@@ -107,7 +107,7 @@ public class ShearsDispenseItemBehavior extends OptionalDispenseItemBehavior {
- continue;
- }
- // CraftBukkit end
-- ishearable.shear(SoundSource.BLOCKS);
-+ ishearable.shear(SoundSource.BLOCKS, net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.MOB_LOOTING, CraftItemStack.asNMSCopy(craftItem))); // Purpur
- worldserver.gameEvent((Entity) null, GameEvent.SHEAR, blockposition);
- return true;
- }
-diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java
-index 0aef4fe2af72e8006f37c02f4f8eaa651b870671..2541b4680f398997f16aa9cefd3b276ebd7f0305 100644
---- a/src/main/java/net/minecraft/network/Connection.java
-+++ b/src/main/java/net/minecraft/network/Connection.java
-@@ -572,11 +572,20 @@ public class Connection extends SimpleChannelInboundHandler> {
- private static final int MAX_PER_TICK = io.papermc.paper.configuration.GlobalConfiguration.get().misc.maxJoinsPerTick; // Paper
- private static int joinAttemptsThisTick; // Paper
- private static int currTick; // Paper
-+ private static int tickSecond; // Purpur
- public void tick() {
- this.flushQueue();
- // Paper start
- if (Connection.currTick != net.minecraft.server.MinecraftServer.currentTick) {
- Connection.currTick = net.minecraft.server.MinecraftServer.currentTick;
-+ // Purpur start
-+ if (org.purpurmc.purpur.PurpurConfig.maxJoinsPerSecond) {
-+ if (++Connection.tickSecond > 20) {
-+ Connection.tickSecond = 0;
-+ Connection.joinAttemptsThisTick = 0;
-+ }
-+ } else
-+ // Purpur end
- Connection.joinAttemptsThisTick = 0;
- }
- // Paper end
-diff --git a/src/main/java/net/minecraft/network/FriendlyByteBuf.java b/src/main/java/net/minecraft/network/FriendlyByteBuf.java
-index 9938bb90bef84cf784f9a1ceb02a1a45aa8b48a1..1f4b64a5f812376c499c98cb4be62469bd0b7dbe 100644
---- a/src/main/java/net/minecraft/network/FriendlyByteBuf.java
-+++ b/src/main/java/net/minecraft/network/FriendlyByteBuf.java
-@@ -98,6 +98,8 @@ public class FriendlyByteBuf extends ByteBuf {
- private static final int MAX_PUBLIC_KEY_LENGTH = 512;
- private static final Gson GSON = new Gson();
-
-+ public static boolean hasItemSerializeEvent = false; // Purpur
-+
- public FriendlyByteBuf(ByteBuf parent) {
- this.source = parent;
- }
-@@ -679,6 +681,17 @@ public class FriendlyByteBuf extends ByteBuf {
- this.writeBoolean(false);
- } else {
- this.writeBoolean(true);
-+ // Purpur start
-+ if (hasItemSerializeEvent) {
-+ var event = new org.purpurmc.purpur.event.packet.NetworkItemSerializeEvent(stack.asBukkitCopy());
-+ event.callEvent();
-+ ItemStack newStack = ItemStack.fromBukkitCopy(event.getItemStack());
-+ if (org.purpurmc.purpur.PurpurConfig.fixNetworkSerializedItemsInCreative && !ItemStack.matches(stack, newStack)) {
-+ stack.save(newStack.getOrCreateTagElement("Purpur.OriginalItem"));
-+ }
-+ stack = newStack;
-+ }
-+ // Purpur end
- Item item = stack.getItem();
-
- this.writeId(BuiltInRegistries.ITEM, item);
-diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerCombatKillPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerCombatKillPacket.java
-index 53b75f5737a910ffc5448cd9a85eae57f9c1488f..ea95873dd034779e56a8b924cd27f9375be05daf 100644
---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerCombatKillPacket.java
-+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundPlayerCombatKillPacket.java
-@@ -9,6 +9,7 @@ public class ClientboundPlayerCombatKillPacket implements Packet {
- private final long gameTime;
-- private final long dayTime;
-+ private long dayTime; public void setDayTime(long dayTime) { this.dayTime = dayTime; } // Purpur
-
- public ClientboundSetTimePacket(long time, long timeOfDay, boolean doDaylightCycle) {
- this.gameTime = time;
-diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
-index b39d799026efbfaf659471e58cfaa9210f0902b9..44a31750f08bee7e44565739fa1eaf6a78f10b3b 100644
---- a/src/main/java/net/minecraft/server/MinecraftServer.java
-+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
-@@ -244,7 +244,7 @@ public abstract class MinecraftServer extends MinecraftServerBlockableEventLoop
- private boolean allowFlight;
- @Nullable
- private String motd;
-- @Nullable private net.kyori.adventure.text.Component cachedMotd; // Paper
-+ private net.kyori.adventure.text.Component cachedMotd = net.kyori.adventure.text.Component.empty(); // Paper // Purpur
- private int playerIdleTimeout;
- public final long[] tickTimes;
- // Paper start
-@@ -394,6 +394,7 @@ public abstract class MinecraftServer extends MinecraftServerBlockableEventLoop
- public org.bukkit.command.RemoteConsoleCommandSender remoteConsole;
- //public ConsoleReader reader; // Paper
- public static int currentTick = 0; // Paper - Further improve tick loop
-+ public static final long startTimeMillis = System.currentTimeMillis();
- public java.util.Queue processQueue = new java.util.concurrent.ConcurrentLinkedQueue();
- public int autosavePeriod;
- public Commands vanillaCommandDispatcher;
-@@ -403,11 +404,13 @@ public abstract class MinecraftServer extends MinecraftServerBlockableEventLoop
- public static final int TPS = 20;
- public static final int TICK_TIME = 1000000000 / MinecraftServer.TPS;
- private static final int SAMPLE_INTERVAL = 20; // Paper
-- public final double[] recentTps = new double[ 3 ];
-+ public final double[] recentTps = new double[ 4 ]; // Purpur
- // Spigot end
- public final io.papermc.paper.configuration.PaperConfigurations paperConfigurations;
- public final GaleConfigurations galeConfigurations; // Gale - Gale configuration
- public static long currentTickLong = 0L; // Paper
-+ public boolean lagging = false; // Purpur
-+ protected boolean upnp = false; // Purpur
-
- public volatile Thread shutdownThread; // Paper
- public volatile boolean abnormalExit = false; // Paper
-@@ -1049,6 +1052,15 @@ public abstract class MinecraftServer extends MinecraftServerBlockableEventLoop
- MinecraftServer.LOGGER.info("Stopping server");
- Commands.COMMAND_SENDING_POOL.shutdownNow(); // Paper - Shutdown and don't bother finishing
- MinecraftTimings.stopServer(); // Paper
-+ // Purpur start
-+ if (upnp) {
-+ if (dev.omega24.upnp4j.UPnP4J.close(this.getPort(), dev.omega24.upnp4j.util.Protocol.TCP)) {
-+ LOGGER.info("[UPnP] Port {} closed", this.getPort());
-+ } else {
-+ LOGGER.error("[UPnP] Failed to close port {}", this.getPort());
-+ }
-+ }
-+ // Purpur end
- // CraftBukkit start
- if (this.server != null) {
- this.server.disablePlugins();
-@@ -1132,6 +1144,8 @@ public abstract class MinecraftServer extends MinecraftServerBlockableEventLoop
- this.safeShutdown(waitForShutdown, false);
- }
- public void safeShutdown(boolean waitForShutdown, boolean isRestarting) {
-+ org.purpurmc.purpur.task.BossBarTask.stopAll(); // Purpur
-+ org.purpurmc.purpur.task.BeehiveTask.instance().unregister(); // Purpur
- this.isRestarting = isRestarting;
- this.hasLoggedStop = true; // Paper
- if (isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Server stopped"); // Paper
-@@ -1270,10 +1284,14 @@ public abstract class MinecraftServer extends MinecraftServerBlockableEventLoop
- tps5.add(currentTps, diff);
- tps15.add(currentTps, diff);
- // Backwards compat with bad plugins
-- this.recentTps[0] = tps1.getAverage();
-- this.recentTps[1] = tps5.getAverage();
-- this.recentTps[2] = tps15.getAverage();
-+ // Purpur start
-+ this.recentTps[0] = tps5s.getAverage();
-+ this.recentTps[1] = tps1.getAverage();
-+ this.recentTps[2] = tps5.getAverage();
-+ this.recentTps[3] = tps15.getAverage();
-+ // Purpur end
- // Paper end
-+ lagging = recentTps[0] < org.purpurmc.purpur.PurpurConfig.laggingThreshold; // Purpur
- tickSection = curTime;
- }
- // Spigot end
-@@ -1284,7 +1302,13 @@ public abstract class MinecraftServer extends MinecraftServerBlockableEventLoop
- long tickProperStart = System.nanoTime(); // Gale - YAPFA - last tick time
- this.tickServer(this::haveTime);
- lastTickProperTime = (System.nanoTime() - tickProperStart) / 1000000L; // Gale - YAPFA - last tick time
-- this.setDelayedTasksMaxNextTickTime(Math.max(Util.getMillis() + 50L, this.nextTickTime)); // Gale - base thread pool
-+ // Purpur start - tps catchup
-+ if (org.purpurmc.purpur.PurpurConfig.tpsCatchup) {
-+ this.setDelayedTasksMaxNextTickTime(Math.max(Util.getMillis() + 50L, this.nextTickTime)); // Gale - base thread pool
-+ } else {
-+ this.setDelayedTasksMaxNextTickTime(this.nextTickTime = curTime / 1000000L + 50L); // Gale - base thread pool
-+ }
-+ // Purpur end - tps catchup
- this.waitUntilNextTick();
- this.isReady = true;
- JvmProfiler.INSTANCE.onServerTick(this.averageTickTime);
-@@ -1596,6 +1620,7 @@ public abstract class MinecraftServer extends MinecraftServerBlockableEventLoop
- tick_shouldKeepTicking = shouldKeepTicking;
- this.tickStep_doSchedulerHeartbeat();
- this.tickStep_tickFunctions();
-+ net.minecraft.network.FriendlyByteBuf.hasItemSerializeEvent = org.purpurmc.purpur.event.packet.NetworkItemSerializeEvent.getHandlerList().getRegisteredListeners().length > 0; // Purpur
- //Iterator iterator = this.getAllLevels().iterator(); // Paper - moved down
- this.tickStep_runProcessQueueTasks();
- for (final ServerLevel world : this.getAllLevelsArray()) { // Gale - base thread pool - optimize server levels
-@@ -1654,7 +1679,7 @@ public abstract class MinecraftServer extends MinecraftServerBlockableEventLoop
- long worldTime = world.getGameTime();
- final ClientboundSetTimePacket worldPacket = new ClientboundSetTimePacket(worldTime, dayTime, doDaylight);
- for (Player entityhuman : world.players()) {
-- if (!(entityhuman instanceof ServerPlayer) || (tickCount + entityhuman.getId()) % 20 != 0) {
-+ if (!(entityhuman instanceof ServerPlayer) || (!world.isForceTime() && (tickCount + entityhuman.getId()) % 20 != 0)) { // Purpur
- continue;
- }
- ServerPlayer entityplayer = (ServerPlayer) entityhuman;
-@@ -1679,6 +1704,7 @@ public abstract class MinecraftServer extends MinecraftServerBlockableEventLoop
- worldserver.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper
- net.minecraft.world.level.block.entity.HopperBlockEntity.skipHopperEvents = worldserver.paperConfig().hopper.disableMoveEvent || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper
- worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper
-+ worldserver.hasRidableMoveEvent = org.purpurmc.purpur.event.entity.RidableMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Purpur
- } // Gale - split tick steps
-
- /* Drop global time updates
-@@ -1882,7 +1908,7 @@ public abstract class MinecraftServer extends MinecraftServerBlockableEventLoop
-
- @DontObfuscate
- public String getServerModName() {
-- return "Gale"; // Gale - branding changes - Gale > // Paper - Paper > // Spigot - Spigot > // CraftBukkit - cb > vanilla!
-+ return org.purpurmc.purpur.PurpurConfig.serverModName; // Purpur - Purpur > // Gale - branding changes - Gale > // Paper - Paper > // Spigot - Spigot > // CraftBukkit - cb > vanilla!
- }
-
- public SystemReport fillSystemReport(SystemReport details) {
-@@ -2072,17 +2098,12 @@ public abstract class MinecraftServer extends MinecraftServerBlockableEventLoop
- }
-
- public net.kyori.adventure.text.Component getComponentMotd() {
-- net.kyori.adventure.text.Component component = cachedMotd;
-- if (this.motd != null && this.cachedMotd == null) {
-- component = cachedMotd = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(this.motd);
-- }
--
-- return component != null ? component : net.kyori.adventure.text.Component.empty();
-+ return this.cachedMotd; // Purpur
- }
-
- public void setMotd(String motd) {
- this.motd = motd;
-- this.cachedMotd = null; // Paper
-+ this.cachedMotd = motd == null ? net.kyori.adventure.text.Component.empty() : net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(motd); // Paper // Purpur
- }
-
- public boolean isStopped() {
-@@ -2745,6 +2766,15 @@ public abstract class MinecraftServer extends MinecraftServerBlockableEventLoop
- new com.google.common.util.concurrent.ThreadFactoryBuilder().setDaemon(true).setNameFormat("Async Chat Thread - #%d").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)).build()); // Paper
-
- public ChatDecorator getChatDecorator() {
-+ // Purpur start
-+ return this.chatDecorator;
-+ }
-+ public void setChatDecorator(ChatDecorator chatDecorator) {
-+ this.chatDecorator = chatDecorator;
-+ }
-+ private ChatDecorator chatDecorator = getPaperHardcodedChatDecorator();
-+ public ChatDecorator getPaperHardcodedChatDecorator() {
-+ // Purpur end
- // Paper start - moved to ChatPreviewProcessor
- return ChatDecorator.create((sender, commandSourceStack, message) -> {
- final io.papermc.paper.adventure.ChatDecorationProcessor processor = new io.papermc.paper.adventure.ChatDecorationProcessor(this, sender, commandSourceStack, message);
-diff --git a/src/main/java/net/minecraft/server/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java
-index c953f7f2f125985eeec9563a22f9188cc979cd36..b75788bd18db4cf23903c54a9ff69c6aabb0cfa3 100644
---- a/src/main/java/net/minecraft/server/PlayerAdvancements.java
-+++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java
-@@ -250,6 +250,7 @@ public class PlayerAdvancements {
- advancement.getRewards().grant(this.player);
- // Paper start - Add Adventure message to PlayerAdvancementDoneEvent
- if (message != null && this.player.level.getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) {
-+ if (org.purpurmc.purpur.PurpurConfig.advancementOnlyBroadcastToAffectedPlayer) this.player.sendMessage(message); else // Purpur
- this.playerList.broadcastSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message), false);
- // Paper end
- }
-diff --git a/src/main/java/net/minecraft/server/commands/EnchantCommand.java b/src/main/java/net/minecraft/server/commands/EnchantCommand.java
-index e639c0ec642910e66b1d68ae0b9208ef58d91fce..24c4ad919eeb9c5e15572ee32b0895c993ac6735 100644
---- a/src/main/java/net/minecraft/server/commands/EnchantCommand.java
-+++ b/src/main/java/net/minecraft/server/commands/EnchantCommand.java
-@@ -48,7 +48,7 @@ public class EnchantCommand {
-
- private static int enchant(CommandSourceStack source, Collection extends Entity> targets, Holder enchantment, int level) throws CommandSyntaxException {
- Enchantment enchantment2 = enchantment.value();
-- if (level > enchantment2.getMaxLevel()) {
-+ if (!org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchantCommand && level > enchantment2.getMaxLevel()) { // Purpur
- throw ERROR_LEVEL_TOO_HIGH.create(level, enchantment2.getMaxLevel());
- } else {
- int i = 0;
-@@ -58,7 +58,7 @@ public class EnchantCommand {
- LivingEntity livingEntity = (LivingEntity)entity;
- ItemStack itemStack = livingEntity.getMainHandItem();
- if (!itemStack.isEmpty()) {
-- if (enchantment2.canEnchant(itemStack) && EnchantmentHelper.isEnchantmentCompatible(EnchantmentHelper.getEnchantments(itemStack).keySet(), enchantment2)) {
-+ if ((enchantment2.canEnchant(itemStack) && EnchantmentHelper.isEnchantmentCompatible(EnchantmentHelper.getEnchantments(itemStack).keySet(), enchantment2)) || (org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchantCommand && !itemStack.hasEnchantment(enchantment2))) { // Purpur
- itemStack.enchant(enchantment2, level);
- ++i;
- } else if (targets.size() == 1) {
-diff --git a/src/main/java/net/minecraft/server/commands/GameModeCommand.java b/src/main/java/net/minecraft/server/commands/GameModeCommand.java
-index 27c0aaf123c3e945eb24e8a3892bd8ac42115733..85e1c1d6eb4472baa958b4f482791e8479dfcbf0 100644
---- a/src/main/java/net/minecraft/server/commands/GameModeCommand.java
-+++ b/src/main/java/net/minecraft/server/commands/GameModeCommand.java
-@@ -41,6 +41,18 @@ public class GameModeCommand {
- }
-
- private static int setMode(CommandContext context, Collection targets, GameType gameMode) {
-+ // Purpur start
-+ if (org.purpurmc.purpur.PurpurConfig.commandGamemodeRequiresPermission) {
-+ String gamemode = gameMode.getName();
-+ CommandSourceStack sender = context.getSource();
-+ if (!sender.testPermission(2, "minecraft.command.gamemode." + gamemode)) {
-+ return 0;
-+ }
-+ if (sender.getEntity() instanceof ServerPlayer player && (targets.size() > 1 || !targets.contains(player)) && !sender.testPermission(2, "minecraft.command.gamemode." + gamemode + ".other")) {
-+ return 0;
-+ }
-+ }
-+ // Purpur end
- int i = 0;
-
- for(ServerPlayer serverPlayer : targets) {
-diff --git a/src/main/java/net/minecraft/server/commands/GiveCommand.java b/src/main/java/net/minecraft/server/commands/GiveCommand.java
-index ee7d29d85c8b024c9b23cba8ecd4192aa7e8aa7b..7a44bac6e66bc5f5fe14a45a5e7c78c940fb1efb 100644
---- a/src/main/java/net/minecraft/server/commands/GiveCommand.java
-+++ b/src/main/java/net/minecraft/server/commands/GiveCommand.java
-@@ -58,6 +58,7 @@ public class GiveCommand {
- boolean flag = entityplayer.getInventory().add(itemstack);
- ItemEntity entityitem;
-
-+ if (org.purpurmc.purpur.PurpurConfig.disableGiveCommandDrops) continue; // Purpur - add config option for toggling give command dropping
- if (flag && itemstack.isEmpty()) {
- itemstack.setCount(1);
- entityitem = entityplayer.drop(itemstack, false, false, false); // SPIGOT-2942: Add boolean to call event
-diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
-index 1704f5f29c4e36580101e760ee003d608ca3550e..acc4e7e0d27ea49d456ea24586e58b02ca95896b 100644
---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
-+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
-@@ -101,6 +101,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
- return;
- }
- // Paper start - Use TerminalConsoleAppender
-+ if (DedicatedServer.this.gui == null || System.console() != null) // Purpur - has no GUI or has console (did not double-click)
- new com.destroystokyo.paper.console.PaperConsole(DedicatedServer.this).start();
- /*
- jline.console.ConsoleReader bufferedreader = reader;
-@@ -225,6 +226,15 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
- io.papermc.paper.command.PaperCommands.registerCommands(this);
- GaleCommands.registerCommands(this); // Gale - Gale commands - register commands
- com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics();
-+ // Purpur start
-+ 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
- com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // load version history now
- io.papermc.paper.brigadier.PaperBrigadierProviderImpl.INSTANCE.getClass(); // init PaperBrigadierProvider
- // Paper end
-@@ -295,6 +305,30 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
- DedicatedServer.LOGGER.warn("Perhaps a server is already running on that port?");
- return false;
- }
-+ // Purpur start
-+ if (org.purpurmc.purpur.PurpurConfig.useUPnP) {
-+ LOGGER.info("[UPnP] Attempting to start UPnP port forwarding service...");
-+ if (dev.omega24.upnp4j.UPnP4J.isUPnPAvailable()) {
-+ if (dev.omega24.upnp4j.UPnP4J.isOpen(this.getPort(), dev.omega24.upnp4j.util.Protocol.TCP)) {
-+ this.upnp = false;
-+ LOGGER.info("[UPnP] Port {} is already open", this.getPort());
-+ } else if (dev.omega24.upnp4j.UPnP4J.open(this.getPort(), dev.omega24.upnp4j.util.Protocol.TCP)) {
-+ this.upnp = true;
-+ LOGGER.info("[UPnP] Successfully opened port {}", this.getPort());
-+ } else {
-+ this.upnp = false;
-+ LOGGER.info("[UPnP] Failed to open port {}", this.getPort());
-+ }
-+
-+ if (upnp) {
-+ LOGGER.info("[UPnP] {}:{}", dev.omega24.upnp4j.UPnP4J.getExternalIP(), this.getPort());
-+ }
-+ } else {
-+ this.upnp = false;
-+ LOGGER.error("[UPnP] Service is unavailable");
-+ }
-+ }
-+ // Purpur end
-
- // CraftBukkit start
- // this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage)); // Spigot - moved up
-@@ -368,6 +402,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
- }
-
- if (LeafConfig.enableAsyncMobSpawning) mobSpawnExecutor.start(); // Pufferfish
-+ org.purpurmc.purpur.task.BossBarTask.startAll(); // Purpur
-+ org.purpurmc.purpur.task.BeehiveTask.instance().register(); // Purpur
- return true;
- }
- }
-diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java
-index 37c647ec9eff5fb2729dc83efed7920bc0227f34..63df083f2f5a866e47d04989beb1b04da5f9a608 100644
---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java
-+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java
-@@ -58,6 +58,7 @@ public class DedicatedServerProperties extends Settings finalizers = Lists.newArrayList();
- final AtomicBoolean isClosing = new AtomicBoolean();
-+ // Purpur start
-+ private final CommandHistory history = new CommandHistory();
-+ private String currentCommand = "";
-+ private int historyIndex = 0;
-+ // Purpur end
-
- public static MinecraftServerGui showFrameFor(final DedicatedServer server) {
- try {
-@@ -51,7 +56,7 @@ public class MinecraftServerGui extends JComponent {
- ;
- }
-
-- final JFrame jframe = new JFrame("Minecraft server");
-+ final JFrame jframe = new JFrame("Purpur Minecraft server"); // Purpur
- final MinecraftServerGui servergui = new MinecraftServerGui(server);
-
- jframe.setDefaultCloseOperation(2);
-@@ -59,7 +64,7 @@ public class MinecraftServerGui extends JComponent {
- jframe.pack();
- jframe.setLocationRelativeTo((Component) null);
- jframe.setVisible(true);
-- jframe.setName("Minecraft server"); // Paper
-+ jframe.setName("Purpur Minecraft server"); // Paper // Purpur
-
- // Paper start - Add logo as frame image
- try {
-@@ -71,7 +76,7 @@ public class MinecraftServerGui extends JComponent {
- jframe.addWindowListener(new WindowAdapter() {
- public void windowClosing(WindowEvent windowevent) {
- if (!servergui.isClosing.getAndSet(true)) {
-- jframe.setTitle("Minecraft server - shutting down!");
-+ jframe.setTitle("Purpur Minecraft server - shutting down!"); // Purpur
- server.halt(true);
- servergui.runFinalizers();
- }
-@@ -125,7 +130,7 @@ public class MinecraftServerGui extends JComponent {
-
- private JComponent buildChatPanel() {
- JPanel jpanel = new JPanel(new BorderLayout());
-- JTextArea jtextarea = new JTextArea();
-+ org.purpurmc.purpur.gui.JColorTextPane jtextarea = new org.purpurmc.purpur.gui.JColorTextPane(); // Purpur
- JScrollPane jscrollpane = new JScrollPane(jtextarea, 22, 30);
-
- jtextarea.setEditable(false);
-@@ -137,10 +142,43 @@ public class MinecraftServerGui extends JComponent {
-
- if (!s.isEmpty()) {
- this.server.handleConsoleInput(s, this.server.createCommandSourceStack());
-+ // Purpur start
-+ history.add(s);
-+ historyIndex = -1;
-+ // Purpur end
- }
-
- jtextfield.setText("");
- });
-+ // Purpur start
-+ jtextfield.getInputMap().put(javax.swing.KeyStroke.getKeyStroke("UP"), "up");
-+ jtextfield.getInputMap().put(javax.swing.KeyStroke.getKeyStroke("DOWN"), "down");
-+ jtextfield.getActionMap().put("up", new javax.swing.AbstractAction() {
-+ @Override
-+ public void actionPerformed(java.awt.event.ActionEvent actionEvent) {
-+ if (historyIndex < 0) {
-+ currentCommand = jtextfield.getText();
-+ }
-+ if (historyIndex < history.size() - 1) {
-+ jtextfield.setText(history.get(historyIndex));
-+ }
-+ }
-+ });
-+ jtextfield.getActionMap().put("down", new javax.swing.AbstractAction() {
-+ @Override
-+ public void actionPerformed(java.awt.event.ActionEvent actionEvent) {
-+ if (historyIndex >= 0) {
-+ if (historyIndex == 0) {
-+ --historyIndex;
-+ jtextfield.setText(currentCommand);
-+ } else {
-+ --historyIndex;
-+ jtextfield.setText(history.get(historyIndex));
-+ }
-+ }
-+ }
-+ });
-+ // Purpur end
- jtextarea.addFocusListener(new FocusAdapter() {
- public void focusGained(FocusEvent focusevent) {}
- });
-@@ -176,7 +214,7 @@ public class MinecraftServerGui extends JComponent {
- }
-
- private static final java.util.regex.Pattern ANSI = java.util.regex.Pattern.compile("\\x1B\\[([0-9]{1,2}(;[0-9]{1,2})*)?[m|K]"); // CraftBukkit
-- public void print(JTextArea textArea, JScrollPane scrollPane, String message) {
-+ public void print(org.purpurmc.purpur.gui.JColorTextPane textArea, JScrollPane scrollPane, String message) { // Purpur
- if (!SwingUtilities.isEventDispatchThread()) {
- SwingUtilities.invokeLater(() -> {
- this.print(textArea, scrollPane, message);
-@@ -190,11 +228,14 @@ public class MinecraftServerGui extends JComponent {
- flag = (double) jscrollbar.getValue() + jscrollbar.getSize().getHeight() + (double) (MinecraftServerGui.MONOSPACED.getSize() * 4) > (double) jscrollbar.getMaximum();
- }
-
-+ /* // Purpur
- try {
- document.insertString(document.getLength(), MinecraftServerGui.ANSI.matcher(message).replaceAll(""), (AttributeSet) null); // CraftBukkit
- } catch (BadLocationException badlocationexception) {
- ;
- }
-+ */ // Purpur
-+ textArea.append(message); // Purpur
-
- if (flag) {
- jscrollbar.setValue(Integer.MAX_VALUE);
-@@ -202,4 +243,16 @@ public class MinecraftServerGui extends JComponent {
-
- }
- }
-+
-+ // Purpur start
-+ public static class CommandHistory extends java.util.LinkedList {
-+ @Override
-+ public boolean add(String command) {
-+ if (size() > 1000) {
-+ remove();
-+ }
-+ return super.offerFirst(command);
-+ }
-+ }
-+ // Purpur end
- }
-diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index d7bab50d51896c6aeb6015d5c2eb130c88338ede..d698d7534df55a53142f0a9ac97b07749f34a905 100644
---- a/src/main/java/net/minecraft/server/level/ChunkMap.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -999,7 +999,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- return this.anyPlayerCloseEnoughForSpawning(this.getUpdatingChunkIfPresent(chunkcoordintpair.toLong()), chunkcoordintpair, reducedRange);
- }
-
-- final boolean anyPlayerCloseEnoughForSpawning(ChunkHolder playerchunk, ChunkPos chunkcoordintpair, boolean reducedRange) {
-+ public final boolean anyPlayerCloseEnoughForSpawning(ChunkHolder playerchunk, ChunkPos chunkcoordintpair, boolean reducedRange) { // Purpur - package -> public
- // this function is so hot that removing the map lookup call can have an order of magnitude impact on its performance
- // tested and confirmed via System.nanoTime()
- com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInRange = reducedRange ? playerchunk.playersInMobSpawnRange : playerchunk.playersInChunkTickRange;
-diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java
-index b79e302cf4e0eae858e2322b5b260282b1939963..fc631d7834cdba20689aa7c3c649eed5f16cb4ed 100644
---- a/src/main/java/net/minecraft/server/level/ServerEntity.java
-+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
-@@ -72,7 +72,7 @@ public class ServerEntity {
- @Nullable
- private List> trackedDataValues;
- // CraftBukkit start
-- final Set trackedPlayers; // Paper - private -> package
-+ public final Set trackedPlayers; // Paper - private -> package // Purpur - package -> public
-
- public ServerEntity(ServerLevel worldserver, Entity entity, int i, boolean flag, Consumer> consumer, Set trackedPlayers) {
- this.trackedPlayers = trackedPlayers;
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index ccedeb88e4cbf0e1088412e72ee317c2182cb277..393579dda853b6881ec9817da2a74cbda4d4120e 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -216,6 +216,8 @@ public class ServerLevel extends Level implements WorldGenLevel {
- private final StructureManager structureManager;
- private final StructureCheck structureCheck;
- private final boolean tickTime;
-+ private double preciseTime; // Purpur
-+ private boolean forceTime; // Purpur
- public long lastMidTickExecuteFailure; // Paper - execute chunk tasks mid tick
-
- // CraftBukkit start
-@@ -224,6 +226,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
- public boolean hasPhysicsEvent = true; // Paper
- public boolean hasEntityMoveEvent = false; // Paper
- private final alternate.current.wire.WireHandler wireHandler = new alternate.current.wire.WireHandler(this); // Paper - optimize redstone (Alternate Current)
-+ public boolean hasRidableMoveEvent = false; // Purpur
- public static Throwable getAddToWorldStackTrace(Entity entity) {
- final Throwable thr = new Throwable(entity + " Added to world at " + new java.util.Date());
- io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(thr);
-@@ -548,7 +551,24 @@ public class ServerLevel extends Level implements WorldGenLevel {
- this.dragonParts = new Int2ObjectOpenHashMap();
- this.tickTime = flag1;
- this.server = minecraftserver;
-- this.customSpawners = list;
-+ // Purpur start - enable/disable MobSpawners per world
-+ this.customSpawners = Lists.newArrayList();
-+ if (purpurConfig.phantomSpawning) {
-+ customSpawners.add(new net.minecraft.world.level.levelgen.PhantomSpawner());
-+ }
-+ if (purpurConfig.patrolSpawning) {
-+ customSpawners.add(new net.minecraft.world.level.levelgen.PatrolSpawner());
-+ }
-+ if (purpurConfig.catSpawning) {
-+ customSpawners.add(new net.minecraft.world.entity.npc.CatSpawner());
-+ }
-+ if (purpurConfig.villageSiegeSpawning) {
-+ customSpawners.add(new net.minecraft.world.entity.ai.village.VillageSiege());
-+ }
-+ if (purpurConfig.villagerTraderSpawning) {
-+ customSpawners.add(new net.minecraft.world.entity.npc.WanderingTraderSpawner(iworlddataserver));
-+ }
-+ // Purpur end
- this.serverLevelData = iworlddataserver;
- ChunkGenerator chunkgenerator = worlddimension.generator();
- // CraftBukkit start
-@@ -611,6 +631,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
-
- this.chunkTaskScheduler = new io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler(this, io.papermc.paper.chunk.system.scheduling.ChunkTaskScheduler.workerThreads); // Paper - rewrite chunk system
- this.entityLookup = new io.papermc.paper.chunk.system.entity.EntityLookup(this, new EntityCallbacks()); // Paper - rewrite chunk system
-+ this.preciseTime = this.serverLevelData.getDayTime(); // Purpur
- }
-
- public void setWeatherParameters(int clearDuration, int rainDuration, boolean raining, boolean thundering) {
-@@ -764,7 +785,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
- int i = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE);
- long j;
-
-- if (this.sleepStatus.areEnoughSleeping(i) && this.sleepStatus.areEnoughDeepSleeping(i, this.players)) {
-+ if (this.purpurConfig.playersSkipNight && this.sleepStatus.areEnoughSleeping(i) && this.sleepStatus.areEnoughDeepSleeping(i, this.players)) {
- // CraftBukkit start
- j = this.levelData.getDayTime() + 24000L;
- TimeSkipEvent event = new TimeSkipEvent(this.getWorld(), TimeSkipEvent.SkipReason.NIGHT_SKIP, (j - j % 24000L) - this.getDayTime());
-@@ -996,6 +1017,13 @@ public class ServerLevel extends Level implements WorldGenLevel {
- this.serverLevelData.setGameTime(i);
- this.serverLevelData.getScheduledEvents().tick(this.server, i);
- if (this.levelData.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) {
-+ // Purpur start
-+ int incrementTicks = isDay() ? this.purpurConfig.daytimeTicks : this.purpurConfig.nighttimeTicks;
-+ if (incrementTicks != 12000) {
-+ this.preciseTime += 12000 / (double) incrementTicks;
-+ this.setDayTime(this.preciseTime);
-+ } else
-+ // Purpur end
- this.setDayTime(this.levelData.getDayTime() + 1L);
- }
-
-@@ -1004,8 +1032,22 @@ public class ServerLevel extends Level implements WorldGenLevel {
-
- public void setDayTime(long timeOfDay) {
- this.serverLevelData.setDayTime(timeOfDay);
-+ // Purpur start
-+ this.preciseTime = timeOfDay;
-+ this.forceTime = false;
-+ }
-+ public void setDayTime(double i) {
-+ this.serverLevelData.setDayTime((long) i);
-+ this.forceTime = true;
-+ // Purpur end
- }
-
-+ // Purpur start
-+ public boolean isForceTime() {
-+ return this.forceTime;
-+ }
-+ // Purpur end
-+
- public void tickCustomSpawners(boolean spawnMonsters, boolean spawnAnimals) {
- Iterator iterator = this.customSpawners.iterator();
-
-@@ -1029,6 +1071,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
- }
- // Paper start - optimise random block ticking
- private final BlockPos.MutableBlockPos chunkTickMutablePosition = new BlockPos.MutableBlockPos();
-+ public final io.papermc.paper.util.math.ThreadUnsafeRandom randomTickRandom = new io.papermc.paper.util.math.ThreadUnsafeRandom(this.random.nextLong()); public net.minecraft.util.RandomSource getThreadUnsafeRandom() { return this.randomTickRandom; } // Pufferfish - moved to super // Purpur - dont break ABI
- // Paper end
-
- private int currentIceAndSnowTick = 0; protected void resetIceAndSnowTick() { this.currentIceAndSnowTick = this.randomTickRandom.nextInt(16); } // Gale - Airplane - optimize random calls in chunk ticking
-@@ -1047,10 +1090,18 @@ public class ServerLevel extends Level implements WorldGenLevel {
- boolean flag1 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.getEffectiveDifficulty() * this.paperConfig().entities.spawning.skeletonHorseThunderSpawnChance.or(0.01D) && !this.getBlockState(blockposition.below()).is(Blocks.LIGHTNING_ROD); // Paper
-
- if (flag1) {
-- SkeletonHorse entityhorseskeleton = (SkeletonHorse) EntityType.SKELETON_HORSE.create(this);
-+ // Purpur start
-+ net.minecraft.world.entity.animal.horse.AbstractHorse entityhorseskeleton;
-+ if (purpurConfig.zombieHorseSpawnChance > 0D && random.nextDouble() <= purpurConfig.zombieHorseSpawnChance) {
-+ entityhorseskeleton = EntityType.ZOMBIE_HORSE.create(this);
-+ } else {
-+ entityhorseskeleton = EntityType.SKELETON_HORSE.create(this);
-+ if (entityhorseskeleton != null) ((SkeletonHorse) entityhorseskeleton).setTrap(true);
-+ }
-+ // Purpur end
-
- if (entityhorseskeleton != null) {
-- entityhorseskeleton.setTrap(true);
-+ //entityhorseskeleton.setTrap(true); // Purpur - moved up
- entityhorseskeleton.setAge(0);
- entityhorseskeleton.setPos((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ());
- this.addFreshEntity(entityhorseskeleton, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.LIGHTNING); // CraftBukkit
-@@ -1159,7 +1210,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
- return holder.is(PoiTypes.LIGHTNING_ROD);
- }, (blockposition1) -> {
- return blockposition1.getY() == this.getHeight(Heightmap.Types.WORLD_SURFACE, blockposition1.getX(), blockposition1.getZ()) - 1;
-- }, pos, 128, PoiManager.Occupancy.ANY);
-+ }, pos, org.purpurmc.purpur.PurpurConfig.lightningRodRange, PoiManager.Occupancy.ANY);
-
- return optional.map((blockposition1) -> {
- return blockposition1.above(1);
-@@ -1208,11 +1259,27 @@ public class ServerLevel extends Level implements WorldGenLevel {
- if (this.canSleepThroughNights()) {
- if (!this.getServer().isSingleplayer() || this.getServer().isPublished()) {
- int i = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE);
-- MutableComponent ichatmutablecomponent;
-+ Component ichatmutablecomponent;
-
- if (this.sleepStatus.areEnoughSleeping(i)) {
-+ // Purpur start
-+ if (org.purpurmc.purpur.PurpurConfig.sleepSkippingNight.isBlank()) {
-+ return;
-+ }
-+ if (!org.purpurmc.purpur.PurpurConfig.sleepSkippingNight.equalsIgnoreCase("default")) {
-+ ichatmutablecomponent = io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.sleepSkippingNight));
-+ } else
- ichatmutablecomponent = Component.translatable("sleep.skipping_night");
- } else {
-+ if (org.purpurmc.purpur.PurpurConfig.sleepingPlayersPercent.isBlank()) {
-+ return;
-+ }
-+ if (!org.purpurmc.purpur.PurpurConfig.sleepingPlayersPercent.equalsIgnoreCase("default")) {
-+ ichatmutablecomponent = io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.sleepingPlayersPercent,
-+ net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.parsed("count", Integer.toString(this.sleepStatus.amountSleeping())),
-+ net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.parsed("total", Integer.toString(this.sleepStatus.sleepersNeeded(i)))));
-+ } else
-+ // Purpur end
- ichatmutablecomponent = Component.translatable("sleep.players_sleeping", this.sleepStatus.amountSleeping(), this.sleepStatus.sleepersNeeded(i));
- }
-
-@@ -1351,6 +1418,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
-
- private void resetWeatherCycle() {
- // CraftBukkit start
-+ if (this.purpurConfig.rainStopsAfterSleep) // Purpur
- this.serverLevelData.setRaining(false, org.bukkit.event.weather.WeatherChangeEvent.Cause.SLEEP); // Paper - when passing the night
- // If we stop due to everyone sleeping we should reset the weather duration to some other random value.
- // Not that everyone ever manages to get the whole server to sleep at the same time....
-@@ -1358,6 +1426,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
- this.serverLevelData.setRainTime(0);
- }
- // CraftBukkit end
-+ if (this.purpurConfig.thunderStopsAfterSleep) // Purpur
- this.serverLevelData.setThundering(false, org.bukkit.event.weather.ThunderChangeEvent.Cause.SLEEP); // Paper - when passing the night
- // CraftBukkit start
- // If we stop due to everyone sleeping we should reset the weather duration to some other random value.
-@@ -2831,7 +2900,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
- // Spigot Start
- if (entity.getBukkitEntity() instanceof org.bukkit.inventory.InventoryHolder && (!(entity instanceof ServerPlayer) || entity.getRemovalReason() != Entity.RemovalReason.KILLED)) { // SPIGOT-6876: closeInventory clears death message
- // Paper start
-- if (entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) {
-+ if (!entity.level.purpurConfig.playerVoidTrading && entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) { // Purpur
- merchant.getTrader().closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED);
- }
- // Paper end
-diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-index 4ea43001ecbaa37b60b42b1a90b14f2ffac62f36..97f29244a4e7cf4edb346b9f0d8d61f320fe9ee0 100644
---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
-+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-@@ -278,6 +278,11 @@ public class ServerPlayer extends Player {
- public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper
- public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper
- public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - there are a lot of changes to do if we change all methods leading to the event
-+ public boolean purpurClient = false; // Purpur
-+ public boolean acceptingResourcePack = false; // Purpur
-+ private boolean ramBar = false; // Purpur
-+ private boolean tpsBar = false; // Purpur
-+ private boolean compassBar = false; // Purpur
-
- public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile) {
- super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile);
-@@ -377,6 +382,7 @@ public class ServerPlayer extends Player {
- this.bukkitPickUpLoot = true;
- this.maxHealthCache = this.getMaxHealth();
- this.cachedSingleMobDistanceMap = new com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper
-+ this.spawnInvulnerableTime = world.purpurConfig.playerSpawnInvulnerableTicks; // Purpur
- }
-
- // Yes, this doesn't match Vanilla, but it's the best we can do for now.
-@@ -516,6 +522,9 @@ public class ServerPlayer extends Player {
- }
- }
-
-+ if (nbt.contains("Purpur.RamBar")) { this.ramBar = nbt.getBoolean("Purpur.RamBar"); } // Purpur
-+ if (nbt.contains("Purpur.TPSBar")) { this.tpsBar = nbt.getBoolean("Purpur.TPSBar"); } // Purpur
-+ if (nbt.contains("Purpur.CompassBar")) { this.compassBar = nbt.getBoolean("Purpur.CompassBar"); } // Purpur
- }
-
- @Override
-@@ -582,6 +591,9 @@ public class ServerPlayer extends Player {
- }
- this.getBukkitEntity().setExtraData(nbt); // CraftBukkit
-
-+ nbt.putBoolean("Purpur.RamBar", this.ramBar); // Purpur
-+ nbt.putBoolean("Purpur.TPSBar", this.tpsBar); // Purpur
-+ nbt.putBoolean("Purpur.CompassBar", this.compassBar); // Purpur
- }
-
- // CraftBukkit start - World fallback code, either respawn location or global spawn
-@@ -710,6 +722,15 @@ public class ServerPlayer extends Player {
- this.trackStartFallingPosition();
- this.trackEnteredOrExitedLavaOnVehicle();
- this.advancements.flushDirty(this);
-+
-+ // Purpur start
-+ 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
- }
-
- public void doTick() {
-@@ -948,6 +969,7 @@ public class ServerPlayer extends Player {
- }));
- Team scoreboardteambase = this.getTeam();
-
-+ if (org.purpurmc.purpur.PurpurConfig.deathMessageOnlyBroadcastToAffectedPlayer) this.sendSystemMessage(ichatbasecomponent); else // Purpur
- if (scoreboardteambase != null && scoreboardteambase.getDeathMessageVisibility() != Team.Visibility.ALWAYS) {
- if (scoreboardteambase.getDeathMessageVisibility() == Team.Visibility.HIDE_FOR_OTHER_TEAMS) {
- this.server.getPlayerList().broadcastSystemToTeam(this, ichatbasecomponent);
-@@ -1049,14 +1071,30 @@ public class ServerPlayer extends Player {
-
- }
-
-+ // Purpur start
-+ public boolean isSpawnInvulnerable() {
-+ return spawnInvulnerableTime > 0 || frozen;
-+ }
-+ // Purpur end
-+
- @Override
- public boolean hurt(DamageSource source, float amount) {
- if (this.isInvulnerableTo(source)) {
- return false;
- } else {
-+ // Purpur start
-+ if (source.is(DamageTypeTags.IS_FALL)) { // Purpur
-+ if (getRootVehicle() instanceof net.minecraft.world.entity.vehicle.AbstractMinecart && level.purpurConfig.minecartControllable && !level.purpurConfig.minecartControllableFallDamage) {
-+ return false;
-+ }
-+ if (getRootVehicle() instanceof net.minecraft.world.entity.vehicle.Boat && !level.purpurConfig.boatsDoFallDamage) {
-+ return false;
-+ }
-+ }
-+ // Purpur end
- boolean flag = this.server.isDedicatedServer() && this.isPvpAllowed() && source.is(DamageTypeTags.IS_FALL);
-
-- if (!flag && this.spawnInvulnerableTime > 0 && !source.is(DamageTypeTags.BYPASSES_INVULNERABILITY)) {
-+ if (!flag && isSpawnInvulnerable() && !source.is(DamageTypeTags.BYPASSES_INVULNERABILITY)) { // Purpur
- return false;
- } else {
- Entity entity = source.getEntity();
-@@ -1197,6 +1235,7 @@ public class ServerPlayer extends Player {
- playerlist.sendPlayerPermissionLevel(this);
- worldserver1.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION);
- this.unsetRemoved();
-+ this.portalPos = io.papermc.paper.util.MCUtil.toBlockPosition(exit); // Purpur
-
- // CraftBukkit end
- this.setLevel(worldserver);
-@@ -1232,6 +1271,7 @@ public class ServerPlayer extends Player {
- }
- // Paper end
-
-+ this.spawnInvulnerableTime = worldserver.purpurConfig.playerSpawnInvulnerableTicks; // Purpur
- return this;
- }
- }
-@@ -1353,7 +1393,7 @@ public class ServerPlayer extends Player {
- return entitymonster.isPreventingPlayerRest(this);
- });
-
-- if (!list.isEmpty()) {
-+ if (!this.level.purpurConfig.playerSleepNearMonsters && !list.isEmpty()) { // Purpur
- return Either.left(Player.BedSleepingProblem.NOT_SAFE);
- }
- }
-@@ -1489,6 +1529,7 @@ public class ServerPlayer extends Player {
-
- @Override
- public void openTextEdit(SignBlockEntity sign) {
-+ if (level.purpurConfig.signAllowColors) this.connection.send(sign.getTranslatedUpdatePacket(textFilteringEnabled)); // Purpur
- sign.setAllowedPlayerEditor(this.getUUID());
- this.connection.send(new ClientboundBlockUpdatePacket(this.level, sign.getBlockPos()));
- this.connection.send(new ClientboundOpenSignEditorPacket(sign.getBlockPos()));
-@@ -1725,6 +1766,26 @@ public class ServerPlayer extends Player {
- this.lastSentExp = -1; // CraftBukkit - Added to reset
- }
-
-+ // Purpur start
-+ 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
-+
- @Override
- public void displayClientMessage(Component message, boolean overlay) {
- this.sendSystemMessage(message, overlay);
-@@ -2030,6 +2091,7 @@ public class ServerPlayer extends Player {
- }
-
- public void sendTexturePack(String url, String hash, boolean required, @Nullable Component resourcePackPrompt) {
-+ this.acceptingResourcePack = true; // Purpur
- this.connection.send(new ClientboundResourcePackPacket(url, hash, required, resourcePackPrompt));
- }
-
-@@ -2044,8 +2106,63 @@ public class ServerPlayer extends Player {
-
- public void resetLastActionTime() {
- this.lastActionTime = Util.getMillis();
-+ this.setAfk(false); // Purpur
- }
-
-+ // Purpur Start
-+ 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()) {
-+ server.getPlayerList().broadcastMiniMessage(String.format(msg, this.getGameProfile().getName()), 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);
-+ }
-+ }
-+
-+ ((ServerLevel) this.level).updateSleepingPlayerList();
-+ }
-+
-+ @Override
-+ public boolean isAfk() {
-+ return this.isAfk;
-+ }
-+
-+ @Override
-+ public boolean canBeCollidedWith() {
-+ return !this.isAfk() && super.canBeCollidedWith();
-+ }
-+ // Purpur End
-+
- public ServerStatsCounter getStats() {
- return this.stats;
- }
-@@ -2517,8 +2634,16 @@ public class ServerPlayer extends Player {
-
- @Override
- public boolean isImmobile() {
-- return super.isImmobile() || (this.connection != null && this.connection.isDisconnected()); // Paper
-+ return super.isImmobile() || frozen || (this.connection != null && this.connection.isDisconnected()); // Paper // Purpur
-+ }
-+
-+ // Purpur start
-+ private boolean frozen = false;
-+
-+ public void setFrozen(boolean frozen) {
-+ this.frozen = frozen;
- }
-+ // Purpur end
-
- @Override
- public Scoreboard getScoreboard() {
-@@ -2567,4 +2692,50 @@ public class ServerPlayer extends Player {
- return (CraftPlayer) super.getBukkitEntity();
- }
- // CraftBukkit end
-+
-+ // Purpur start
-+ public void teleport(Location to) {
-+ this.ejectPassengers();
-+ this.stopRiding(true);
-+
-+ if (this.isSleeping()) {
-+ this.stopSleepInBed(true, false);
-+ }
-+
-+ if (this.containerMenu != this.inventoryMenu) {
-+ this.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.TELEPORT);
-+ }
-+
-+ ServerLevel toLevel = ((CraftWorld) to.getWorld()).getHandle();
-+ if (this.level == toLevel) {
-+ this.connection.internalTeleport(to.getX(), to.getY(), to.getZ(), to.getYaw(), to.getPitch(), java.util.EnumSet.noneOf(net.minecraft.world.entity.RelativeMovement.class));
-+ } else {
-+ this.server.getPlayerList().respawn(this, toLevel, true, to, !toLevel.paperConfig().environment.disableTeleportationSuffocationCheck, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.DEATH);
-+ }
-+ }
-+
-+ public boolean ramBar() {
-+ return this.ramBar;
-+ }
-+
-+ public void ramBar(boolean ramBar) {
-+ this.ramBar = ramBar;
-+ }
-+
-+ public boolean tpsBar() {
-+ return this.tpsBar;
-+ }
-+
-+ public void tpsBar(boolean tpsBar) {
-+ this.tpsBar = tpsBar;
-+ }
-+
-+ public boolean compassBar() {
-+ return this.compassBar;
-+ }
-+
-+ public void compassBar(boolean compassBar) {
-+ this.compassBar = compassBar;
-+ }
-+ // Purpur end
- }
-diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
-index 4c8275728175ebc1fdc8d0e8d0ba8398cefa0e17..87c52aeabcafe40b3014fe23b7a807bf642fed91 100644
---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
-+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
-@@ -397,6 +397,7 @@ public class ServerPlayerGameMode {
- } else {capturedBlockEntity = true;} // Paper end
- return false;
- }
-+ if (this.player.level.purpurConfig.slabHalfBreak && this.player.isShiftKeyDown() && iblockdata.getBlock() instanceof net.minecraft.world.level.block.SlabBlock && ((net.minecraft.world.level.block.SlabBlock) iblockdata.getBlock()).halfBreak(iblockdata, pos, this.player)) return true; // Purpur
- }
- // CraftBukkit end
-
-@@ -427,7 +428,7 @@ public class ServerPlayerGameMode {
-
- ItemStack mainHandStack = null; // Paper
- boolean isCorrectTool = false; // Paper
-- if (this.isCreative()) {
-+ if (this.isCreative() || (this.level.purpurConfig.shulkerBoxAllowOversizedStacks && block instanceof net.minecraft.world.level.block.ShulkerBoxBlock)) { // Purpur
- // return true; // CraftBukkit
- } else {
- ItemStack itemstack = this.player.getMainHandItem();
-@@ -516,6 +517,7 @@ public class ServerPlayerGameMode {
- public InteractionHand interactHand;
- public ItemStack interactItemStack;
- public InteractionResult useItemOn(ServerPlayer player, Level world, ItemStack stack, InteractionHand hand, BlockHitResult hitResult) {
-+ if (shiftClickMended(stack)) return InteractionResult.SUCCESS; // Purpur
- BlockPos blockposition = hitResult.getBlockPos();
- BlockState iblockdata = world.getBlockState(blockposition);
- InteractionResult enuminteractionresult = InteractionResult.PASS;
-@@ -576,7 +578,7 @@ public class ServerPlayerGameMode {
- boolean flag1 = player.isSecondaryUseActive() && flag;
- ItemStack itemstack1 = stack.copy();
-
-- if (!flag1) {
-+ if (!flag1 || (player.level.purpurConfig.composterBulkProcess && iblockdata.is(Blocks.COMPOSTER))) { // Purpur
- enuminteractionresult = iblockdata.use(world, player, hand, hitResult);
-
- if (enuminteractionresult.consumesAction()) {
-@@ -612,4 +614,18 @@ public class ServerPlayerGameMode {
- public void setLevel(ServerLevel world) {
- this.level = world;
- }
-+
-+ // Purpur start
-+ public boolean shiftClickMended(ItemStack itemstack) {
-+ if (this.player.level.purpurConfig.shiftRightClickRepairsMendingPoints > 0 && this.player.isShiftKeyDown() && this.player.getBukkitEntity().hasPermission("purpur.mending_shift_click")) {
-+ int points = Math.min(this.player.totalExperience, this.player.level.purpurConfig.shiftRightClickRepairsMendingPoints);
-+ if (points > 0 && itemstack.isDamaged() && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.MENDING, itemstack) > 0) {
-+ this.player.giveExperiencePoints(-points);
-+ this.player.level.addFreshEntity(new net.minecraft.world.entity.ExperienceOrb(this.player.level, this.player.getX(), this.player.getY(), this.player.getZ(), points, org.bukkit.entity.ExperienceOrb.SpawnReason.UNKNOWN, this.player, this.player));
-+ return true;
-+ }
-+ }
-+ return false;
-+ }
-+ // Purpur end
- }
-diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-index f5f9b500d3c0221ef2eb5d2f1a5321d0ace0bb95..5ee7fee40fd930424456f4f454e94bc88951b0d2 100644
---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-@@ -348,6 +348,20 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
- private boolean justTeleported = false;
- private boolean hasMoved; // Spigot
-
-+ // Purpur start
-+ 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
-+
- public CraftPlayer getCraftPlayer() {
- return (this.player == null) ? null : (CraftPlayer) this.player.getBukkitEntity();
- }
-@@ -461,6 +475,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
- }
-
- if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) (this.server.getPlayerIdleTimeout() * 1000 * 60) && !this.player.wonGame) { // Paper - Prevent AFK kick while watching end credits.
-+ // Purpur start
-+ 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
- this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854
- this.disconnect(Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause
- }
-@@ -772,6 +792,8 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
- 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
-+
- // Skip the first time we do this
- if (true) { // Spigot - don't skip any move events
- Location oldTo = to.clone();
-@@ -848,6 +870,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
- if (packet.getId() == this.awaitingTeleport) {
- if (this.awaitingPositionFromClient == null) {
- this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause
-+ ServerGamePacketListenerImpl.LOGGER.warn("Disconnected on accept teleport packet. Was not expecting position data from client at this time"); // Purpur
- return;
- }
-
-@@ -1255,10 +1278,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
- int maxBookPageSize = io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.bookSize.pageMax;
- double multiplier = Math.max(0.3D, Math.min(1D, io.papermc.paper.configuration.GlobalConfiguration.get().itemValidation.bookSize.totalMultiplier));
- long byteAllowed = maxBookPageSize;
-+ ItemStack itemstack = this.player.getInventory().getItem(packet.getSlot()); // Purpur
- for (String testString : pageList) {
- int byteLength = testString.getBytes(java.nio.charset.StandardCharsets.UTF_8).length;
- if (byteLength > 256 * 4) {
- ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send a book with with a page too large!");
-+ org.purpurmc.purpur.event.player.PlayerBookTooLargeEvent event = new org.purpurmc.purpur.event.player.PlayerBookTooLargeEvent(player.getBukkitEntity(), itemstack.asBukkitCopy()); if (event.shouldKickPlayer()) // Purpur
- ScheduledServerThreadTaskQueues.add(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION), ScheduledServerThreadTaskQueues.KICK_FOR_BOOK_TOO_LARGE_PACKET_TASK_MAX_DELAY); // Paper - kick event cause // Gale - base thread pool
- return;
- }
-@@ -1282,6 +1307,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
-
- if (byteTotal > byteAllowed) {
- ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send too large of a book. Book Size: " + byteTotal + " - Allowed: "+ byteAllowed + " - Pages: " + pageList.size());
-+ org.purpurmc.purpur.event.player.PlayerBookTooLargeEvent event = new org.purpurmc.purpur.event.player.PlayerBookTooLargeEvent(player.getBukkitEntity(), itemstack.asBukkitCopy()); if (event.shouldKickPlayer()) // Purpur
- ScheduledServerThreadTaskQueues.add(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION), ScheduledServerThreadTaskQueues.KICK_FOR_BOOK_TOO_LARGE_PACKET_TASK_MAX_DELAY); // Paper - kick event cause // Gale - base thread pool
- return;
- }
-@@ -1335,13 +1361,16 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
- itemstack1.setTag(nbttagcompound.copy());
- }
-
-+ // Purpur start
-+ boolean hasPerm = getCraftPlayer().hasPermission("purpur.book.color.edit") || getCraftPlayer().hasPermission("purpur.book.color.sign");
- itemstack1.addTagElement("author", StringTag.valueOf(this.player.getName().getString()));
- if (this.player.isTextFilteringEnabled()) {
-- itemstack1.addTagElement("title", StringTag.valueOf(title.filteredOrEmpty()));
-+ itemstack1.addTagElement("title", StringTag.valueOf(color(title.filteredOrEmpty(), hasPerm)));
- } else {
-- itemstack1.addTagElement("filtered_title", StringTag.valueOf(title.filteredOrEmpty()));
-- itemstack1.addTagElement("title", StringTag.valueOf(title.raw()));
-+ itemstack1.addTagElement("filtered_title", StringTag.valueOf(color(title.filteredOrEmpty(), hasPerm)));
-+ itemstack1.addTagElement("title", StringTag.valueOf(color(title.raw(), hasPerm)));
- }
-+ // Purpur end
-
- this.updateBookPages(pages, (s) -> {
- return Component.Serializer.toJson(Component.literal(s));
-@@ -1353,10 +1382,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
- private void updateBookPages(List list, UnaryOperator unaryoperator, ItemStack itemstack, int slot, ItemStack handItem) { // CraftBukkit
- ListTag nbttaglist = new ListTag();
-
-+ // Purpur start
-+ boolean hasPerm = getCraftPlayer().hasPermission("purpur.book.color.edit");
- if (this.player.isTextFilteringEnabled()) {
-- Stream stream = list.stream().map((filteredtext) -> { // CraftBukkit - decompile error
-- return StringTag.valueOf((String) unaryoperator.apply(filteredtext.filteredOrEmpty()));
-+ Stream stream = list.stream().map(s -> color(s.filteredOrEmpty(), hasPerm, false)).map((s) -> { // CraftBukkit - decompile error
-+ return StringTag.valueOf((String) unaryoperator.apply(s));
- });
-+ // Purpur end
-
- Objects.requireNonNull(nbttaglist);
- stream.forEach(nbttaglist::add);
-@@ -1366,11 +1398,11 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
-
- for (int j = list.size(); i < j; ++i) {
- FilteredText filteredtext = (FilteredText) list.get(i);
-- String s = filteredtext.raw();
-+ String s = color(filteredtext.raw(), hasPerm, false); // Purpur
-
- nbttaglist.add(StringTag.valueOf((String) unaryoperator.apply(s)));
- if (filteredtext.isFiltered()) {
-- nbttagcompound.putString(String.valueOf(i), (String) unaryoperator.apply(filteredtext.filteredOrEmpty()));
-+ nbttagcompound.putString(String.valueOf(i), (String) unaryoperator.apply((String) color(filteredtext.filteredOrEmpty(), hasPerm, false))); // Purpur
- }
- }
-
-@@ -1383,6 +1415,16 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
- this.player.getInventory().setItem(slot, CraftEventFactory.handleEditBookEvent(player, slot, handItem, itemstack)); // CraftBukkit // Paper - Don't ignore result (see other callsite for handleEditBookEvent)
- }
-
-+ // Purpur start
-+ private String color(String str, boolean hasPerm) {
-+ return color(str, hasPerm, true);
-+ }
-+
-+ private String color(String str, boolean hasPerm, boolean parseHex) {
-+ return hasPerm ? org.bukkit.ChatColor.color(str, parseHex) : str;
-+ }
-+ // Purpur end
-+
- @Override
- public void handleEntityTagQuery(ServerboundEntityTagQuery packet) {
- PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel());
-@@ -1412,8 +1454,16 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
- @Override
- public void handleMovePlayer(ServerboundMovePlayerPacket packet) {
- PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel());
-- if (ServerGamePacketListenerImpl.containsInvalidValues(packet.getX(0.0D), packet.getY(0.0D), packet.getZ(0.0D), packet.getYRot(0.0F), packet.getXRot(0.0F))) {
-+ // Purpur start
-+ boolean invalidX = Double.isNaN(packet.getX(0.0D));
-+ boolean invalidY = Double.isNaN(packet.getY(0.0D));
-+ boolean invalidZ = Double.isNaN(packet.getZ(0.0D));
-+ boolean invalidYaw = !Floats.isFinite(packet.getYRot(0.0F));
-+ boolean invalidPitch = !Floats.isFinite(packet.getXRot(0.0F));
-+ if (invalidX || invalidY || invalidZ || invalidYaw || invalidPitch) {
- this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause
-+ ServerGamePacketListenerImpl.LOGGER.warn(String.format("Disconnected on move player packet. Invalid data: x=%b, y=%b, z=%b, yaw=%b, pitch=%b", invalidX, invalidY, invalidZ, invalidYaw, invalidPitch));
-+ // Purpur end
- } else {
- ServerLevel worldserver = this.player.getLevel();
-
-@@ -1579,7 +1629,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
-
- if (!this.player.isChangingDimension() && d11 > org.spigotmc.SpigotConfig.movedWronglyThreshold && !this.player.isSleeping() && !this.player.gameMode.isCreative() && this.player.gameMode.getGameModeForPlayer() != GameType.SPECTATOR) { // Spigot
- flag2 = true; // Paper - diff on change, this should be moved wrongly
-- ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!", this.player.getName().getString());
-+ ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!, ({})", this.player.getName().getString(), d11); // Purpur
- }
-
- this.player.absMoveTo(d0, d1, d2, f, f1);
-@@ -1630,6 +1680,8 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
- 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
-+
- // Skip the first time we do this
- if (from.getX() != Double.MAX_VALUE) {
- Location oldTo = to.clone();
-@@ -1669,6 +1721,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
- this.player.resetFallDistance();
- }
-
-+ // Purpur Start
-+ if (this.player.level.purpurConfig.dontRunWithScissors && this.player.isSprinting() && !(this.player.level.purpurConfig.ignoreScissorsInWater && this.player.isInWater()) && !(this.player.level.purpurConfig.ignoreScissorsInLava && this.player.isInLava()) && (isScissor(this.player.getItemInHand(InteractionHand.MAIN_HAND)) || isScissor(this.player.getItemInHand(InteractionHand.OFF_HAND))) && (int) (Math.random() * 10) == 0) {
-+ this.player.hurt(this.player.damageSources().magic(), (float) this.player.level.purpurConfig.scissorsRunningDamage);
-+ if (!org.purpurmc.purpur.PurpurConfig.dontRunWithScissors.isBlank()) this.player.sendActionBarMessage(org.purpurmc.purpur.PurpurConfig.dontRunWithScissors);
-+ }
-+ // Purpur End
-+
- this.player.checkMovementStatistics(this.player.getX() - d3, this.player.getY() - d4, this.player.getZ() - d5);
- this.lastGoodX = this.player.getX();
- this.lastGoodY = this.player.getY();
-@@ -1702,6 +1761,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
- }
- // Paper end - optimise out extra getCubes
-
-+ // Purpur start
-+ public boolean isScissor(ItemStack stack) {
-+ return stack.is(Items.SHEARS) && (stack.getTag() == null || stack.getTag().getInt("CustomModelData") == 0);
-+ }
-+ // Purpur end
-+
- private boolean isPlayerCollidingWithAnythingNew(LevelReader world, AABB box) {
- Iterable iterable = world.getCollisions(this.player, this.player.getBoundingBox().deflate(9.999999747378752E-6D));
- VoxelShape voxelshape = Shapes.create(box.deflate(9.999999747378752E-6D));
-@@ -2046,6 +2111,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
-
- boolean cancelled;
- if (movingobjectposition == null || movingobjectposition.getType() != HitResult.Type.BLOCK) {
-+ if (this.player.gameMode.shiftClickMended(itemstack)) return; // Purpur
- org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.RIGHT_CLICK_AIR, itemstack, enumhand);
- cancelled = event.useItemInHand() == Event.Result.DENY;
- } else {
-@@ -2096,12 +2162,21 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
- @Override
- public void handleResourcePackResponse(ServerboundResourcePackPacket packet) {
- PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel());
-+ // Purpur start
-+ if (player.level.purpurConfig.playerInvulnerableWhileAcceptingResourcePack && !this.player.acceptingResourcePack) {
-+ ServerGamePacketListenerImpl.LOGGER.info("Disconnecting {} due to resource pack packet exploitation attempt", this.player.getName());
-+ this.disconnect(Component.translatable("multiplayer.texturePrompt.failure.line1"), org.bukkit.event.player.PlayerKickEvent.Cause.RESOURCE_PACK_REJECTION); // "Server resource pack couldn't be applied"
-+ return;
-+ }
-+ // Purpur end
- if (packet.getAction() == ServerboundResourcePackPacket.Action.DECLINED && this.server.isResourcePackRequired()) {
- ServerGamePacketListenerImpl.LOGGER.info("Disconnecting {} due to resource pack rejection", this.player.getGameProfile().getName()); // Paper - Don't print component in resource pack rejection message
- this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect"), org.bukkit.event.player.PlayerKickEvent.Cause.RESOURCE_PACK_REJECTION); // Paper - add cause
- }
- // Paper start
- PlayerResourcePackStatusEvent.Status packStatus = PlayerResourcePackStatusEvent.Status.values()[packet.action.ordinal()];
-+ if (player.level.purpurConfig.playerInvulnerableWhileAcceptingResourcePack) player.setFrozen(packStatus == PlayerResourcePackStatusEvent.Status.ACCEPTED); // Purpur
-+ this.player.acceptingResourcePack = packStatus == PlayerResourcePackStatusEvent.Status.ACCEPTED; // Purpur
- player.getBukkitEntity().setResourcePackStatus(packStatus);
- this.cserver.getPluginManager().callEvent(new PlayerResourcePackStatusEvent(this.getCraftPlayer(), packStatus)); // CraftBukkit
- // Paper end
-@@ -2406,7 +2481,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
- do {
- instant1 = (Instant) this.lastChatTimeStamp.get();
- if (timestamp.isBefore(instant1)) {
-- return false;
-+ return !org.purpurmc.purpur.PurpurConfig.kickForOutOfOrderChat; // Purpur
- }
- } while (!this.lastChatTimeStamp.compareAndSet(instant1, timestamp));
-
-@@ -2832,6 +2907,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
- AABB axisalignedbb = entity.getBoundingBox();
-
- if (axisalignedbb.distanceToSqr(this.player.getEyePosition()) < ServerGamePacketListenerImpl.getMaxInteractionDistanceSquared(this.player.level)) { // Gale - make max interaction distance configurable
-+ if (entity instanceof Mob mob) mob.ticksSinceLastInteraction = 0; // Purpur
- packet.dispatch(new ServerboundInteractPacket.Handler() {
- private void performInteraction(InteractionHand enumhand, ServerGamePacketListenerImpl.EntityInteraction playerconnection_a, PlayerInteractEntityEvent event) { // CraftBukkit
- ItemStack itemstack = ServerGamePacketListenerImpl.this.player.getItemInHand(enumhand);
-@@ -2845,6 +2921,8 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
-
- ServerGamePacketListenerImpl.this.cserver.getPluginManager().callEvent(event);
-
-+ player.processClick(enumhand); // Purpur
-+
- // 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.getEntityData().resendPossiblyDesyncedEntity(player); // Paper - The entire mob gets deleted, so resend it.
-@@ -3396,6 +3474,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
- }
- }
- }
-+ // Purpur start
-+ if (org.purpurmc.purpur.PurpurConfig.fixNetworkSerializedItemsInCreative) {
-+ var tag = itemstack.getTagElement("Purpur.OriginalItem");
-+ if (tag != null) itemstack = ItemStack.of(tag);
-+ }
-+ // Purpur end
-
- boolean flag1 = packet.getSlotNum() >= 1 && packet.getSlotNum() <= 45;
- boolean flag2 = itemstack.isEmpty() || itemstack.getDamageValue() >= 0 && itemstack.getCount() <= 64 && !itemstack.isEmpty();
-@@ -3502,11 +3586,17 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
- for (int i = 0; i < signText.size(); ++i) {
- FilteredText filteredtext = (FilteredText) signText.get(i);
-
-- if (this.player.isTextFilteringEnabled()) {
-- lines.add(net.kyori.adventure.text.Component.text(SharedConstants.filterText(filteredtext.filteredOrEmpty()))); // Paper - adventure
-+ // Purpur start
-+ String line = SharedConstants.filterText(this.player.isTextFilteringEnabled() ? filteredtext.filteredOrEmpty() : filteredtext.raw());
-+ if (worldserver.purpurConfig.signAllowColors) {
-+ if (player.hasPermission("purpur.sign.color")) line = line.replaceAll("(?i)&([0-9a-fr])", "\u00a7$1");
-+ if (player.hasPermission("purpur.sign.style")) line = line.replaceAll("(?i)&([l-or])", "\u00a7$1");
-+ if (player.hasPermission("purpur.sign.magic")) line = line.replaceAll("(?i)&([kr])", "\u00a7$1");
-+ lines.add(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(line));
- } else {
-- lines.add(net.kyori.adventure.text.Component.text(SharedConstants.filterText(filteredtext.raw()))); // Paper - adventure
-+ lines.add(net.kyori.adventure.text.Component.text(line));
- }
-+ // Purpur end
- }
- SignChangeEvent event = new SignChangeEvent((org.bukkit.craftbukkit.block.CraftBlock) player.getWorld().getBlockAt(x, y, z), this.player.getBukkitEntity(), lines);
- this.cserver.getPluginManager().callEvent(event);
-@@ -3588,6 +3678,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
- private static final ResourceLocation CUSTOM_UNREGISTER = new ResourceLocation("unregister");
-
- private static final ResourceLocation MINECRAFT_BRAND = new ResourceLocation("brand"); // Paper - Brand support
-+ private static final ResourceLocation PURPUR_CLIENT = new ResourceLocation("purpur", "client"); // Purpur
-
- @Override
- public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {
-@@ -3612,6 +3703,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
- ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t unregister custom payload", ex);
- this.disconnect("Invalid payload UNREGISTER!", org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause
- }
-+ // Purpur start
-+ } else if (packet.identifier.equals(PURPUR_CLIENT)) {
-+ try {
-+ player.purpurClient = true;
-+ } catch (Exception ignore) {
-+ }
-+ // Purpur end
- } else {
- try {
- byte[] data = new byte[packet.data.readableBytes()];
-diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
-index 5b7c12db86be64433c65e31e3ecc0b444b0ddf48..5ed89ce9d2c29927f48c1f7f8f9288f39d68b56c 100644
---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
-+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
-@@ -225,6 +225,8 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
- return false;
- }
-
-+ if (true) return org.purpurmc.purpur.PurpurConfig.usernameValidCharactersPattern.matcher(in).matches(); // Purpur
-+
- for (int i = 0, len = in.length(); i < len; ++i) {
- char c = in.charAt(i);
-
-@@ -346,7 +348,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
- ServerLoginPacketListenerImpl.this.gameProfile = gameprofile;
- ServerLoginPacketListenerImpl.this.state = ServerLoginPacketListenerImpl.State.READY_TO_ACCEPT;
- } else {
-- ServerLoginPacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.unverified_username"));
-+ ServerLoginPacketListenerImpl.this.disconnect(org.purpurmc.purpur.PurpurConfig.unverifiedUsername.equals("default") ? Component.translatable("multiplayer.disconnect.unverified_username") : io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.purpurmc.purpur.PurpurConfig.unverifiedUsername))); // Purpur
- ServerLoginPacketListenerImpl.LOGGER.error("Username '{}' tried to join with an invalid session", gameprofile.getName());
- }
- } catch (AuthenticationUnavailableException authenticationunavailableexception) {
-diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
-index f70d5167fad762c858f10ba19636ddb829dc9538..4e1db6c64254eeef8579d4cae5919b5a6a18d200 100644
---- a/src/main/java/net/minecraft/server/players/PlayerList.java
-+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
-@@ -536,6 +536,7 @@ public abstract class PlayerList {
- scoreboard.addPlayerToTeam(player.getScoreboardName(), collideRuleTeam);
- }
- // Paper end
-+ org.purpurmc.purpur.task.BossBarTask.addToAll(player); // Purpur
- // CraftBukkit - Moved from above, added world
- if (GaleGlobalConfiguration.get().logToConsole.playerLoginLocations) { // Gale - JettPack - make logging login location configurable
- PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", player.getName().getString(), s1, player.getId(), worldserver1.serverLevelData.getLevelName(), player.getX(), player.getY(), player.getZ());
-@@ -651,6 +652,8 @@ public abstract class PlayerList {
- }
- public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer, net.kyori.adventure.text.Component leaveMessage) {
- // Paper end
-+ org.purpurmc.purpur.task.BossBarTask.removeFromAll(entityplayer.getBukkitEntity()); // Purpur
-+
- ServerLevel worldserver = entityplayer.getLevel();
-
- entityplayer.awardStat(Stats.LEAVE_GAME);
-@@ -805,7 +808,7 @@ public abstract class PlayerList {
- event.disallow(PlayerLoginEvent.Result.KICK_BANNED, PaperAdventure.asAdventure(ichatmutablecomponent)); // Paper - Adventure
- } else {
- // return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameprofile) ? IChatBaseComponent.translatable("multiplayer.disconnect.server_full") : null;
-- if (this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameprofile)) {
-+ if (this.players.size() >= this.maxPlayers && !(player.hasPermission("purpur.joinfullserver") || this.canBypassPlayerLimit(gameprofile))) { // Purpur
- event.disallow(PlayerLoginEvent.Result.KICK_FULL, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.serverFullMessage)); // Spigot // Paper - Adventure
- }
- }
-@@ -1045,6 +1048,8 @@ public abstract class PlayerList {
- }
- // Paper end
-
-+ entityplayer1.spawnInvulnerableTime = entityplayer1.level.purpurConfig.playerSpawnInvulnerableTicks; // Purpur
-+
- // CraftBukkit end
- return entityplayer1;
- }
-@@ -1144,6 +1149,20 @@ public abstract class PlayerList {
- }
- // CraftBukkit end
-
-+ // Purpur Start
-+ 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
-+
- public void broadcastAll(Packet> packet, ResourceKey dimension) {
- Iterator iterator = this.players.iterator();
-
-@@ -1247,6 +1266,7 @@ public abstract class PlayerList {
- } else {
- b0 = (byte) (24 + permissionLevel);
- }
-+ if (b0 < 28 && player.getBukkitEntity().hasPermission("purpur.debug.f3n")) b0 = 28; // Purpur
-
- player.connection.send(new ClientboundEntityEventPacket(player, b0));
- }
-@@ -1255,6 +1275,27 @@ public abstract class PlayerList {
- player.getBukkitEntity().recalculatePermissions(); // CraftBukkit
- this.server.getCommands().sendCommands(player);
- } // Paper
-+
-+ // 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/server/players/SleepStatus.java b/src/main/java/net/minecraft/server/players/SleepStatus.java
-index 823efad652d8ff9e96b99375b102fef6f017716e..60f89d7c77a5e792e21e93e35ed1670bd565799a 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
- 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
- ++this.sleepingPlayers;
- }
- // CraftBukkit start
-diff --git a/src/main/java/net/minecraft/world/damagesource/CombatRules.java b/src/main/java/net/minecraft/world/damagesource/CombatRules.java
-index ccbfcef3e83b1bef364447657bfd08a92d615cf6..aa2331c6df4e79d4bb0add071a0b11d2a3a08b88 100644
---- a/src/main/java/net/minecraft/world/damagesource/CombatRules.java
-+++ b/src/main/java/net/minecraft/world/damagesource/CombatRules.java
-@@ -11,12 +11,12 @@ public class CombatRules {
-
- public static float getDamageAfterAbsorb(float damage, float armor, float armorToughness) {
- float f = 2.0F + armorToughness / 4.0F;
-- float g = Mth.clamp(armor - damage / f, armor * 0.2F, 20.0F);
-+ float g = Mth.clamp(armor - damage / f, armor * 0.2F, org.purpurmc.purpur.PurpurConfig.limitArmor ? 20F : Float.MAX_VALUE); // Purpur
- return damage * (1.0F - g / 25.0F);
- }
-
- public static float getDamageAfterMagicAbsorb(float damageDealt, float protection) {
-- float f = Mth.clamp(protection, 0.0F, 20.0F);
-+ float f = Mth.clamp(protection, 0.0F, org.purpurmc.purpur.PurpurConfig.limitArmor ? 20F : Float.MAX_VALUE); // Purpur
- return damageDealt * (1.0F - f / 25.0F);
- }
- }
-diff --git a/src/main/java/net/minecraft/world/damagesource/DamageSource.java b/src/main/java/net/minecraft/world/damagesource/DamageSource.java
-index 93a1e990b0a6caae4143c2f9d09bfb368fa1d6db..615611fe372d6edaef56db058bbf2cf7641e3c26 100644
---- a/src/main/java/net/minecraft/world/damagesource/DamageSource.java
-+++ b/src/main/java/net/minecraft/world/damagesource/DamageSource.java
-@@ -126,6 +126,15 @@ public class DamageSource {
- }
- }
-
-+ // Purpur start
-+ 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
-+
- public String getMsgId() {
- return this.type().msgId();
- }
-diff --git a/src/main/java/net/minecraft/world/effect/MobEffect.java b/src/main/java/net/minecraft/world/effect/MobEffect.java
-index 2cc714585fc3790b70a7ad1ab8034543462e2b3b..22d7f04cefafa0115a4504e37380787777091b18 100644
---- a/src/main/java/net/minecraft/world/effect/MobEffect.java
-+++ b/src/main/java/net/minecraft/world/effect/MobEffect.java
-@@ -60,16 +60,16 @@ public class MobEffect {
- public void applyEffectTick(LivingEntity entity, int amplifier) {
- if (this == MobEffects.REGENERATION) {
- if (entity.getHealth() < entity.getMaxHealth()) {
-- entity.heal(1.0F, RegainReason.MAGIC_REGEN); // CraftBukkit
-+ entity.heal(entity.level.purpurConfig.entityHealthRegenAmount, RegainReason.MAGIC_REGEN); // CraftBukkit // Purpur
- }
- } else if (this == MobEffects.POISON) {
-- if (entity.getHealth() > 1.0F) {
-- entity.hurt(entity.damageSources().poison, 1.0F); // CraftBukkit - DamageSource.MAGIC -> CraftEventFactory.POISON
-+ if (entity.getHealth() > entity.level.purpurConfig.entityMinimalHealthPoison) { // Purpur
-+ entity.hurt(entity.damageSources().poison, entity.level.purpurConfig.entityPoisonDegenerationAmount); // CraftBukkit - DamageSource.MAGIC -> CraftEventFactory.POISON // Purpur
- }
- } else if (this == MobEffects.WITHER) {
-- entity.hurt(entity.damageSources().wither(), 1.0F);
-+ entity.hurt(entity.damageSources().wither(), entity.level.purpurConfig.entityWitherDegenerationAmount); // Purpur
- } else if (this == MobEffects.HUNGER && entity instanceof Player) {
-- ((Player) entity).causeFoodExhaustion(0.005F * (float) (amplifier + 1), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.HUNGER_EFFECT); // CraftBukkit - EntityExhaustionEvent
-+ ((Player) entity).causeFoodExhaustion(entity.level.purpurConfig.humanHungerExhaustionAmount * (float) (amplifier + 1), org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.HUNGER_EFFECT); // CraftBukkit - EntityExhaustionEvent // Purpur
- } else if (this == MobEffects.SATURATION && entity instanceof Player) {
- if (!entity.level.isClientSide) {
- // CraftBukkit start
-@@ -79,7 +79,7 @@ public class MobEffect {
- org.bukkit.event.entity.FoodLevelChangeEvent event = CraftEventFactory.callFoodLevelChangeEvent(entityhuman, amplifier + 1 + oldFoodLevel);
-
- if (!event.isCancelled()) {
-- entityhuman.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, 1.0F);
-+ entityhuman.getFoodData().eat(event.getFoodLevel() - oldFoodLevel, entity.level.purpurConfig.humanSaturationRegenAmount); // Purpur
- }
-
- ((ServerPlayer) entityhuman).connection.send(new ClientboundSetHealthPacket(((ServerPlayer) entityhuman).getBukkitEntity().getScaledHealth(), entityhuman.getFoodData().foodLevel, entityhuman.getFoodData().saturationLevel));
-diff --git a/src/main/java/net/minecraft/world/effect/MobEffectInstance.java b/src/main/java/net/minecraft/world/effect/MobEffectInstance.java
-index 14fab63346d56c72cd7534a04760efd10eef4295..745e792482f61c571e2efbd4200dd1bdaef6e474 100644
---- a/src/main/java/net/minecraft/world/effect/MobEffectInstance.java
-+++ b/src/main/java/net/minecraft/world/effect/MobEffectInstance.java
-@@ -14,6 +14,7 @@ import net.minecraft.util.ExtraCodecs;
- import net.minecraft.util.Mth;
- import net.minecraft.world.entity.LivingEntity;
- import org.slf4j.Logger;
-+import org.bukkit.NamespacedKey;
-
- public class MobEffectInstance implements Comparable {
- private static final Logger LOGGER = LogUtils.getLogger();
-@@ -25,6 +26,7 @@ public class MobEffectInstance implements Comparable {
- private boolean visible;
- private boolean showIcon;
- @Nullable
-+ private NamespacedKey key; // Purpur - add key
- private MobEffectInstance hiddenEffect;
- private final Optional factorData;
-
-@@ -44,17 +46,36 @@ public class MobEffectInstance implements Comparable {
- this(type, duration, amplifier, ambient, visible, visible);
- }
-
-+ // Purpur start
-+ public MobEffectInstance(MobEffect type, int duration, int amplifier, boolean ambient, boolean visible, @Nullable NamespacedKey key) {
-+ this(type, duration, amplifier, ambient, visible, visible, key);
-+ }
-+ // Purpur end
-+
- public MobEffectInstance(MobEffect type, int duration, int amplifier, boolean ambient, boolean showParticles, boolean showIcon) {
-- this(type, duration, amplifier, ambient, showParticles, showIcon, (MobEffectInstance)null, type.createFactorData());
-+ // Purpur start
-+ this(type, duration, amplifier, ambient, showParticles, showIcon, (MobEffectInstance)null, type.createFactorData(), (NamespacedKey)null);
-+ }
-+
-+ public MobEffectInstance(MobEffect type, int duration, int amplifier, boolean ambient, boolean showParticles, boolean showIcon, @Nullable NamespacedKey key) {
-+ this(type, duration, amplifier, ambient, showParticles, showIcon, (MobEffectInstance)null, type.createFactorData(), key);
-+ // Purpur end
- }
-
- public MobEffectInstance(MobEffect type, int duration, int amplifier, boolean ambient, boolean showParticles, boolean showIcon, @Nullable MobEffectInstance hiddenEffect, Optional factorCalculationData) {
-+ // Purpur start
-+ this(type, duration, amplifier, ambient, showParticles, showIcon, hiddenEffect, factorCalculationData, (NamespacedKey) null);
-+ }
-+
-+ public MobEffectInstance(MobEffect type, int duration, int amplifier, boolean ambient, boolean showParticles, boolean showIcon, @Nullable MobEffectInstance hiddenEffect, Optional factorCalculationData, @Nullable NamespacedKey key) {
-+ // Purpur end
- this.effect = type;
- this.duration = duration;
- this.amplifier = amplifier;
- this.ambient = ambient;
- this.visible = showParticles;
- this.showIcon = showIcon;
-+ this.key = key; // Purpur - add key
- this.hiddenEffect = hiddenEffect;
- this.factorData = factorCalculationData;
- }
-@@ -75,6 +96,7 @@ public class MobEffectInstance implements Comparable {
- this.ambient = that.ambient;
- this.visible = that.visible;
- this.showIcon = that.showIcon;
-+ this.key = that.key; // Purpur - add key
- }
-
- public boolean update(MobEffectInstance that) {
-@@ -120,6 +142,13 @@ public class MobEffectInstance implements Comparable {
- bl = true;
- }
-
-+ // Purpur start
-+ if (that.key != this.key) {
-+ this.key = that.key;
-+ bl = true;
-+ }
-+ // Purpur end
-+
- return bl;
- }
-
-@@ -163,6 +192,17 @@ public class MobEffectInstance implements Comparable {
- return this.showIcon;
- }
-
-+ // Purpur start
-+ public boolean hasKey() {
-+ return this.key != null;
-+ }
-+
-+ @Nullable
-+ public NamespacedKey getKey() {
-+ return this.key;
-+ }
-+ // Purpur end
-+
- public boolean tick(LivingEntity entity, Runnable overwriteCallback) {
- if (this.hasRemainingDuration()) {
- int i = this.isInfiniteDuration() ? entity.tickCount : this.duration;
-@@ -226,6 +266,12 @@ public class MobEffectInstance implements Comparable {
- string = string + ", Show Icon: false";
- }
-
-+ // Purpur start
-+ if (this.hasKey()) {
-+ string = string + ", Key: " + this.key;
-+ }
-+ // Purpur end
-+
- return string;
- }
-
-@@ -241,7 +287,7 @@ public class MobEffectInstance implements Comparable {
- return false;
- } else {
- MobEffectInstance mobEffectInstance = (MobEffectInstance)object;
-- return this.duration == mobEffectInstance.duration && this.amplifier == mobEffectInstance.amplifier && this.ambient == mobEffectInstance.ambient && this.effect.equals(mobEffectInstance.effect);
-+ return this.duration == mobEffectInstance.duration && this.amplifier == mobEffectInstance.amplifier && this.ambient == mobEffectInstance.ambient && this.effect.equals(mobEffectInstance.effect) && this.key == mobEffectInstance.key; // Purpur - add key
- }
- }
-
-@@ -265,6 +311,11 @@ public class MobEffectInstance implements Comparable {
- nbt.putBoolean("Ambient", this.isAmbient());
- nbt.putBoolean("ShowParticles", this.isVisible());
- nbt.putBoolean("ShowIcon", this.showIcon());
-+ // Purpur start
-+ if (this.key != null) {
-+ nbt.putString("Key", this.key.toString());
-+ }
-+ // Purpur end
- if (this.hiddenEffect != null) {
- CompoundTag compoundTag = new CompoundTag();
- this.hiddenEffect.save(compoundTag);
-@@ -299,6 +350,13 @@ public class MobEffectInstance implements Comparable {
- bl3 = nbt.getBoolean("ShowIcon");
- }
-
-+ // Purpur start
-+ NamespacedKey key = null;
-+ if (nbt.contains("Key")) {
-+ key = NamespacedKey.fromString(nbt.getString("Key"));
-+ }
-+ // Purpur end
-+
- MobEffectInstance mobEffectInstance = null;
- if (nbt.contains("HiddenEffect", 10)) {
- mobEffectInstance = loadSpecifiedEffect(type, nbt.getCompound("HiddenEffect"));
-@@ -311,7 +369,7 @@ public class MobEffectInstance implements Comparable {
- optional = Optional.empty();
- }
-
-- return new MobEffectInstance(type, j, Math.max(i, 0), bl, bl2, bl3, mobEffectInstance, optional);
-+ return new MobEffectInstance(type, j, Math.max(i, 0), bl, bl2, bl3, mobEffectInstance, optional, key); // Purpur - add key
- }
-
- @Override
-diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
-index 101ce131804492779e34218c65879398794e2d7f..7fdcd4b78ff43050c157dea7e4fd7b9aad2cda5c 100644
---- a/src/main/java/net/minecraft/world/entity/Entity.java
-+++ b/src/main/java/net/minecraft/world/entity/Entity.java
-@@ -157,7 +157,7 @@ import org.bukkit.plugin.PluginManager;
- // CraftBukkit end
-
- public abstract class Entity implements Nameable, EntityAccess, CommandSource {
--
-+ public static javax.script.ScriptEngine scriptEngine = new javax.script.ScriptEngineManager().getEngineByName("rhino"); // Purpur
- // CraftBukkit start
- private static final int CURRENT_LEVEL = 2;
- public boolean preserveMotion = true; // Paper - keep initial motion on first setPositionRotation
-@@ -322,7 +322,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
- public double xOld;
- public double yOld;
- public double zOld;
-- private float maxUpStep;
-+ public float maxUpStep; // Purpur - private -> public
- public boolean noPhysics;
- protected final RandomSource random;
- public int tickCount;
-@@ -364,7 +364,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
- private final Set tags;
- private final double[] pistonDeltas;
- private long pistonDeltasGameTime;
-- private EntityDimensions dimensions;
-+ protected EntityDimensions dimensions; // Purpur - private -> protected
- private float eyeHeight;
- public boolean isInPowderSnow;
- public boolean wasInPowderSnow;
-@@ -403,6 +403,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
- private UUID originWorld;
- public boolean freezeLocked = false; // Paper - Freeze Tick Lock API
- public boolean collidingWithWorldBorder; // Paper
-+ public @Nullable Boolean immuneToFire = null; // Purpur - Fire immune API
-
- public void setOrigin(@javax.annotation.Nonnull Location location) {
- this.origin = location.toVector();
-@@ -583,7 +584,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
- this.bb = Entity.INITIAL_AABB;
- this.stuckSpeedMultiplier = Vec3.ZERO;
- this.nextStep = 1.0F;
-- this.random = SHARED_RANDOM; // Paper
-+ this.random = world == null || world.purpurConfig.entitySharedRandom ? SHARED_RANDOM : RandomSource.create(); // Paper // Purpur
- this.remainingFireTicks = -this.getFireImmuneTicks();
- this.fluidHeight = new Object2DoubleArrayMap(2);
- this.fluidOnEyes = new HashSet();
-@@ -902,10 +903,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
-
- public void checkOutOfWorld() {
- // Paper start - Configurable nether ceiling damage
-- if (this.getY() < (double) (this.level.getMinBuildHeight() - 64) || (this.level.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER
-+ if (this.getY() < (double) (this.level.getMinBuildHeight() + level.purpurConfig.voidDamageHeight) || (this.level.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER // Purpur
- && this.level.paperConfig().environment.netherCeilingVoidDamageHeight.test(v -> this.getY() >= v)
- && (!(this instanceof Player player) || !player.getAbilities().invulnerable))) {
- // Paper end
-+ if (this.level.purpurConfig.teleportOnNetherCeilingDamage && this.level.getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER && this instanceof ServerPlayer player) player.teleport(MCUtil.toLocation(this.level, this.level.getSharedSpawnPos())); else // Purpur
- this.outOfWorld();
- }
-
-@@ -1691,7 +1693,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
- }
-
- public boolean fireImmune() {
-- return this.getType().fireImmune();
-+ return this.immuneToFire != null ? immuneToFire : this.getType().fireImmune(); // Purpur - add fire immune API
- }
-
- public boolean causeFallDamage(float fallDistance, float damageMultiplier, DamageSource damageSource) {
-@@ -1760,7 +1762,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
- return this.isInWater() || flag;
- }
-
-- void updateInWaterStateAndDoWaterCurrentPushing() {
-+ public void updateInWaterStateAndDoWaterCurrentPushing() { // Purpur - package-private -> public
- Entity entity = this.getVehicle();
-
- if (entity instanceof Boat) {
-@@ -2361,6 +2363,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
- nbt.putBoolean("Paper.FreezeLock", true);
- }
- // Paper end
-+ // Purpur start
-+ if (immuneToFire != null) {
-+ nbt.putBoolean("Purpur.FireImmune", immuneToFire);
-+ }
-+ // Purpur end
- return nbt;
- } catch (Throwable throwable) {
- CrashReport crashreport = CrashReport.forThrowable(throwable, "Saving entity NBT");
-@@ -2529,6 +2536,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
- freezeLocked = nbt.getBoolean("Paper.FreezeLock");
- }
- // Paper end
-+ // Purpur start
-+ if (nbt.contains("Purpur.FireImmune")) {
-+ immuneToFire = nbt.getBoolean("Purpur.FireImmune");
-+ }
-+ // Purpur end
-
- } catch (Throwable throwable) {
- CrashReport crashreport = CrashReport.forThrowable(throwable, "Loading entity NBT");
-@@ -2845,6 +2857,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
- this.passengers = ImmutableList.copyOf(list);
- }
-
-+ // Purpur start
-+ if (isRidable() && this.passengers.get(0) == entity && entity instanceof Player player) {
-+ onMount(player);
-+ this.rider = player;
-+ }
-+ // Purpur end
-+
- this.gameEvent(GameEvent.ENTITY_MOUNT, entity);
- }
- return true; // CraftBukkit
-@@ -2886,6 +2905,14 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
- return false;
- }
- // Spigot end
-+
-+ // Purpur start
-+ if (this.rider != null && this.passengers.get(0) == this.rider) {
-+ onDismount(this.rider);
-+ this.rider = null;
-+ }
-+ // Purpur end
-+
- if (this.passengers.size() == 1 && this.passengers.get(0) == entity) {
- this.passengers = ImmutableList.of();
- } else {
-@@ -2945,12 +2972,15 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
- return Vec3.directionFromRotation(this.getRotationVector());
- }
-
-+ public BlockPos portalPos = BlockPos.ZERO; // Purpur
- public void handleInsidePortal(BlockPos pos) {
- if (this.isOnPortalCooldown()) {
-+ if (!(level.purpurConfig.playerFixStuckPortal && this instanceof Player && !pos.equals(portalPos))) // Purpur
- this.setPortalCooldown();
-- } else {
-+ } else if (level.purpurConfig.entitiesCanUsePortals || this instanceof ServerPlayer) { // Purpur
- if (!this.level.isClientSide && !pos.equals(this.portalEntrancePos)) {
- this.portalEntrancePos = pos.immutable();
-+ portalPos = BlockPos.ZERO; // Purpur
- }
-
- this.isInsidePortal = true;
-@@ -2999,7 +3029,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
- }
-
- this.processPortalCooldown();
-- this.tickEndPortal(); // Paper - make end portalling safe
-+ if (this.level.purpurConfig.endPortalSafeTeleporting) this.tickEndPortal(); // Paper - make end portalling safe // Purpur
- }
- }
-
-@@ -3181,7 +3211,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
- }
-
- public int getMaxAirSupply() {
-- return this.maxAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir()
-+ return this.level == null? this.maxAirTicks : this.level.purpurConfig.drowningAirTicks; // CraftBukkit - SPIGOT-6907: re-implement LivingEntity#setMaximumAir() // Purpur
- }
-
- public int getAirSupply() {
-@@ -3640,7 +3670,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
- }
-
- public boolean canChangeDimensions() {
-- return !this.isPassenger() && !this.isVehicle() && isAlive() && valid; // Paper
-+ return !this.isPassenger() && !this.isVehicle() && isAlive() && valid && (level.purpurConfig.entitiesCanUsePortals || this instanceof ServerPlayer); // Paper // Purpur
- }
-
- public float getBlockExplosionResistance(Explosion explosion, BlockGetter world, BlockPos pos, BlockState blockState, FluidState fluidState, float max) {
-@@ -3942,6 +3972,20 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
- return SlotAccess.NULL;
- }
-
-+ // Purpur Start
-+ 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
-+
- @Override
- public void sendSystemMessage(Component message) {}
-
-@@ -4223,6 +4267,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
- this.yRotO = this.getYRot();
- }
-
-+ // Purpur start
-+ public AABB getAxisForFluidCheck() {
-+ return this.getBoundingBox().deflate(0.001D);
-+ }
-+ // Purpur end
- public boolean updateFluidHeightAndDoFluidPushing(TagKey tag, double speed) {
- if (false && this.touchingUnloadedChunk()) { // Gale - Airplane - reduce entity fluid lookups if no fluids - cost of a lookup here is the same cost as below, so skip
- return false;
-@@ -4761,4 +4810,81 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
- return ((net.minecraft.server.level.ServerChunkCache) level.getChunkSource()).isPositionTicking(this);
- }
- // Paper end
-+
-+ // Purpur start
-+ @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;
-+ }
-+
-+ public boolean canSaveToDisk() {
-+ return true;
-+ }
-+
-+
-+ // Gale start - JettPack - optimize sun burn tick - cache eye blockpos
-+ private BlockPos cached_eye_blockpos;
-+ private int cached_position_hashcode;
-+ // Gale end - JettPack - optimize sun burn tick - cache eye blockpos
-+
-+ // Purpur start - copied from Mob
-+ public boolean isSunBurnTick() {
-+ if (this.level.isDay() && !this.level.isClientSide) {
-+ // Gale start - JettPack - optimize sun burn tick - optimizations and cache eye blockpos
-+ int positionHashCode = this.position.hashCode();
-+ if (this.cached_position_hashcode != positionHashCode) {
-+ this.cached_eye_blockpos = BlockPos.containing(this.getX(), this.getEyeY(), this.getZ());
-+ this.cached_position_hashcode = positionHashCode;
-+ }
-+
-+ float f = this.getLightLevelDependentMagicValue(cached_eye_blockpos); // Pass BlockPos to getBrightness
-+
-+ // Check brightness first
-+ if (f <= 0.5F) return false;
-+ if (this.random.nextFloat() * 30.0F >= (f - 0.4F) * 2.0F) return false;
-+ // Gale end - JettPack - optimize sun burn tick - optimizations and cache eye blockpos
-+ boolean flag = this.isInWaterRainOrBubble() || this.isInPowderSnow || this.wasInPowderSnow;
-+
-+ if (!flag && this.level.canSeeSky(this.cached_eye_blockpos)) { // Gale - JettPack - optimize sun burn tick - optimizations and cache eye blockpos
-+ return true;
-+ }
-+ }
-+
-+ return false;
-+ }
-+ // Purpur end
- }
-diff --git a/src/main/java/net/minecraft/world/entity/EntitySelector.java b/src/main/java/net/minecraft/world/entity/EntitySelector.java
-index 72abebff2018cde2922e97ad6478f93da9aed3ec..412963d7af38a53b6010007278d959a5b11b83c3 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
-+ public static Predicate notAfk = (player) -> !player.isAfk(); // Purpur
-
- private EntitySelector() {}
- // Paper start
-diff --git a/src/main/java/net/minecraft/world/entity/EntityType.java b/src/main/java/net/minecraft/world/entity/EntityType.java
-index 8af0918d3a62de58a4b2af55022c812bb0e46092..3fc26a8976f4bfa28c2c6a862aac997d5f721f51 100644
---- a/src/main/java/net/minecraft/world/entity/EntityType.java
-+++ b/src/main/java/net/minecraft/world/entity/EntityType.java
-@@ -308,13 +308,24 @@ public class EntityType implements FeatureElement, EntityTypeT
- private Component description;
- @Nullable
- private ResourceLocation lootTable;
-- private final EntityDimensions dimensions;
-+ private EntityDimensions dimensions; // Purpur - remove final
-+ public void setDimensions(EntityDimensions dimensions) { this.dimensions = dimensions; } // Purpur
- private final FeatureFlagSet requiredFeatures;
-
- private static EntityType register(String id, EntityType.Builder type) { // CraftBukkit - decompile error
- return (EntityType) Registry.register(BuiltInRegistries.ENTITY_TYPE, id, (EntityType) type.build(id)); // CraftBukkit - decompile error
- }
-
-+ // Purpur start
-+ public static EntityType> getFromBukkitType(org.bukkit.entity.EntityType bukkitType) {
-+ return getFromKey(new ResourceLocation(bukkitType.getKey().toString()));
-+ }
-+
-+ public static EntityType> getFromKey(ResourceLocation location) {
-+ return BuiltInRegistries.ENTITY_TYPE.get(location);
-+ }
-+ // Purpur end
-+
- public static ResourceLocation getKey(EntityType> type) {
- return BuiltInRegistries.ENTITY_TYPE.getKey(type);
- }
-@@ -530,6 +541,16 @@ public class EntityType implements FeatureElement, EntityTypeT
- return this.category;
- }
-
-+ // Purpur start
-+ public String getName() {
-+ return BuiltInRegistries.ENTITY_TYPE.getKey(this).getPath();
-+ }
-+
-+ public String getTranslatedName() {
-+ return getDescription().getString();
-+ }
-+ // Purpur end
-+
- public String getDescriptionId() {
- if (this.descriptionId == null) {
- this.descriptionId = Util.makeDescriptionId("entity", BuiltInRegistries.ENTITY_TYPE.getKey(this));
-@@ -591,6 +612,12 @@ public class EntityType implements FeatureElement, EntityTypeT
- entity.load(nbt);
- }, () -> {
- EntityType.LOGGER.warn("Skipping Entity with id {}", nbt.getString("id"));
-+ // Purpur start - log skipped entity's position
-+ try {
-+ ListTag pos = nbt.getList("Pos", 6);
-+ EntityType.LOGGER.warn("Location: {} {},{},{}", world.getWorld().getName(), pos.getDouble(0), pos.getDouble(1), pos.getDouble(2));
-+ } catch (Throwable ignore) {}
-+ // Purpur end
- });
- }
-
-diff --git a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java
-index 89699aaccd45a5a928a97d1b3ad06f5de5b9fad1..04805693a0cbc64c96a023c4abbee1827e1c7b7b 100644
---- a/src/main/java/net/minecraft/world/entity/ExperienceOrb.java
-+++ b/src/main/java/net/minecraft/world/entity/ExperienceOrb.java
-@@ -324,7 +324,7 @@ public class ExperienceOrb extends Entity {
- }
-
- private int repairPlayerItems(Player player, int amount) {
-- Entry entry = EnchantmentHelper.getRandomItemWith(Enchantments.MENDING, player, ItemStack::isDamaged);
-+ Entry entry = level.purpurConfig.useBetterMending ? EnchantmentHelper.getMostDamagedEquipment(Enchantments.MENDING, player) : EnchantmentHelper.getRandomItemWith(Enchantments.MENDING, player, ItemStack::isDamaged); // Purpur
-
- if (entry != null) {
- ItemStack itemstack = (ItemStack) entry.getValue();
-diff --git a/src/main/java/net/minecraft/world/entity/GlowSquid.java b/src/main/java/net/minecraft/world/entity/GlowSquid.java
-index c1e9b40a4a0f9cdc650caa88b5ea132e06ee2496..6f723171fa71d74b351b5cf0cd167bb6f7ca1691 100644
---- a/src/main/java/net/minecraft/world/entity/GlowSquid.java
-+++ b/src/main/java/net/minecraft/world/entity/GlowSquid.java
-@@ -18,11 +18,45 @@ import net.minecraft.world.level.block.Blocks;
-
- public class GlowSquid extends Squid {
- private static final EntityDataAccessor DATA_DARK_TICKS_REMAINING = SynchedEntityData.defineId(GlowSquid.class, EntityDataSerializers.INT);
-+ private static final net.minecraft.network.syncher.EntityDataAccessor SQUID_COLOR = net.minecraft.network.syncher.SynchedEntityData.defineId(GlowSquid.class, net.minecraft.network.syncher.EntityDataSerializers.STRING); // Purpur
-
- public GlowSquid(EntityType extends GlowSquid> type, Level world) {
- super(type, world);
- }
-
-+ // Purpur start
-+ @Override
-+ public boolean isRidable() {
-+ return level.purpurConfig.glowSquidRidable;
-+ }
-+
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level.purpurConfig.glowSquidControllable;
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.glowSquidMaxHealth);
-+ }
-+
-+ @Override
-+ public boolean canFly() {
-+ return this.level.purpurConfig.glowSquidsCanFly;
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.glowSquidTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.glowSquidAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @Override
- protected ParticleOptions getInkParticle() {
- return ParticleTypes.GLOW_SQUID_INK;
-@@ -32,6 +66,7 @@ public class GlowSquid extends Squid {
- protected void defineSynchedData() {
- super.defineSynchedData();
- this.entityData.define(DATA_DARK_TICKS_REMAINING, 0);
-+ this.entityData.define(SQUID_COLOR, this.level.purpurConfig.glowSquidColorMode.getRandom(this.random).toString()); // Purpur
- }
-
- @Override
-@@ -58,12 +93,14 @@ public class GlowSquid extends Squid {
- public void addAdditionalSaveData(CompoundTag nbt) {
- super.addAdditionalSaveData(nbt);
- nbt.putInt("DarkTicksRemaining", this.getDarkTicksRemaining());
-+ nbt.putString("Colour", this.entityData.get(SQUID_COLOR)); // Purpur - key must match rainglow
- }
-
- @Override
- public void readAdditionalSaveData(CompoundTag nbt) {
- super.readAdditionalSaveData(nbt);
- this.setDarkTicks(nbt.getInt("DarkTicksRemaining"));
-+ if (nbt.contains("Colour")) this.entityData.set(SQUID_COLOR, nbt.getString("Colour")); // Purpur - key must match rainglow
- }
-
- @Override
-diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
-index 9d5150aec55411abb5192f2e5cd59b3e1038a35c..89a2e52dd1d93a9521d48218711f92305b0d7848 100644
---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
-+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
-@@ -216,9 +216,9 @@ public abstract class LivingEntity extends Entity implements Attackable {
- protected int deathScore;
- 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;
-@@ -251,6 +251,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
- private boolean skipDropExperience;
- // CraftBukkit start
- public int expToDrop;
-+ public float safeFallDistance = 3.0F; // Purpur
- public boolean forceDrops;
- public ArrayList drops = new ArrayList();
- public final org.bukkit.craftbukkit.attribute.CraftAttributeMap craftAttributes;
-@@ -260,6 +261,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
- public org.bukkit.craftbukkit.entity.CraftLivingEntity getBukkitLivingEntity() { return (org.bukkit.craftbukkit.entity.CraftLivingEntity) super.getBukkitEntity(); } // Paper
- public boolean silentDeath = false; // Paper - mark entity as dying silently for cancellable death event
- public net.kyori.adventure.util.TriState frictionState = net.kyori.adventure.util.TriState.NOT_SET; // Paper
-+ protected boolean shouldBurnInDay = false; public boolean shouldBurnInDay() { return this.shouldBurnInDay; } public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } // Purpur
-
- @Override
- public float getBukkitYaw() {
-@@ -284,7 +286,8 @@ public abstract class LivingEntity extends Entity implements Attackable {
- this.effectsDirty = true;
- this.useItem = ItemStack.EMPTY;
- this.lastClimbablePos = Optional.empty();
-- this.attributes = new AttributeMap(DefaultAttributes.getSupplier(type));
-+ this.attributes = new AttributeMap(DefaultAttributes.getSupplier(type), this); // Purpur
-+ this.initAttributes(); // Purpur
- 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());
-@@ -300,6 +303,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
-+
- public Brain> getBrain() {
- return this.brain;
- }
-@@ -335,6 +340,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);
- }
-+ public boolean shouldSendAttribute(Attribute attribute) { return true; } // Purpur
-
- @Override
- protected void checkFallDamage(double heightDifference, boolean onGround, BlockState state, BlockPos landedPosition) {
-@@ -347,8 +353,8 @@ public abstract class LivingEntity extends Entity implements Attackable {
- this.tryAddSoulSpeed();
- }
-
-- if (!this.level.isClientSide && this.fallDistance > 3.0F && onGround) {
-- float f = (float) Mth.ceil(this.fallDistance - 3.0F);
-+ if (!this.level.isClientSide && this.fallDistance > this.safeFallDistance && onGround) { // Purpur
-+ float f = (float) Mth.ceil(this.fallDistance - this.safeFallDistance); // Purpur
-
- if (!state.isAir()) {
- double d1 = Math.min((double) (0.2F + f / 15.0F), 2.5D);
-@@ -407,6 +413,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
- double d1 = this.level.getWorldBorder().getDamagePerBlock();
-
- if (d1 > 0.0D) {
-+ if (level.purpurConfig.teleportIfOutsideBorder && this instanceof ServerPlayer) { ((ServerPlayer) this).teleport(io.papermc.paper.util.MCUtil.toLocation(level, ((ServerLevel) level).getSharedSpawnPos())); return; } // Purpur
- this.hurt(this.damageSources().inWall(), (float) Math.max(1, Mth.floor(-d0 * d1)));
- }
- }
-@@ -418,7 +425,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
-
- if (flag1) {
- this.setAirSupply(this.decreaseAirSupply(this.getAirSupply()));
-- if (this.getAirSupply() == -20) {
-+ if (this.getAirSupply() == -this.level.purpurConfig.drowningDamageInterval) { // Purpur
- this.setAirSupply(0);
- Vec3 vec3d = this.getDeltaMovement();
-
-@@ -430,7 +437,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
- this.level.addParticle(ParticleTypes.BUBBLE, this.getX() + d2, this.getY() + d3, this.getZ() + d4, vec3d.x, vec3d.y, vec3d.z);
- }
-
-- this.hurt(this.damageSources().drown(), 2.0F);
-+ this.hurt(this.damageSources().drown(), (float) this.level.purpurConfig.damageFromDrowning); // Purpur
- }
- }
-
-@@ -780,6 +787,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
- dataresult.resultOrPartial(logger::error).ifPresent((nbtbase) -> {
- nbt.put("Brain", nbtbase);
- });
-+ nbt.putBoolean("Purpur.ShouldBurnInDay", shouldBurnInDay); // Purpur
- }
-
- @Override
-@@ -864,6 +872,11 @@ public abstract class LivingEntity extends Entity implements Attackable {
- this.brain = this.makeBrain(new Dynamic(NbtOps.INSTANCE, nbt.get("Brain")));
- }
-
-+ // Purpur start
-+ if (nbt.contains("Purpur.ShouldBurnInDay")) {
-+ shouldBurnInDay = nbt.getBoolean("Purpur.ShouldBurnInDay");
-+ }
-+ // Purpur end
- }
-
- // CraftBukkit start
-@@ -1009,9 +1022,31 @@ public abstract class LivingEntity extends Entity implements Attackable {
- ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD);
- EntityType> entitytypes = entity.getType();
-
-- if (entitytypes == EntityType.SKELETON && itemstack.is(Items.SKELETON_SKULL) || entitytypes == EntityType.ZOMBIE && itemstack.is(Items.ZOMBIE_HEAD) || entitytypes == EntityType.PIGLIN && itemstack.is(Items.PIGLIN_HEAD) || entitytypes == EntityType.PIGLIN_BRUTE && itemstack.is(Items.PIGLIN_HEAD) || entitytypes == EntityType.CREEPER && itemstack.is(Items.CREEPER_HEAD)) {
-- d0 *= 0.5D;
-+ // Purpur start
-+ if (entitytypes == EntityType.SKELETON && itemstack.is(Items.SKELETON_SKULL)) {
-+ d0 *= entity.level.purpurConfig.skeletonHeadVisibilityPercent;
-+ }
-+ else if (entitytypes == EntityType.ZOMBIE && itemstack.is(Items.ZOMBIE_HEAD)) {
-+ d0 *= entity.level.purpurConfig.zombieHeadVisibilityPercent;
-+ }
-+ else if (entitytypes == EntityType.CREEPER && itemstack.is(Items.CREEPER_HEAD)) {
-+ d0 *= entity.level.purpurConfig.creeperHeadVisibilityPercent;
-+ }
-+ else if ((entitytypes == EntityType.PIGLIN || entitytypes == EntityType.PIGLIN_BRUTE) && itemstack.is(Items.PIGLIN_HEAD)) {
-+ d0 *= entity.level.purpurConfig.piglinHeadVisibilityPercent;
-+ }
-+ // Purpur end
-+
-+ // Purpur start
-+ if (entity instanceof LivingEntity entityliving) {
-+ if (entityliving.hasEffect(MobEffects.BLINDNESS)) {
-+ int amplifier = entityliving.getEffect(MobEffects.BLINDNESS).getAmplifier();
-+ for (int i = 0; i < amplifier; i++) {
-+ d0 *= this.level.purpurConfig.mobsBlindnessMultiplier;
-+ }
-+ }
- }
-+ // Purpur end
- }
-
- return d0;
-@@ -1071,6 +1106,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
- for (flag = false; iterator.hasNext(); flag = true) {
- // CraftBukkit start
- MobEffectInstance effect = (MobEffectInstance) iterator.next();
-+ if (cause == EntityPotionEffectEvent.Cause.MILK && !this.level.purpurConfig.milkClearsBeneficialEffects && effect.getEffect().isBeneficial()) continue; // Purpur
- EntityPotionEffectEvent event = CraftEventFactory.callEntityPotionEffectChangeEvent(this, effect, null, cause, EntityPotionEffectEvent.Action.CLEARED);
- if (event.isCancelled()) {
- continue;
-@@ -1427,13 +1463,13 @@ public abstract class LivingEntity extends Entity implements Attackable {
- if (entity1 instanceof net.minecraft.world.entity.player.Player) {
- net.minecraft.world.entity.player.Player entityhuman = (net.minecraft.world.entity.player.Player) entity1;
-
-- this.lastHurtByPlayerTime = 100;
-+ this.lastHurtByPlayerTime = this.level.purpurConfig.mobLastHurtByPlayerTime; // Purpur
- this.lastHurtByPlayer = entityhuman;
- } else if (entity1 instanceof Wolf) {
- Wolf entitywolf = (Wolf) entity1;
-
- if (entitywolf.isTame()) {
-- this.lastHurtByPlayerTime = 100;
-+ this.lastHurtByPlayerTime = this.level.purpurConfig.mobLastHurtByPlayerTime; // Purpur
- LivingEntity entityliving2 = entitywolf.getOwner();
-
- if (entityliving2 instanceof net.minecraft.world.entity.player.Player) {
-@@ -1544,6 +1580,18 @@ public abstract class LivingEntity extends Entity implements Attackable {
- }
- }
-
-+ // Purpur start
-+ if (level.purpurConfig.totemOfUndyingWorksInInventory && this instanceof ServerPlayer player && (itemstack == null || itemstack.getItem() != Items.TOTEM_OF_UNDYING) && player.getBukkitEntity().hasPermission("purpur.inventory_totem")) {
-+ for (ItemStack item : player.getInventory().items) {
-+ if (item.getItem() == Items.TOTEM_OF_UNDYING) {
-+ itemstack1 = item;
-+ itemstack = item.copy();
-+ break;
-+ }
-+ }
-+ }
-+ // Purpur end
-+
- org.bukkit.inventory.EquipmentSlot handSlot = (hand != null) ? org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand) : null;
- EntityResurrectEvent event = new EntityResurrectEvent((org.bukkit.entity.LivingEntity) this.getBukkitEntity(), handSlot);
- event.setCancelled(itemstack == null);
-@@ -1704,7 +1752,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
- boolean flag = false;
-
- if (this.dead && adversary instanceof WitherBoss) { // Paper
-- if (this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
-+ if (this.level.purpurConfig.witherBypassMobGriefing || this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur
- BlockPos blockposition = this.blockPosition();
- BlockState iblockdata = Blocks.WITHER_ROSE.defaultBlockState();
-
-@@ -1750,6 +1798,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
-
- this.dropEquipment(); // CraftBukkit - from below
- if (this.shouldDropLoot() && this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
-+ if (!(source.is(net.minecraft.world.damagesource.DamageTypes.CRAMMING) && level.purpurConfig.disableDropsOnCrammingDeath)) { // Purpur
- this.dropFromLootTable(source, flag);
- // Paper start
- final boolean prev = this.clearEquipmentSlots;
-@@ -1758,6 +1807,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
- // Paper end
- this.dropCustomDeathLoot(source, i, flag);
- this.clearEquipmentSlots = prev; // Paper
-+ } // Purpur
- }
- // CraftBukkit start - Call death event // Paper start - call advancement triggers with correct entity equipment
- org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, this.drops, () -> {
-@@ -2037,7 +2087,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
- MobEffectInstance mobeffect = this.getEffect(MobEffects.JUMP);
- float f2 = mobeffect == null ? 0.0F : (float) (mobeffect.getAmplifier() + 1);
-
-- return Mth.ceil((fallDistance - 3.0F - f2) * damageMultiplier);
-+ return Mth.ceil((fallDistance - this.safeFallDistance - f2) * damageMultiplier); // Purpur
- }
- }
-
-@@ -2260,6 +2310,20 @@ public abstract class LivingEntity extends Entity implements Attackable {
- }
- }
-
-+ // Purpur start
-+ if (damagesource.getEntity() instanceof net.minecraft.world.entity.player.Player player && damagesource.getEntity().level.purpurConfig.creativeOnePunch) {
-+ if (player.isCreative()) {
-+ double attackDamage = 0;
-+ for (AttributeModifier modifier : player.getMainHandItem().getAttributeModifiers(EquipmentSlot.MAINHAND).get(Attributes.ATTACK_DAMAGE)) {
-+ attackDamage += modifier.getAmount();
-+ }
-+ if (attackDamage == 0) {
-+ this.setHealth(0);
-+ }
-+ }
-+ }
-+ // Purpur end
-+
- if (f > 0 || !human) {
- if (human) {
- // PAIL: Be sure to drag all this code from the EntityHuman subclass each update.
-@@ -2478,7 +2542,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
-
- @Override
- protected void outOfWorld() {
-- this.hurt(this.damageSources().outOfWorld(), 4.0F);
-+ this.hurt(this.damageSources().outOfWorld(), (float) level.purpurConfig.voidDamageDealt); // Purpur
- }
-
- protected void updateSwingTime() {
-@@ -2675,7 +2739,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
- }
-
- protected long lastJumpTime = 0L; // Paper
-- protected void jumpFromGround() {
-+ public void jumpFromGround() { // Purpur - protected -> public
- double d0 = (double) this.getJumpPower() + this.getJumpBoostPower();
- Vec3 vec3d = this.getDeltaMovement();
- // Paper start
-@@ -2829,6 +2893,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
-
- if (f3 > 0.0F) {
- this.playSound(this.getFallDamageSound((int) f3), 1.0F, 1.0F);
-+ if (level.purpurConfig.elytraKineticDamage) // Purpur
- this.hurt(this.damageSources().flyIntoWall(), f3);
- }
- }
-@@ -3423,8 +3488,10 @@ public abstract class LivingEntity extends Entity implements Attackable {
-
- this.pushEntities();
- // Paper start
-- if (((ServerLevel) this.level).hasEntityMoveEvent && !(this instanceof net.minecraft.world.entity.player.Player)) {
-- if (this.xo != getX() || this.yo != this.getY() || this.zo != this.getZ() || this.yRotO != this.getYRot() || this.xRotO != this.getXRot()) {
-+ // Purpur start
-+ 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
- 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());
-@@ -3434,12 +3501,48 @@ public abstract class LivingEntity extends Entity implements Attackable {
- absMoveTo(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ(), event.getTo().getYaw(), event.getTo().getPitch());
- }
- }
-+ // Purpur start
-+ 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
- }
- // Paper end
- if (!this.level.isClientSide && this.isSensitiveToWater() && this.isInWaterRainOrBubble()) {
- this.hurt(this.damageSources().drown(), 1.0F);
- }
-
-+ // Purpur start - copied from Zombie
-+ if (this.isAlive()) {
-+ boolean flag = this.shouldBurnInDay() && this.isSunBurnTick();
-+ if (flag) {
-+ ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD);
-+ if (!itemstack.isEmpty()) {
-+ if (itemstack.isDamageableItem()) {
-+ itemstack.setDamageValue(itemstack.getDamageValue() + this.random.nextInt(2));
-+ if (itemstack.getDamageValue() >= itemstack.getMaxDamage()) {
-+ this.broadcastBreakEvent(EquipmentSlot.HEAD);
-+ this.setItemSlot(EquipmentSlot.HEAD, ItemStack.EMPTY);
-+ }
-+ }
-+ flag = false;
-+ }
-+ if (flag) {
-+ this.setSecondsOnFire(8);
-+ }
-+ }
-+ }
-+ // Purpur end
- }
-
- public boolean isSensitiveToWater() {
-@@ -3460,7 +3563,16 @@ public abstract class LivingEntity extends Entity implements Attackable {
- int j = i / 10;
-
- if (j % 2 == 0) {
-- itemstack.hurtAndBreak(1, this, (entityliving) -> {
-+ // Purpur start
-+ int damage = level.purpurConfig.elytraDamagePerSecond;
-+ if (level.purpurConfig.elytraDamageMultiplyBySpeed > 0) {
-+ double speed = getDeltaMovement().lengthSqr();
-+ if (speed > level.purpurConfig.elytraDamageMultiplyBySpeed) {
-+ damage *= (int) speed;
-+ }
-+ }
-+ itemstack.hurtAndBreak(damage, this, (entityliving) -> {
-+ // Purpur end
- entityliving.broadcastBreakEvent(EquipmentSlot.CHEST);
- });
- }
-diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java
-index 23f9b67ed0f9ec357d2ef8220f6f21c74248dcd2..ad56f2ecd8bb74d94974fdd05f4550b48e6b8d9e 100644
---- a/src/main/java/net/minecraft/world/entity/Mob.java
-+++ b/src/main/java/net/minecraft/world/entity/Mob.java
-@@ -65,6 +65,7 @@ import net.minecraft.world.item.ProjectileWeaponItem;
- import net.minecraft.world.item.SpawnEggItem;
- import net.minecraft.world.item.SwordItem;
- import net.minecraft.world.item.enchantment.EnchantmentHelper;
-+import net.minecraft.world.item.enchantment.Enchantments;
- import net.minecraft.world.level.GameRules;
- import net.minecraft.world.level.ItemLike;
- import net.minecraft.world.level.Level;
-@@ -134,6 +135,7 @@ public abstract class Mob extends LivingEntity implements Targeting {
- private BlockPos restrictCenter;
- private float restrictRadius;
-
-+ public int ticksSinceLastInteraction; // Purpur
- public boolean aware = true; // CraftBukkit
-
- protected Mob(EntityType extends Mob> type, Level world) {
-@@ -149,8 +151,8 @@ public abstract class Mob extends LivingEntity implements Targeting {
- this.goalSelector = new GoalSelector();
- this.targetSelector = new GoalSelector();
- // Gale end - Purpur - remove vanilla profiler
-- this.lookControl = new LookControl(this);
-- this.moveControl = new MoveControl(this);
-+ this.lookControl = new org.purpurmc.purpur.controller.LookControllerWASD(this); // Purpur
-+ this.moveControl = new org.purpurmc.purpur.controller.MoveControllerWASD(this); // Purpur
- this.jumpControl = new JumpControl(this);
- this.bodyRotationControl = this.createBodyControl();
- this.navigation = this.createNavigation(world);
-@@ -322,6 +324,7 @@ public abstract class Mob extends LivingEntity implements Targeting {
- entityliving = null;
- }
- }
-+ if (entityliving instanceof ServerPlayer) this.ticksSinceLastInteraction = 0; // Purpur
- this.target = entityliving;
- return true;
- // CraftBukkit end
-@@ -366,8 +369,28 @@ public abstract class Mob extends LivingEntity implements Targeting {
- this.resetAmbientSoundTime();
- this.playAmbientSound();
- }
-+ incrementTicksSinceLastInteraction(); // Purpur
- }
-
-+ // Purpur start
-+ private void incrementTicksSinceLastInteraction() {
-+ ++this.ticksSinceLastInteraction;
-+ if (getRider() != null) {
-+ this.ticksSinceLastInteraction = 0;
-+ return;
-+ }
-+ if (this.level.purpurConfig.entityLifeSpan <= 0) {
-+ return; // feature disabled
-+ }
-+ if (!this.removeWhenFarAway(0) || isPersistenceRequired() || requiresCustomPersistence() || hasCustomName()) {
-+ return; // mob persistent
-+ }
-+ if (this.ticksSinceLastInteraction > this.level.purpurConfig.entityLifeSpan) {
-+ this.discard();
-+ }
-+ }
-+ // Purpur end
-+
- @Override
- protected void playHurtSound(DamageSource source) {
- this.resetAmbientSoundTime();
-@@ -557,6 +580,7 @@ public abstract class Mob extends LivingEntity implements Targeting {
- }
-
- nbt.putBoolean("Bukkit.Aware", this.aware); // CraftBukkit
-+ nbt.putInt("Purpur.ticksSinceLastInteraction", this.ticksSinceLastInteraction); // Purpur
- }
-
- @Override
-@@ -627,6 +651,11 @@ public abstract class Mob extends LivingEntity implements Targeting {
- this.aware = nbt.getBoolean("Bukkit.Aware");
- }
- // CraftBukkit end
-+ // Purpur start
-+ if (nbt.contains("Purpur.ticksSinceLastInteraction")) {
-+ this.ticksSinceLastInteraction = nbt.getInt("Purpur.ticksSinceLastInteraction");
-+ }
-+ // Purpur end
- }
-
- @Override
-@@ -670,7 +699,7 @@ public abstract class Mob extends LivingEntity implements Targeting {
- @Override
- public void aiStep() {
- super.aiStep();
-- if (!this.level.isClientSide && this.canPickUpLoot() && this.isAlive() && !this.dead && this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
-+ if (!this.level.isClientSide && this.canPickUpLoot() && this.isAlive() && !this.dead && (this.level.purpurConfig.entitiesPickUpLootBypassMobGriefing || this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) {
- Vec3i baseblockposition = this.getPickupReach();
- List list = this.level.getEntitiesOfClass(ItemEntity.class, this.getBoundingBox().inflate((double) baseblockposition.getX(), (double) baseblockposition.getY(), (double) baseblockposition.getZ()));
- Iterator iterator = list.iterator();
-@@ -1140,6 +1169,12 @@ public abstract class Mob extends LivingEntity implements Targeting {
-
- }
-
-+ // Purpur start
-+ public static @Nullable EquipmentSlot getSlotForDispenser(ItemStack itemstack) {
-+ return EnchantmentHelper.getItemEnchantmentLevel(Enchantments.BINDING_CURSE, itemstack) > 0 ? null : getEquipmentSlotForItem(itemstack);
-+ }
-+ // Purpur end
-+
- @Nullable
- public static Item getEquipmentForSlot(EquipmentSlot equipmentSlot, int equipmentLevel) {
- switch (equipmentSlot) {
-@@ -1234,7 +1269,7 @@ public abstract class Mob extends LivingEntity implements Targeting {
- RandomSource randomsource = world.getRandom();
-
- this.getAttribute(Attributes.FOLLOW_RANGE).addPermanentModifier(new AttributeModifier("Random spawn bonus", randomsource.triangle(0.0D, 0.11485000000000001D), AttributeModifier.Operation.MULTIPLY_BASE));
-- if (randomsource.nextFloat() < 0.05F) {
-+ if (randomsource.nextFloat() < world.getLevel().purpurConfig.entityLeftHandedChance) { // Purpur
- this.setLeftHanded(true);
- } else {
- this.setLeftHanded(false);
-@@ -1282,6 +1317,7 @@ public abstract class Mob extends LivingEntity implements Targeting {
- if (!this.isAlive()) {
- return InteractionResult.PASS;
- } else if (this.getLeashHolder() == player) {
-+ if (hand == InteractionHand.OFF_HAND && (level.purpurConfig.villagerCanBeLeashed || level.purpurConfig.wanderingTraderCanBeLeashed) && this instanceof net.minecraft.world.entity.npc.AbstractVillager) return InteractionResult.CONSUME; // Purpur
- // CraftBukkit start - fire PlayerUnleashEntityEvent
- // Paper start - drop leash variable
- org.bukkit.event.player.PlayerUnleashEntityEvent event = CraftEventFactory.callPlayerUnleashEntityEvent(this, player, hand, !player.getAbilities().instabuild);
-@@ -1355,7 +1391,7 @@ public abstract class Mob extends LivingEntity implements Targeting {
- protected void onOffspringSpawnedFromEgg(Player player, Mob child) {}
-
- protected InteractionResult mobInteract(Player player, InteractionHand hand) {
-- return InteractionResult.PASS;
-+ return tryRide(player, hand); // Purpur
- }
-
- public boolean isWithinRestriction() {
-@@ -1661,6 +1697,7 @@ public abstract class Mob extends LivingEntity implements Targeting {
- this.setLastHurtMob(target);
- }
-
-+ if (target instanceof ServerPlayer) this.ticksSinceLastInteraction = 0; // Purpur
- return flag;
- }
-
-@@ -1676,34 +1713,8 @@ public abstract class Mob extends LivingEntity implements Targeting {
-
- }
-
-- // Gale start - JettPack - optimize sun burn tick - cache eye blockpos
-- private BlockPos cached_eye_blockpos;
-- private int cached_position_hashcode;
-- // Gale end - JettPack - optimize sun burn tick - cache eye blockpos
--
- public boolean isSunBurnTick() {
-- if (this.level.isDay() && !this.level.isClientSide) {
-- // Gale start - JettPack - optimize sun burn tick - optimizations and cache eye blockpos
-- int positionHashCode = this.position.hashCode();
-- if (this.cached_position_hashcode != positionHashCode) {
-- this.cached_eye_blockpos = BlockPos.containing(this.getX(), this.getEyeY(), this.getZ());
-- this.cached_position_hashcode = positionHashCode;
-- }
--
-- float f = this.getLightLevelDependentMagicValue(cached_eye_blockpos); // Pass BlockPos to getBrightness
--
-- // Check brightness first
-- if (f <= 0.5F) return false;
-- if (this.random.nextFloat() * 30.0F >= (f - 0.4F) * 2.0F) return false;
-- // Gale end - JettPack - optimize sun burn tick - optimizations and cache eye blockpos
-- boolean flag = this.isInWaterRainOrBubble() || this.isInPowderSnow || this.wasInPowderSnow;
--
-- if (!flag && this.level.canSeeSky(this.cached_eye_blockpos)) { // Gale - JettPack - optimize sun burn tick - optimizations and cache eye blockpos
-- return true;
-- }
-- }
--
-- return false;
-+ return super.isSunBurnTick(); // Purpur - moved contents to Entity
- }
-
- @Override
-@@ -1747,4 +1758,56 @@ public abstract class Mob extends LivingEntity implements Targeting {
-
- return itemmonsteregg == null ? null : new ItemStack(itemmonsteregg);
- }
-+
-+ // Purpur start
-+ 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())) {
-+ player.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
- }
-diff --git a/src/main/java/net/minecraft/world/entity/Shearable.java b/src/main/java/net/minecraft/world/entity/Shearable.java
-index 5e8cc5cfac8888628c6d513148f41be09ca65a2c..a089fc61ec09be6b7490375489178dc6ba5a644b 100644
---- a/src/main/java/net/minecraft/world/entity/Shearable.java
-+++ b/src/main/java/net/minecraft/world/entity/Shearable.java
-@@ -3,7 +3,13 @@ package net.minecraft.world.entity;
- import net.minecraft.sounds.SoundSource;
-
- public interface Shearable {
-- void shear(SoundSource shearedSoundCategory);
-+ // Purpur start
-+ default void shear(SoundSource shearedSoundCategory) {
-+ shear(shearedSoundCategory, 0);
-+ }
-+
-+ void shear(SoundSource shearedSoundCategory, int looting);
-+ // Purpur end
-
- boolean readyForShearing();
- }
-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 c6f4e9e465e24a37b773b348feb24feb0ac3adf7..d71958fbad160719bc574c64d323dcb34aaec645 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
-@@ -26,15 +26,23 @@ public class AttributeMap {
- private final Set dirtyAttributes = new ReferenceOpenHashSet<>(0);
- // Gale end - Lithium - replace AI attributes with optimized collections
- private final AttributeSupplier supplier;
-+ private final net.minecraft.world.entity.LivingEntity entity; // Purpur
- private final java.util.function.Function createInstance; // Gale - Airplane - reduce entity allocations
-
- public AttributeMap(AttributeSupplier defaultAttributes) {
-+ // Purpur start
-+ this(defaultAttributes, null);
-+ }
-+
-+ public AttributeMap(AttributeSupplier defaultAttributes, net.minecraft.world.entity.LivingEntity entity) {
-+ this.entity = entity;
-+ // Purpur end
- this.supplier = defaultAttributes;
- this.createInstance = attribute -> this.supplier.createInstance(this::onAttributeModified, attribute); // Gale - Airplane - reduce entity allocations
- }
-
- private void onAttributeModified(AttributeInstance instance) {
-- if (instance.getAttribute().isClientSyncable()) {
-+ if (instance.getAttribute().isClientSyncable() && (entity == null || entity.shouldSendAttribute(instance.getAttribute()))) { // Purpur
- this.dirtyAttributes.add(instance);
- }
-
-@@ -46,7 +54,7 @@ public class AttributeMap {
-
- public Collection getSyncableAttributes() {
- return this.attributes.values().stream().filter((attribute) -> {
-- return attribute.getAttribute().isClientSyncable();
-+ return attribute.getAttribute().isClientSyncable() && (entity == null || entity.shouldSendAttribute(attribute.getAttribute())); // Purpur
- }).collect(Collectors.toList());
- }
-
-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 8a720f9ae81d7ea856e28cb27a66adcf04bcb0eb..e0b70d9732a2b7d96999b7e4a497ffa1d8cf86a7 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
-@@ -80,7 +80,88 @@ import org.slf4j.Logger;
-
- public class DefaultAttributes {
- private static final Logger LOGGER = LogUtils.getLogger();
-- private static final Map, AttributeSupplier> SUPPLIERS = ImmutableMap., AttributeSupplier>builder().put(EntityType.ALLAY, Allay.createAttributes().build()).put(EntityType.ARMOR_STAND, LivingEntity.createLivingAttributes().build()).put(EntityType.AXOLOTL, Axolotl.createAttributes().build()).put(EntityType.BAT, Bat.createAttributes().build()).put(EntityType.BEE, Bee.createAttributes().build()).put(EntityType.BLAZE, Blaze.createAttributes().build()).put(EntityType.CAT, Cat.createAttributes().build()).put(EntityType.CAMEL, Camel.createAttributes().build()).put(EntityType.CAVE_SPIDER, CaveSpider.createCaveSpider().build()).put(EntityType.CHICKEN, Chicken.createAttributes().build()).put(EntityType.COD, AbstractFish.createAttributes().build()).put(EntityType.COW, Cow.createAttributes().build()).put(EntityType.CREEPER, Creeper.createAttributes().build()).put(EntityType.DOLPHIN, Dolphin.createAttributes().build()).put(EntityType.DONKEY, AbstractChestedHorse.createBaseChestedHorseAttributes().build()).put(EntityType.DROWNED, Zombie.createAttributes().build()).put(EntityType.ELDER_GUARDIAN, ElderGuardian.createAttributes().build()).put(EntityType.ENDERMAN, EnderMan.createAttributes().build()).put(EntityType.ENDERMITE, Endermite.createAttributes().build()).put(EntityType.ENDER_DRAGON, EnderDragon.createAttributes().build()).put(EntityType.EVOKER, Evoker.createAttributes().build()).put(EntityType.FOX, Fox.createAttributes().build()).put(EntityType.FROG, Frog.createAttributes().build()).put(EntityType.GHAST, Ghast.createAttributes().build()).put(EntityType.GIANT, Giant.createAttributes().build()).put(EntityType.GLOW_SQUID, GlowSquid.createAttributes().build()).put(EntityType.GOAT, Goat.createAttributes().build()).put(EntityType.GUARDIAN, Guardian.createAttributes().build()).put(EntityType.HOGLIN, Hoglin.createAttributes().build()).put(EntityType.HORSE, AbstractHorse.createBaseHorseAttributes().build()).put(EntityType.HUSK, Zombie.createAttributes().build()).put(EntityType.ILLUSIONER, Illusioner.createAttributes().build()).put(EntityType.IRON_GOLEM, IronGolem.createAttributes().build()).put(EntityType.LLAMA, Llama.createAttributes().build()).put(EntityType.MAGMA_CUBE, MagmaCube.createAttributes().build()).put(EntityType.MOOSHROOM, Cow.createAttributes().build()).put(EntityType.MULE, AbstractChestedHorse.createBaseChestedHorseAttributes().build()).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.PIG, Pig.createAttributes().build()).put(EntityType.PIGLIN, Piglin.createAttributes().build()).put(EntityType.PIGLIN_BRUTE, PiglinBrute.createAttributes().build()).put(EntityType.PILLAGER, Pillager.createAttributes().build()).put(EntityType.PLAYER, Player.createAttributes().build()).put(EntityType.POLAR_BEAR, PolarBear.createAttributes().build()).put(EntityType.PUFFERFISH, AbstractFish.createAttributes().build()).put(EntityType.RABBIT, Rabbit.createAttributes().build()).put(EntityType.RAVAGER, Ravager.createAttributes().build()).put(EntityType.SALMON, AbstractFish.createAttributes().build()).put(EntityType.SHEEP, Sheep.createAttributes().build()).put(EntityType.SHULKER, Shulker.createAttributes().build()).put(EntityType.SILVERFISH, Silverfish.createAttributes().build()).put(EntityType.SKELETON, AbstractSkeleton.createAttributes().build()).put(EntityType.SKELETON_HORSE, SkeletonHorse.createAttributes().build()).put(EntityType.SLIME, Monster.createMonsterAttributes().build()).put(EntityType.SNIFFER, Sniffer.createAttributes().build()).put(EntityType.SNOW_GOLEM, SnowGolem.createAttributes().build()).put(EntityType.SPIDER, Spider.createAttributes().build()).put(EntityType.SQUID, Squid.createAttributes().build()).put(EntityType.STRAY, AbstractSkeleton.createAttributes().build()).put(EntityType.STRIDER, Strider.createAttributes().build()).put(EntityType.TADPOLE, Tadpole.createAttributes().build()).put(EntityType.TRADER_LLAMA, Llama.createAttributes().build()).put(EntityType.TROPICAL_FISH, AbstractFish.createAttributes().build()).put(EntityType.TURTLE, Turtle.createAttributes().build()).put(EntityType.VEX, Vex.createAttributes().build()).put(EntityType.VILLAGER, Villager.createAttributes().build()).put(EntityType.VINDICATOR, Vindicator.createAttributes().build()).put(EntityType.WARDEN, Warden.createAttributes().build()).put(EntityType.WANDERING_TRADER, Mob.createMobAttributes().build()).put(EntityType.WITCH, Witch.createAttributes().build()).put(EntityType.WITHER, WitherBoss.createAttributes().build()).put(EntityType.WITHER_SKELETON, AbstractSkeleton.createAttributes().build()).put(EntityType.WOLF, Wolf.createAttributes().build()).put(EntityType.ZOGLIN, Zoglin.createAttributes().build()).put(EntityType.ZOMBIE, Zombie.createAttributes().build()).put(EntityType.ZOMBIE_HORSE, ZombieHorse.createAttributes().build()).put(EntityType.ZOMBIE_VILLAGER, Zombie.createAttributes().build()).put(EntityType.ZOMBIFIED_PIGLIN, ZombifiedPiglin.createAttributes().build()).build();
-+ private static final Map, AttributeSupplier> SUPPLIERS = ImmutableMap., AttributeSupplier>builder()
-+ .put(EntityType.ALLAY, Allay.createAttributes().build())
-+ .put(EntityType.ARMOR_STAND, LivingEntity.createLivingAttributes().build())
-+ .put(EntityType.AXOLOTL, Axolotl.createAttributes().build())
-+ .put(EntityType.BAT, Bat.createAttributes().build())
-+ .put(EntityType.BEE, Bee.createAttributes().build())
-+ .put(EntityType.BLAZE, Blaze.createAttributes().build())
-+ .put(EntityType.CAT, Cat.createAttributes().build())
-+ .put(EntityType.CAMEL, Camel.createAttributes().build())
-+ .put(EntityType.CAVE_SPIDER, CaveSpider.createCaveSpider().build())
-+ .put(EntityType.CHICKEN, Chicken.createAttributes().build())
-+ .put(EntityType.COD, AbstractFish.createAttributes().build())
-+ .put(EntityType.COW, Cow.createAttributes().build())
-+ .put(EntityType.CREEPER, Creeper.createAttributes().build())
-+ .put(EntityType.DOLPHIN, Dolphin.createAttributes().build())
-+ .put(EntityType.DONKEY, AbstractChestedHorse.createBaseChestedHorseAttributes().build())
-+ .put(EntityType.DROWNED, Zombie.createAttributes().build())
-+ .put(EntityType.ELDER_GUARDIAN, ElderGuardian.createAttributes().build())
-+ .put(EntityType.ENDERMAN, EnderMan.createAttributes().build())
-+ .put(EntityType.ENDERMITE, Endermite.createAttributes().build())
-+ .put(EntityType.ENDER_DRAGON, EnderDragon.createAttributes().build())
-+ .put(EntityType.EVOKER, Evoker.createAttributes().build())
-+ .put(EntityType.FOX, Fox.createAttributes().build())
-+ .put(EntityType.FROG, Frog.createAttributes().build())
-+ .put(EntityType.GHAST, Ghast.createAttributes().build())
-+ .put(EntityType.GIANT, Giant.createAttributes().build())
-+ .put(EntityType.GLOW_SQUID, GlowSquid.createAttributes().build())
-+ .put(EntityType.GOAT, Goat.createAttributes().build())
-+ .put(EntityType.GUARDIAN, Guardian.createAttributes().build())
-+ .put(EntityType.HOGLIN, Hoglin.createAttributes().build())
-+ .put(EntityType.HORSE, AbstractHorse.createBaseHorseAttributes().build())
-+ .put(EntityType.HUSK, Zombie.createAttributes().build())
-+ .put(EntityType.ILLUSIONER, Illusioner.createAttributes().build())
-+ .put(EntityType.IRON_GOLEM, IronGolem.createAttributes().build())
-+ .put(EntityType.LLAMA, Llama.createAttributes().build())
-+ .put(EntityType.MAGMA_CUBE, MagmaCube.createAttributes().build())
-+ .put(EntityType.MOOSHROOM, Cow.createAttributes().build())
-+ .put(EntityType.MULE, AbstractChestedHorse.createBaseChestedHorseAttributes().build())
-+ .put(EntityType.OCELOT, Ocelot.createAttributes().build())
-+ .put(EntityType.PANDA, Panda.createAttributes().build())
-+ .put(EntityType.PARROT, Parrot.createAttributes().build())
-+ .put(EntityType.PHANTOM, net.minecraft.world.entity.monster.Phantom.createAttributes().build()) // Purpur
-+ .put(EntityType.PIG, Pig.createAttributes().build())
-+ .put(EntityType.PIGLIN, Piglin.createAttributes().build())
-+ .put(EntityType.PIGLIN_BRUTE, PiglinBrute.createAttributes().build())
-+ .put(EntityType.PILLAGER, Pillager.createAttributes().build())
-+ .put(EntityType.PLAYER, Player.createAttributes().build())
-+ .put(EntityType.POLAR_BEAR, PolarBear.createAttributes().build())
-+ .put(EntityType.PUFFERFISH, AbstractFish.createAttributes().build())
-+ .put(EntityType.RABBIT, Rabbit.createAttributes().build())
-+ .put(EntityType.RAVAGER, Ravager.createAttributes().build())
-+ .put(EntityType.SALMON, AbstractFish.createAttributes().build())
-+ .put(EntityType.SHEEP, Sheep.createAttributes().build())
-+ .put(EntityType.SHULKER, Shulker.createAttributes().build())
-+ .put(EntityType.SILVERFISH, Silverfish.createAttributes().build())
-+ .put(EntityType.SKELETON, AbstractSkeleton.createAttributes().build())
-+ .put(EntityType.SKELETON_HORSE, SkeletonHorse.createAttributes().build())
-+ .put(EntityType.SLIME, Monster.createMonsterAttributes().build())
-+ .put(EntityType.SNIFFER, Sniffer.createAttributes().build())
-+ .put(EntityType.SNOW_GOLEM, SnowGolem.createAttributes().build())
-+ .put(EntityType.SPIDER, Spider.createAttributes().build())
-+ .put(EntityType.SQUID, Squid.createAttributes().build())
-+ .put(EntityType.STRAY, AbstractSkeleton.createAttributes().build())
-+ .put(EntityType.STRIDER, Strider.createAttributes().build())
-+ .put(EntityType.TADPOLE, Tadpole.createAttributes().build())
-+ .put(EntityType.TRADER_LLAMA, Llama.createAttributes().build())
-+ .put(EntityType.TROPICAL_FISH, AbstractFish.createAttributes().build())
-+ .put(EntityType.TURTLE, Turtle.createAttributes().build())
-+ .put(EntityType.VEX, Vex.createAttributes().build())
-+ .put(EntityType.VILLAGER, Villager.createAttributes().build())
-+ .put(EntityType.VINDICATOR, Vindicator.createAttributes().build())
-+ .put(EntityType.WARDEN, Warden.createAttributes().build())
-+ .put(EntityType.WANDERING_TRADER, Mob.createMobAttributes().build())
-+ .put(EntityType.WITCH, Witch.createAttributes().build())
-+ .put(EntityType.WITHER, WitherBoss.createAttributes().build())
-+ .put(EntityType.WITHER_SKELETON, AbstractSkeleton.createAttributes().build())
-+ .put(EntityType.WOLF, Wolf.createAttributes().build())
-+ .put(EntityType.ZOGLIN, Zoglin.createAttributes().build())
-+ .put(EntityType.ZOMBIE, Zombie.createAttributes().build())
-+ .put(EntityType.ZOMBIE_HORSE, ZombieHorse.createAttributes().build())
-+ .put(EntityType.ZOMBIE_VILLAGER, Zombie.createAttributes().build())
-+ .put(EntityType.ZOMBIFIED_PIGLIN, ZombifiedPiglin.createAttributes().build()).build();
-
- public static AttributeSupplier getSupplier(EntityType extends LivingEntity> type) {
- return SUPPLIERS.get(type);
-diff --git a/src/main/java/net/minecraft/world/entity/ai/attributes/RangedAttribute.java b/src/main/java/net/minecraft/world/entity/ai/attributes/RangedAttribute.java
-index f0703302e7dbbda88de8c648d20d87c55ed9b1e0..a913ebabaa5f443afa987b972355a8f8d1723c78 100644
---- a/src/main/java/net/minecraft/world/entity/ai/attributes/RangedAttribute.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/attributes/RangedAttribute.java
-@@ -29,6 +29,7 @@ public class RangedAttribute extends Attribute {
-
- @Override
- public double sanitizeValue(double value) {
-+ if (!org.purpurmc.purpur.PurpurConfig.clampAttributes) return Double.isNaN(value) ? this.minValue : value; // Purpur
- return Double.isNaN(value) ? this.minValue : Mth.clamp(value, this.minValue, this.maxValue);
- }
- }
-diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java b/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java
-index 7ad71f2c139c2288b49d6b0fde3f8b8013f5e095..2dca8e45b9b1f5451db2734cba4c2b03c9dd303b 100644
---- a/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java
-@@ -34,17 +34,19 @@ public class HarvestFarmland extends Behavior {
- private long nextOkStartTime;
- private int timeWorkedSoFar;
- private final List validFarmlandAroundVillager = Lists.newArrayList();
-+ private boolean clericWartFarmer = false; // Purpur
-
- public HarvestFarmland() {
- super(ImmutableMap.of(MemoryModuleType.LOOK_TARGET, MemoryStatus.VALUE_ABSENT, MemoryModuleType.WALK_TARGET, MemoryStatus.VALUE_ABSENT, MemoryModuleType.SECONDARY_JOB_SITE, MemoryStatus.VALUE_PRESENT));
- }
-
- protected boolean checkExtraStartConditions(ServerLevel world, Villager entity) {
-- if (!world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
-+ if (!world.purpurConfig.villagerBypassMobGriefing && !world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur
- return false;
-- } else if (entity.getVillagerData().getProfession() != VillagerProfession.FARMER) {
-+ } else if (entity.getVillagerData().getProfession() != VillagerProfession.FARMER && !(world.purpurConfig.villagerClericsFarmWarts && entity.getVillagerData().getProfession() == VillagerProfession.CLERIC)) { // Purpur
- return false;
- } else {
-+ if (!this.clericWartFarmer && entity.getVillagerData().getProfession() == VillagerProfession.CLERIC) this.clericWartFarmer = true; // Purpur
- BlockPos.MutableBlockPos blockposition_mutableblockposition = entity.blockPosition().mutable();
-
- this.validFarmlandAroundVillager.clear();
-@@ -75,6 +77,7 @@ public class HarvestFarmland extends Behavior {
- Block block = iblockdata.getBlock();
- Block block1 = world.getBlockState(pos.below()).getBlock();
-
-+ if (this.clericWartFarmer) return block == Blocks.NETHER_WART && iblockdata.getValue(net.minecraft.world.level.block.NetherWartBlock.AGE) == 3 || iblockdata.isAir() && block1 == Blocks.SOUL_SAND; // Purpur
- return block instanceof CropBlock && ((CropBlock) block).isMaxAge(iblockdata) || iblockdata.isAir() && block1 instanceof FarmBlock;
- }
-
-@@ -100,7 +103,7 @@ public class HarvestFarmland extends Behavior {
- Block block = iblockdata.getBlock();
- Block block1 = world.getBlockState(this.aboveFarmlandPos.below()).getBlock();
-
-- if (block instanceof CropBlock && ((CropBlock) block).isMaxAge(iblockdata)) {
-+ if (block instanceof CropBlock && ((CropBlock) block).isMaxAge(iblockdata) && !this.clericWartFarmer || this.clericWartFarmer && block == Blocks.NETHER_WART && iblockdata.getValue(net.minecraft.world.level.block.NetherWartBlock.AGE) == 3) { // Purpur
- // CraftBukkit start
- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, this.aboveFarmlandPos, Blocks.AIR.defaultBlockState()).isCancelled()) {
- world.destroyBlock(this.aboveFarmlandPos, true, entity);
-@@ -108,7 +111,7 @@ public class HarvestFarmland extends Behavior {
- // CraftBukkit end
- }
-
-- if (iblockdata.isAir() && block1 instanceof FarmBlock && entity.hasFarmSeeds()) {
-+ if (iblockdata.isAir() && (block1 instanceof FarmBlock && !this.clericWartFarmer || this.clericWartFarmer && block1 == Blocks.SOUL_SAND) && entity.hasFarmSeeds()) { // Purpur
- SimpleContainer inventorysubcontainer = entity.getInventory();
-
- for (int j = 0; j < inventorysubcontainer.getContainerSize(); ++j) {
-@@ -119,6 +122,12 @@ public class HarvestFarmland extends Behavior {
- BlockState iblockdata1;
-
- // CraftBukkit start
-+ // Purpur start
-+ if (this.clericWartFarmer && itemstack.getItem() == Items.NETHER_WART) {
-+ iblockdata1 = Blocks.NETHER_WART.defaultBlockState();
-+ flag = true;
-+ } else
-+ // Purpur end
- if (itemstack.is(Items.WHEAT_SEEDS)) {
- iblockdata1 = Blocks.WHEAT.defaultBlockState();
- flag = true;
-@@ -145,7 +154,7 @@ public class HarvestFarmland extends Behavior {
- }
-
- if (flag) {
-- world.playSound((Player) null, (double) this.aboveFarmlandPos.getX(), (double) this.aboveFarmlandPos.getY(), (double) this.aboveFarmlandPos.getZ(), SoundEvents.CROP_PLANTED, SoundSource.BLOCKS, 1.0F, 1.0F);
-+ world.playSound((Player) null, (double) this.aboveFarmlandPos.getX(), (double) this.aboveFarmlandPos.getY(), (double) this.aboveFarmlandPos.getZ(), this.clericWartFarmer ? SoundEvents.NETHER_WART_PLANTED : SoundEvents.CROP_PLANTED, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur
- itemstack.shrink(1);
- if (itemstack.isEmpty()) {
- inventorysubcontainer.setItem(j, ItemStack.EMPTY);
-diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java b/src/main/java/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java
-index c3fb86dc3d94d3a0d2464f2dbb83cda2fb9f7bbe..fd77dd0c0bfaba57e5bdfd13f7a90241ecdf813a 100644
---- a/src/main/java/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/InteractWithDoor.java
-@@ -57,7 +57,7 @@ public class InteractWithDoor {
-
- if (iblockdata.is(BlockTags.WOODEN_DOORS, (blockbase_blockdata) -> {
- return blockbase_blockdata.getBlock() instanceof DoorBlock;
-- })) {
-+ }) && !DoorBlock.requiresRedstone(entityliving.level, iblockdata, blockposition)) { // Purpur
- DoorBlock blockdoor = (DoorBlock) iblockdata.getBlock();
-
- if (!blockdoor.isOpen(iblockdata)) {
-@@ -79,7 +79,7 @@ public class InteractWithDoor {
-
- if (iblockdata1.is(BlockTags.WOODEN_DOORS, (blockbase_blockdata) -> {
- return blockbase_blockdata.getBlock() instanceof DoorBlock;
-- })) {
-+ }) && !DoorBlock.requiresRedstone(entityliving.level, iblockdata, blockposition1)) { // Purpur
- DoorBlock blockdoor1 = (DoorBlock) iblockdata1.getBlock();
-
- if (!blockdoor1.isOpen(iblockdata1)) {
-@@ -122,7 +122,7 @@ public class InteractWithDoor {
-
- if (!iblockdata.is(BlockTags.WOODEN_DOORS, (blockbase_blockdata) -> {
- return blockbase_blockdata.getBlock() instanceof DoorBlock;
-- })) {
-+ }) || DoorBlock.requiresRedstone(entity.level, iblockdata, blockposition)) { // Purpur
- iterator.remove();
- } else {
- DoorBlock blockdoor = (DoorBlock) iblockdata.getBlock();
-diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java b/src/main/java/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java
-index 98373e013748817209b811d4adbb40a8787242a6..567b501f4de7556e55e2418d2f5700b4e4265235 100644
---- a/src/main/java/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/ShowTradesToPlayer.java
-@@ -42,6 +42,7 @@ public class ShowTradesToPlayer extends Behavior {
-
- @Override
- public boolean canStillUse(ServerLevel world, Villager entity, long time) {
-+ if (!entity.level.purpurConfig.villagerDisplayTradeItem) return false; // Purpur
- return this.checkExtraStartConditions(world, entity) && this.lookTime > 0 && entity.getBrain().getMemory(MemoryModuleType.INTERACTION_TARGET).isPresent();
- }
-
-diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java b/src/main/java/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java
-index 94fb9bcf601832ee934331c0376de8707b5043c5..e49e6b9b7b0f1fab6a8888fcfd67d709d5a0dbd7 100644
---- a/src/main/java/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/TradeWithVillager.java
-@@ -3,6 +3,8 @@ package net.minecraft.world.entity.ai.behavior;
- import com.google.common.collect.ImmutableMap;
-
- import java.util.Arrays;
-+
-+import com.google.common.collect.ImmutableSet;
- import net.minecraft.server.level.ServerLevel;
- import net.minecraft.world.SimpleContainer;
- import net.minecraft.world.entity.EntityType;
-@@ -60,6 +62,12 @@ public class TradeWithVillager extends Behavior {
- throwHalfStack(entity, WHEAT_SINGLETON_ARRAY, villager); // Gale - optimize villager data storage
- }
-
-+ // Purpur start
-+ if (world.purpurConfig.villagerClericsFarmWarts && world.purpurConfig.villagerClericFarmersThrowWarts && entity.getVillagerData().getProfession() == VillagerProfession.CLERIC && entity.getInventory().countItem(Items.NETHER_WART) > Items.NETHER_WART.getMaxStackSize() / 2) {
-+ throwHalfStack(entity, ImmutableSet.of(Items.NETHER_WART).toArray(new Item[0]), villager);
-+ }
-+ // Purpur end
-+
- // Gale start - optimize villager data storage
- if (this.trades != null && entity.getInventory().hasAnyOf(this.trades)) {
- throwHalfStack(entity, this.trades, villager);
-diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java
-index cd7a90ec1073b2b452ca70decefe6a594445003b..47672e48c1cae73cffe532d622b296343fc12ef0 100644
---- a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerGoalPackages.java
-@@ -30,8 +30,13 @@ public class VillagerGoalPackages {
- }
-
- public static ImmutableList>> getWorkPackage(VillagerProfession profession, float speed) {
-+ // Purpur start
-+ return getWorkPackage(profession, speed, false);
-+ }
-+ public static ImmutableList>> getWorkPackage(VillagerProfession profession, float speed, boolean clericsFarmWarts) {
-+ // Purpur end
- WorkAtPoi workAtPoi;
-- if (profession == VillagerProfession.FARMER) {
-+ if (profession == VillagerProfession.FARMER || (clericsFarmWarts && profession == VillagerProfession.CLERIC)) { // Purpur
- workAtPoi = new WorkAtComposter();
- } else {
- workAtPoi = new WorkAtPoi();
-diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java
-index 0951c04533e7c39b969d041271684355770b53c2..02d4ba2ccdce99ca97614baa7c8e49213126af96 100644
---- a/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/VillagerMakeLove.java
-@@ -123,8 +123,10 @@ public class VillagerMakeLove extends Behavior {
- return Optional.empty();
- }
- // CraftBukkit end
-- parent.setAge(6000);
-- partner.setAge(6000);
-+ // Purpur start
-+ parent.setAge(world.purpurConfig.villagerBreedingTicks);
-+ partner.setAge(world.purpurConfig.villagerBreedingTicks);
-+ // Purpur end
- world.addFreshEntityWithPassengers(entityvillager2, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING); // CraftBukkit - added SpawnReason
- world.broadcastEntityEvent(entityvillager2, (byte) 12);
- return Optional.of(entityvillager2);
-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 3d7586142aa5116964e4fffc6198f55fc6da324b..b4fb0af5bffffb9f0de3a2452c22b09fe92d7129 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
-+ 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
-+
- 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 7df56705a4a0de2dc4ff7ab133fc26612c219162..60d21d6171b9af20a4c6fcc0d564a31aaa4ecdba 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
- 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
- if (this.lookAtCooldown > 0) {
- --this.lookAtCooldown;
- this.getYRotD().ifPresent((yaw) -> {
-@@ -32,9 +32,9 @@ public class SmoothSwimmingLookControl extends LookControl {
- }
-
- float f = Mth.wrapDegrees(this.mob.yHeadRot - this.mob.yBodyRot);
-- if (f < (float)(-this.maxYRotFromCenter)) {
-+ if (f < (float) (-this.maxYRotFromCenter)) {
- this.mob.yBodyRot -= 4.0F;
-- } else if (f > (float)this.maxYRotFromCenter) {
-+ } else if (f > (float) this.maxYRotFromCenter) {
- this.mob.yBodyRot += 4.0F;
- }
-
-diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java
-index 529435cf648d61f80a37f041cee3c6fc0b74ceb6..6c7195c93b5968845da35450e80022c70839487d 100644
---- a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java
-@@ -32,7 +32,7 @@ public class BreakDoorGoal extends DoorInteractGoal {
-
- @Override
- public boolean canUse() {
-- return !super.canUse() ? false : (!this.mob.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? false : this.isValidDifficulty(this.mob.level.getDifficulty()) && !this.isOpen());
-+ return !super.canUse() ? false : ((!this.mob.level.purpurConfig.zombieBypassMobGriefing && !this.mob.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) ? false : this.isValidDifficulty(this.mob.level.getDifficulty()) && !this.isOpen()); // Purpur
- }
-
- @Override
-diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java
-index cb8fc8a88c14d2374a0bbe35aa1c2056d625b71c..34edda847e6c957eb0d064aa510c28153727a8ac 100644
---- a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java
-@@ -75,7 +75,7 @@ public class EatBlockGoal extends Goal {
-
- if (EatBlockGoal.IS_TALL_GRASS.test(this.level.getBlockState(blockposition))) {
- // CraftBukkit
-- if (!CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition, Blocks.AIR.defaultBlockState(), !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)).isCancelled()) {
-+ if (!CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition, Blocks.AIR.defaultBlockState(), !this.level.purpurConfig.sheepBypassMobGriefing && !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)).isCancelled()) { // Purpur
- this.level.destroyBlock(blockposition, false);
- }
-
-@@ -85,7 +85,7 @@ public class EatBlockGoal extends Goal {
-
- if (this.level.getBlockState(blockposition1).is(Blocks.GRASS_BLOCK)) {
- // CraftBukkit
-- if (!CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.DIRT.defaultBlockState(), !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)).isCancelled()) { // Paper - Fix wrong block state
-+ if (!CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.DIRT.defaultBlockState(), !this.level.purpurConfig.sheepBypassMobGriefing && !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)).isCancelled()) { // Paper - Fix wrong block state // Purpur
- this.level.levelEvent(2001, blockposition1, Block.getId(Blocks.GRASS_BLOCK.defaultBlockState()));
- this.level.setBlock(blockposition1, Blocks.DIRT.defaultBlockState(), 2);
- }
-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 721971f7618751a2e95f1c49fdc48a9c0c672cab..ad30f2d678cfc4b0d693e84e6e152c63b1b3cbb8 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.level.purpurConfig.llamaJoinCaravans || !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.0D, 4.0D, 9.0D), (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.0D) {
-diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java
-index d3e91faee8805e88d850740fb5de9e5c8288c48b..fe526ebf395ff9813b94284fc3f0142323d6a303 100644
---- a/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/goal/RemoveBlockGoal.java
-@@ -40,7 +40,7 @@ public class RemoveBlockGoal extends MoveToBlockGoal {
-
- @Override
- public boolean canUse() {
-- if (!this.removerMob.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
-+ if (!this.removerMob.level.purpurConfig.zombieBypassMobGriefing && !this.removerMob.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur
- return false;
- } else if (this.nextStartTick > 0) {
- --this.nextStartTick;
-diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java
-index 5c64905e90ccca6e0b347241ddf9cc3f71058b8e..3bd7521b131b2b40f807bdc7ab95e64cf9bcdadc 100644
---- a/src/main/java/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java
-@@ -63,7 +63,7 @@ public class RunAroundLikeCrazyGoal extends Goal {
- int j = this.horse.getMaxTemper();
-
- // CraftBukkit - fire EntityTameEvent
-- if (j > 0 && this.horse.getRandom().nextInt(j) < i && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this.horse, ((org.bukkit.craftbukkit.entity.CraftHumanEntity) this.horse.getBukkitEntity().getPassenger()).getHandle()).isCancelled()) {
-+ if ((this.horse.level.purpurConfig.alwaysTameInCreative && ((Player) entity).getAbilities().instabuild) || (j > 0 && this.horse.getRandom().nextInt(j) < i && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this.horse, ((org.bukkit.craftbukkit.entity.CraftHumanEntity) this.horse.getBukkitEntity().getPassenger()).getHandle()).isCancelled())) { // Purpur
- this.horse.tameWithName((Player) entity);
- return;
- }
-diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java
-index e241ae250f4f04a17ef2c583d00b065a4ca56a4c..02b567e4e808e1a809d285ef39e1abc54e1e6ad2 100644
---- a/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/goal/SwellGoal.java
-@@ -54,6 +54,14 @@ public class SwellGoal extends Goal {
- this.creeper.setSwellDir(-1);
- } else {
- this.creeper.setSwellDir(1);
-+ // Purpur start
-+ if (this.creeper.getLevel().purpurConfig.creeperEncircleTarget) {
-+ net.minecraft.world.phys.Vec3 relative = this.creeper.position().subtract(this.target.position());
-+ relative = relative.yRot((float) Math.PI / 3).normalize().multiply(2, 2, 2);
-+ net.minecraft.world.phys.Vec3 destination = this.target.position().add(relative);
-+ this.creeper.getNavigation().moveTo(destination.x, destination.y, destination.z, 1);
-+ }
-+ // Purpur end
- }
- }
- }
-diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/TemptGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/TemptGoal.java
-index 79bb13c5614bab1f0749c5f8f57f762c6216c564..2cbc9adc8e417def48be03d08174a5833068ec65 100644
---- a/src/main/java/net/minecraft/world/entity/ai/goal/TemptGoal.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/goal/TemptGoal.java
-@@ -62,7 +62,7 @@ public class TemptGoal extends Goal {
- }
-
- private boolean shouldFollow(LivingEntity entity) {
-- return this.items.test(entity.getMainHandItem()) || this.items.test(entity.getOffhandItem());
-+ return (this.items.test(entity.getMainHandItem()) || this.items.test(entity.getOffhandItem())) && (!(this.mob instanceof net.minecraft.world.entity.npc.Villager villager) || !villager.isSleeping()); // Purpur Fix #512
- }
-
- @Override
-diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java
-index 53b0519bbc5d52490040eaf0fe449648f021d0c2..9f88753823a762b274a888926f6a470a49930101 100644
---- a/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java
-@@ -24,6 +24,13 @@ public class SecondaryPoiSensor extends Sensor {
-
- @Override
- protected void doTick(ServerLevel world, Villager entity) {
-+ // Purpur start - make sure clerics don't wander to soul sand when the option is off
-+ Brain> brain = entity.getBrain();
-+ if (!world.purpurConfig.villagerClericsFarmWarts && entity.getVillagerData().getProfession() == net.minecraft.world.entity.npc.VillagerProfession.CLERIC) {
-+ brain.eraseMemory(MemoryModuleType.SECONDARY_JOB_SITE);
-+ return;
-+ }
-+ // Purpur end
- // Gale start - Lithium - skip secondary POI sensor if absent
- var secondaryPoi = entity.getVillagerData().getProfession().secondaryPoi();
- if (secondaryPoi == null) { // Gale - optimize villager data storage
-@@ -52,7 +59,7 @@ public class SecondaryPoiSensor extends Sensor {
- }
- }
-
-- Brain> brain = entity.getBrain();
-+ //Brain> brain = entity.getBrain(); // Purpur - moved up
- // Gale start - optimize villager data storage
- if (list != null) {
- list.trimToSize();
-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 58c3b8e622941108e46bb1b4e9eb9497d6553ab4..dfc1bb51e1c54e68301249a27326492e84ca65a5 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(targetEntity)) {
- return false;
-+ // Purpur start
-+ } else if (!targetEntity.level.purpurConfig.idleTimeoutTargetPlayer && targetEntity instanceof net.minecraft.server.level.ServerPlayer player && player.isAfk()) {
-+ return false;
-+ // Purpur end
- } else {
- if (baseEntity == null) {
- if (this.isCombat && (!targetEntity.canBeSeenAsEnemy() || targetEntity.level.getDifficulty() == Difficulty.PEACEFUL)) {
-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 c1d6ba5bddf5a473ba746975fd323ea90b60ee8d..7767d74b8ade4be078438b00b46de9af3c56e57e 100644
---- a/src/main/java/net/minecraft/world/entity/ambient/Bat.java
-+++ b/src/main/java/net/minecraft/world/entity/ambient/Bat.java
-@@ -18,6 +18,7 @@ import net.minecraft.world.entity.EntityDimensions;
- import net.minecraft.world.entity.EntityType;
- import net.minecraft.world.entity.Mob;
- import net.minecraft.world.entity.MobSpawnType;
-+import net.minecraft.world.entity.MoverType;
- import net.minecraft.world.entity.Pose;
- import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
- import net.minecraft.world.entity.ai.attributes.Attributes;
-@@ -41,12 +42,81 @@ 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
- if (!world.isClientSide) {
- this.setResting(true);
- }
-
- }
-
-+ // Purpur start
-+ @Override
-+ public boolean shouldSendAttribute(net.minecraft.world.entity.ai.attributes.Attribute attribute) { return attribute != Attributes.FLYING_SPEED; } // 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(MoverType.SELF, mot.multiply(speed, 0.25, speed));
-+ setDeltaMovement(mot.scale(0.9D));
-+ }
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.batMaxHealth);
-+ 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);
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.batTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.batAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @Override
- public boolean isFlapping() {
- return !this.isResting() && this.tickCount % Bat.TICKS_PER_FLAP == 0;
-@@ -96,7 +166,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
- }
-
- public boolean isResting() {
-@@ -128,6 +198,14 @@ public class Bat extends AmbientCreature {
-
- @Override
- protected void customServerAiStep() {
-+ // Purpur start
-+ if (getRider() != null && this.isControllable()) {
-+ Vec3 mot = getDeltaMovement();
-+ setDeltaMovement(mot.x(), mot.y() + (getVerticalMot() > 0 ? 0.07D : 0.0D), mot.z());
-+ return;
-+ }
-+ // Purpur end
-+
- super.customServerAiStep();
- BlockPos blockposition = this.blockPosition();
- BlockPos blockposition1 = blockposition.above();
-@@ -241,7 +319,7 @@ public class Bat extends AmbientCreature {
- int i = world.getMaxLocalRawBrightness(pos);
- byte b0 = 4;
-
-- if (Bat.isHalloween()) {
-+ if (Bat.isHalloween()) { // Purpur
- b0 = 7;
- } else if (random.nextBoolean()) {
- return false;
-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 1f85f34c1e50f34fb270d2fac7d307c82a550bfa..324f52edd95b5f9a498e46def8c14435cfd00abb 100644
---- a/src/main/java/net/minecraft/world/entity/animal/AbstractFish.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/AbstractFish.java
-@@ -94,7 +94,7 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable {
- @Override
- protected void registerGoals() {
- super.registerGoals();
-- this.goalSelector.addGoal(0, new PanicGoal(this, 1.25D));
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
- this.goalSelector.addGoal(2, new AvoidEntityGoal<>(this, Player.class, 8.0F, 1.6D, 1.4D, EntitySelector.NO_SPECTATORS::test));
- this.goalSelector.addGoal(4, new AbstractFish.FishSwimGoal(this));
- }
-@@ -107,7 +107,7 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable {
- @Override
- public void travel(Vec3 movementInput) {
- if (this.isEffectiveAi() && this.isInWater()) {
-- this.moveRelative(0.01F, movementInput);
-+ this.moveRelative(getRider() != null ? getSpeed() : 0.01F, movementInput); // Purpur
- this.move(MoverType.SELF, this.getDeltaMovement());
- this.setDeltaMovement(this.getDeltaMovement().scale(0.9D));
- if (this.getTarget() == null) {
-@@ -166,7 +166,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
- private final AbstractFish fish;
-
- FishMoveControl(AbstractFish owner) {
-@@ -174,14 +174,22 @@ public abstract class AbstractFish extends WaterAnimal implements Bucketable {
- this.fish = owner;
- }
-
-+ // Purpur start
- @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
-+
-+ @Override
-+ public void vanillaTick() { // Purpur
- if (this.fish.isEyeInFluid(FluidTags.WATER)) {
- this.fish.setDeltaMovement(this.fish.getDeltaMovement().add(0.0D, 0.005D, 0.0D));
- }
-
- 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
- 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/Animal.java b/src/main/java/net/minecraft/world/entity/animal/Animal.java
-index 3c4d142e982c34a23bdb5da1f51c8dcacc0532c1..2ac88f06ebb79e515cd9934ac1e3e2c8003d9e3c 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Animal.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Animal.java
-@@ -39,6 +39,7 @@ public abstract class Animal extends AgeableMob {
- @Nullable
- public UUID loveCause;
- public ItemStack breedItem; // CraftBukkit - Add breedItem variable
-+ public abstract int getPurpurBreedTime(); // Purpur
-
- protected Animal(EntityType extends Animal> type, Level world) {
- super(type, world);
-@@ -150,7 +151,7 @@ public abstract class Animal extends AgeableMob {
- if (this.isFood(itemstack)) {
- int i = this.getAge();
-
-- if (!this.level.isClientSide && i == 0 && this.canFallInLove()) {
-+ if (!this.level.isClientSide && i == 0 && this.canFallInLove() && (this.level.purpurConfig.animalBreedingCooldownSeconds <= 0 || !this.level.hasBreedingCooldown(player.getUUID(), this.getClass()))) { // Purpur
- this.usePlayerItem(player, hand, itemstack);
- this.setInLove(player);
- return InteractionResult.SUCCESS;
-@@ -237,6 +238,14 @@ public abstract class Animal extends AgeableMob {
- if (entityplayer == null && other.getLoveCause() != null) {
- entityplayer = other.getLoveCause();
- }
-+ // Purpur start
-+ if (entityplayer != null && world.purpurConfig.animalBreedingCooldownSeconds > 0) {
-+ if (world.hasBreedingCooldown(entityplayer.getUUID(), this.getClass())) {
-+ return;
-+ }
-+ world.addBreedingCooldown(entityplayer.getUUID(), this.getClass());
-+ }
-+ // Purpur end
- // CraftBukkit start - call EntityBreedEvent
- entityageable.setBaby(true);
- entityageable.moveTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F);
-@@ -253,8 +262,10 @@ public abstract class Animal extends AgeableMob {
- CriteriaTriggers.BRED_ANIMALS.trigger(entityplayer, this, other, entityageable);
- }
-
-- this.setAge(6000);
-- other.setAge(6000);
-+ // Purpur start
-+ this.setAge(this.getPurpurBreedTime());
-+ other.setAge(other.getPurpurBreedTime());
-+ // Purpur end
- this.resetLove();
- other.resetLove();
- world.addFreshEntityWithPassengers(entityageable, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING); // CraftBukkit - added SpawnReason
-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 c33e5c51839c8e6ec04c1b302127d2bf0f48664c..d47dc0c3fe8c2b80d7b7eb828a12af6eb32145e4 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Bee.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java
-@@ -43,6 +43,7 @@ import net.minecraft.world.entity.EntityType;
- import net.minecraft.world.entity.LivingEntity;
- import net.minecraft.world.entity.Mob;
- import net.minecraft.world.entity.MobType;
-+import net.minecraft.world.entity.MoverType;
- import net.minecraft.world.entity.NeutralMob;
- import net.minecraft.world.entity.PathfinderMob;
- import net.minecraft.world.entity.Pose;
-@@ -143,6 +144,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
- // Paper start - apply gravity to bees when they get stuck in the void, fixes MC-167279
- class BeeFlyingMoveControl extends FlyingMoveControl {
- public BeeFlyingMoveControl(final Mob entity, final int maxPitchChange, final boolean noGravity) {
-@@ -151,22 +153,89 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
-
- @Override
- public void tick() {
-+ // Purpur start
-+ if (mob.getRider() != null && mob.isControllable()) {
-+ flyingController.purpurTick(mob.getRider());
-+ return;
-+ }
-+ // Purpur end
- if (this.mob.getY() <= Bee.this.level.getMinBuildHeight()) {
- this.mob.setNoGravity(false);
- }
- super.tick();
- }
-+
-+ // Purpur start
-+ @Override
-+ public boolean hasWanted() {
-+ return mob.getRider() != null || !mob.isControllable() || super.hasWanted();
-+ }
-+ // Purpur end
- }
- this.moveControl = new BeeFlyingMoveControl(this, 20, true);
- // Paper end
- this.lookControl = new Bee.BeeLookControl(this);
- this.setPathfindingMalus(BlockPathTypes.DANGER_FIRE, -1.0F);
-- this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F);
-+ if (isSensitiveToWater()) this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F); // Purpur
- this.setPathfindingMalus(BlockPathTypes.WATER_BORDER, 16.0F);
- this.setPathfindingMalus(BlockPathTypes.COCOA, -1.0F);
- this.setPathfindingMalus(BlockPathTypes.FENCE, -1.0F);
- }
-
-+ // Purpur start
-+ @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(MoverType.SELF, mot.multiply(speed, speed, speed));
-+ setDeltaMovement(mot.scale(0.9D));
-+ }
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.beeMaxHealth);
-+ }
-+
-+ @Override
-+ public int getPurpurBreedTime() {
-+ return this.level.purpurConfig.beeBreedingTicks;
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.beeTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.beeAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @Override
- protected void defineSynchedData() {
- super.defineSynchedData();
-@@ -181,6 +250,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
- 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));
-@@ -196,6 +266,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
- 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));
-@@ -344,7 +415,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
-
- boolean wantsToEnterHive() {
- if (this.stayOutOfHiveCountdown <= 0 && !this.beePollinateGoal.isPollinating() && !this.hasStung() && this.getTarget() == null) {
-- boolean flag = this.isTiredOfLookingForNectar() || this.level.isRaining() || this.level.isNight() || this.hasNectar();
-+ boolean flag = this.isTiredOfLookingForNectar() || (this.level.isRaining() && !this.level.purpurConfig.beeCanWorkInRain) || (this.level.isNight() && !this.level.purpurConfig.beeCanWorkAtNight) || this.hasNectar(); // Purpur
-
- return flag && !this.isHiveNearFire();
- } else {
-@@ -384,6 +455,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
- this.hurt(this.damageSources().drown(), 1.0F);
- }
-
-+ if (flag && !this.level.purpurConfig.beeDiesAfterSting) setHasStung(false); else // Purpur
- if (flag) {
- ++this.timeSinceSting;
- if (this.timeSinceSting % 5 == 0 && this.random.nextInt(Mth.clamp(1200 - this.timeSinceSting, 1, 1200)) == 0) {
-@@ -732,6 +804,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
- if (optional.isPresent()) {
- Bee.this.savedFlowerPos = (BlockPos) optional.get();
- Bee.this.navigation.moveTo((double) Bee.this.savedFlowerPos.getX() + 0.5D, (double) Bee.this.savedFlowerPos.getY() + 0.5D, (double) Bee.this.savedFlowerPos.getZ() + 0.5D, 1.2000000476837158D);
-+ new org.purpurmc.purpur.event.entity.BeeFoundFlowerEvent((org.bukkit.entity.Bee) Bee.this.getBukkitEntity(), io.papermc.paper.util.MCUtil.toLocation(Bee.this.level, Bee.this.savedFlowerPos)).callEvent(); // Purpur
- return true;
- } else {
- Bee.this.remainingCooldownBeforeLocatingNewFlower = Mth.nextInt(Bee.this.random, 20, 60);
-@@ -788,6 +861,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
- this.pollinating = false;
- Bee.this.navigation.stop();
- Bee.this.remainingCooldownBeforeLocatingNewFlower = 200;
-+ new org.purpurmc.purpur.event.entity.BeeStopPollinatingEvent((org.bukkit.entity.Bee) Bee.this.getBukkitEntity(), Bee.this.savedFlowerPos == null ? null : io.papermc.paper.util.MCUtil.toLocation(Bee.this.level, Bee.this.savedFlowerPos), Bee.this.hasNectar()).callEvent(); // Purpur
- }
-
- @Override
-@@ -834,6 +908,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
- this.setWantedPos();
- }
-
-+ if (this.successfulPollinatingTicks == 0) new org.purpurmc.purpur.event.entity.BeeStartedPollinatingEvent((org.bukkit.entity.Bee) Bee.this.getBukkitEntity(), io.papermc.paper.util.MCUtil.toLocation(Bee.this.level, Bee.this.savedFlowerPos)).callEvent(); // Purpur
- ++this.successfulPollinatingTicks;
- if (Bee.this.random.nextFloat() < 0.05F && this.successfulPollinatingTicks > this.lastSoundPlayedTick + 60) {
- this.lastSoundPlayedTick = this.successfulPollinatingTicks;
-@@ -878,16 +953,16 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
- }
- }
-
-- private class BeeLookControl extends LookControl {
-+ private class BeeLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur
-
- BeeLookControl(Mob entity) {
- super(entity);
- }
-
- @Override
-- public void tick() {
-+ public void vanillaTick() { // Purpur
- if (!Bee.this.isAngry()) {
-- super.tick();
-+ super.vanillaTick(); // Purpur
- }
- }
-
-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 72b30a5cdeb8a43702d9ab5f198311929761fad1..fe08d83a49efe5e1648cafc50e9184dbd0db2115 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Cat.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Cat.java
-@@ -97,6 +97,51 @@ public class Cat extends TamableAnimal implements VariantHolder {
- super(type, world);
- }
-
-+ // Purpur start
-+ @Override
-+ public boolean isRidable() {
-+ return level.purpurConfig.catRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level.purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level.purpurConfig.catRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level.purpurConfig.catControllable;
-+ }
-+
-+ @Override
-+ public void onMount(Player rider) {
-+ super.onMount(rider);
-+ setInSittingPose(false);
-+ setLying(false);
-+ setRelaxStateOne(false);
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.catMaxHealth);
-+ }
-+
-+ @Override
-+ public int getPurpurBreedTime() {
-+ return this.level.purpurConfig.catBreedingTicks;
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.catTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.catAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- public ResourceLocation getResourceLocation() {
- return this.getVariant().texture();
- }
-@@ -105,6 +150,7 @@ public class Cat extends TamableAnimal implements VariantHolder {
- protected void registerGoals() {
- this.temptGoal = new Cat.CatTemptGoal(this, 0.6D, Cat.TEMPT_INGREDIENT, true);
- this.goalSelector.addGoal(1, new FloatGoal(this));
-+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
- this.goalSelector.addGoal(1, new PanicGoal(this, 1.5D));
- this.goalSelector.addGoal(2, new SitWhenOrderedToGoal(this));
- this.goalSelector.addGoal(3, new Cat.CatRelaxOnOwnerGoal(this));
-@@ -117,6 +163,7 @@ public class Cat extends TamableAnimal implements VariantHolder {
- this.goalSelector.addGoal(10, new BreedGoal(this, 0.8D));
- this.goalSelector.addGoal(11, new WaterAvoidingRandomStrollGoal(this, 0.8D, 1.0000001E-5F));
- this.goalSelector.addGoal(12, new LookAtPlayerGoal(this, Player.class, 10.0F));
-+ this.targetSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
- this.targetSelector.addGoal(1, new NonTameRandomTargetGoal<>(this, Rabbit.class, false, (Predicate) null));
- this.targetSelector.addGoal(1, new NonTameRandomTargetGoal<>(this, Turtle.class, false, Turtle.BABY_ON_LAND_SELECTOR));
- }
-@@ -308,6 +355,14 @@ public class Cat extends TamableAnimal implements VariantHolder {
- return Mth.lerp(tickDelta, this.relaxStateOneAmountO, this.relaxStateOneAmount);
- }
-
-+ // Purpur start
-+ @Override
-+ public void tame(Player player) {
-+ setCollarColor(level.purpurConfig.catDefaultCollarColor);
-+ super.tame(player);
-+ }
-+ // Purpur end
-+
- @Nullable
- @Override
- public Cat getBreedOffspring(ServerLevel world, AgeableMob entity) {
-@@ -373,6 +428,7 @@ public class Cat extends TamableAnimal implements VariantHolder {
-
- @Override
- public InteractionResult mobInteract(Player player, InteractionHand hand) {
-+ if (getRider() != null) return InteractionResult.PASS; // Purpur
- ItemStack itemstack = player.getItemInHand(hand);
- Item item = itemstack.getItem();
-
-@@ -419,7 +475,7 @@ public class Cat extends TamableAnimal implements VariantHolder {
- }
- } else if (this.isFood(itemstack)) {
- this.usePlayerItem(player, hand, itemstack);
-- if (this.random.nextInt(3) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) { // CraftBukkit
-+ if ((this.level.purpurConfig.alwaysTameInCreative && player.getAbilities().instabuild) || (this.random.nextInt(3) == 0 && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityTameEvent(this, player).isCancelled())) { // CraftBukkit // Purpur
- this.tame(player);
- this.setOrderedToSit(true);
- this.level.broadcastEntityEvent(this, (byte) 7);
-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 b4dc621cb1be7cb4bf1cb31f921d4e9f6cffef88..c5e81244331d76535028f8296d10939933010d09 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Chicken.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Chicken.java
-@@ -54,16 +54,65 @@ public class Chicken extends Animal {
- this.setPathfindingMalus(BlockPathTypes.WATER, 0.0F);
- }
-
-+ // Purpur start
-+ @Override
-+ public boolean isRidable() {
-+ return level.purpurConfig.chickenRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level.purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level.purpurConfig.chickenRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level.purpurConfig.chickenControllable;
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.chickenMaxHealth);
-+ if (level.purpurConfig.chickenRetaliate) {
-+ this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(2.0D);
-+ }
-+ }
-+
-+ @Override
-+ public int getPurpurBreedTime() {
-+ return this.level.purpurConfig.chickenBreedingTicks;
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.chickenTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.chickenAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @Override
- protected void registerGoals() {
- this.goalSelector.addGoal(0, new FloatGoal(this));
-- this.goalSelector.addGoal(1, new PanicGoal(this, 1.4D));
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
-+ // this.goalSelector.addGoal(1, new PanicGoal(this, 1.4D)); // Purpur - moved down
- this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D));
- this.goalSelector.addGoal(3, new TemptGoal(this, 1.0D, Chicken.FOOD_ITEMS, false));
- this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.1D));
- this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0D));
- this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F));
- this.goalSelector.addGoal(7, new RandomLookAroundGoal(this));
-+ // Purpur start
-+ if (level.purpurConfig.chickenRetaliate) {
-+ this.goalSelector.addGoal(1, new net.minecraft.world.entity.ai.goal.MeleeAttackGoal(this, 1.0D, false));
-+ this.targetSelector.addGoal(1, new net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal(this));
-+ } else {
-+ this.goalSelector.addGoal(1, new PanicGoal(this, 1.4D));
-+ }
-+ // Purpur end
- }
-
- @Override
-@@ -72,7 +121,7 @@ public class Chicken extends Animal {
- }
-
- public static AttributeSupplier.Builder createAttributes() {
-- return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 4.0D).add(Attributes.MOVEMENT_SPEED, 0.25D);
-+ return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 4.0D).add(Attributes.MOVEMENT_SPEED, 0.25D).add(Attributes.ATTACK_DAMAGE, 0.0D); // Purpur
- }
-
- @Override
-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..2a45b487e5305e7c40cc8de4ddbb142af4b041de 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,33 @@ public class Cod extends AbstractSchoolingFish {
- super(type, world);
- }
-
-+ // Purpur start
-+ @Override
-+ public boolean isRidable() {
-+ return level.purpurConfig.codRidable;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level.purpurConfig.codControllable;
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.codMaxHealth);
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.codTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.codAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @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 abae850f5babfd75c7547e88fb7637e9775991d3..54d9213d9de26a14a5ca770440d098bf0438373e 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Cow.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Cow.java
-@@ -2,6 +2,7 @@ package net.minecraft.world.entity.animal;
-
- import javax.annotation.Nullable;
- import net.minecraft.core.BlockPos;
-+import net.minecraft.core.particles.ParticleTypes;
- import net.minecraft.server.level.ServerLevel;
- import net.minecraft.sounds.SoundEvent;
- import net.minecraft.sounds.SoundEvents;
-@@ -29,6 +30,7 @@ import net.minecraft.world.item.ItemUtils;
- import net.minecraft.world.item.Items;
- import net.minecraft.world.item.crafting.Ingredient;
- import net.minecraft.world.level.Level;
-+import net.minecraft.world.level.block.Blocks;
- import net.minecraft.world.level.block.state.BlockState;
- // CraftBukkit start
- import org.bukkit.craftbukkit.event.CraftEventFactory;
-@@ -36,25 +38,74 @@ import org.bukkit.craftbukkit.inventory.CraftItemStack;
- // CraftBukkit end
-
- public class Cow extends Animal {
-+ private boolean isNaturallyAggressiveToPlayers; // Purpur
-
- public Cow(EntityType extends Cow> type, Level world) {
- super(type, world);
- }
-
-+ // Purpur start
-+ @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;
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.cowMaxHealth);
-+ this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(this.level.purpurConfig.cowNaturallyAggressiveToPlayersDamage); // Purpur
-+ }
-+
-+ @Override
-+ public int getPurpurBreedTime() {
-+ return this.level.purpurConfig.cowBreedingTicks;
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.cowTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ public net.minecraft.world.entity.SpawnGroupData finalizeSpawn(net.minecraft.world.level.ServerLevelAccessor world, net.minecraft.world.DifficultyInstance difficulty, net.minecraft.world.entity.MobSpawnType spawnReason, net.minecraft.world.entity.SpawnGroupData entityData, net.minecraft.nbt.CompoundTag entityNbt) {
-+ this.isNaturallyAggressiveToPlayers = world.getLevel().purpurConfig.cowNaturallyAggressiveToPlayersChance > 0.0D && random.nextDouble() <= world.getLevel().purpurConfig.cowNaturallyAggressiveToPlayersChance;
-+ return super.finalizeSpawn(world, difficulty, spawnReason, entityData, entityNbt);
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.cowAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @Override
- protected void registerGoals() {
- this.goalSelector.addGoal(0, new FloatGoal(this));
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
- this.goalSelector.addGoal(1, new PanicGoal(this, 2.0D));
-+ this.goalSelector.addGoal(1, new net.minecraft.world.entity.ai.goal.MeleeAttackGoal(this, 1.2000000476837158D, true)); // Purpur
- this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D));
-+ if (level.purpurConfig.cowFeedMushrooms > 0) this.goalSelector.addGoal(3, new TemptGoal(this, 1.25D, Ingredient.of(Items.WHEAT, Blocks.RED_MUSHROOM.asItem(), Blocks.BROWN_MUSHROOM.asItem()), false)); else // Purpur
- this.goalSelector.addGoal(3, new TemptGoal(this, 1.25D, Ingredient.of(Items.WHEAT), false));
- this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.25D));
- this.goalSelector.addGoal(5, new WaterAvoidingRandomStrollGoal(this, 1.0D));
- this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F));
- this.goalSelector.addGoal(7, new RandomLookAroundGoal(this));
-+ this.targetSelector.addGoal(0, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, target -> isNaturallyAggressiveToPlayers)); // Purpur
- }
-
- public static AttributeSupplier.Builder createAttributes() {
-- return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.MOVEMENT_SPEED, 0.20000000298023224D);
-+ return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 10.0D).add(Attributes.MOVEMENT_SPEED, 0.20000000298023224D).add(Attributes.ATTACK_DAMAGE, 0.0D); // Purpur
- }
-
- @Override
-@@ -84,6 +135,7 @@ public class Cow extends Animal {
-
- @Override
- public InteractionResult mobInteract(Player player, InteractionHand hand) {
-+ if (getRider() != null) return InteractionResult.PASS; // Purpur
- ItemStack itemstack = player.getItemInHand(hand);
-
- if (itemstack.is(Items.BUCKET) && !this.isBaby()) {
-@@ -91,7 +143,7 @@ public class Cow extends Animal {
- org.bukkit.event.player.PlayerBucketFillEvent event = CraftEventFactory.callPlayerBucketFillEvent((ServerLevel) player.level, player, this.blockPosition(), this.blockPosition(), null, itemstack, Items.MILK_BUCKET, hand);
-
- if (event.isCancelled()) {
-- return InteractionResult.PASS;
-+ return tryRide(player, hand); // Purpur
- }
- // CraftBukkit end
-
-@@ -100,6 +152,10 @@ public class Cow extends Animal {
-
- player.setItemInHand(hand, itemstack1);
- return InteractionResult.sidedSuccess(this.level.isClientSide);
-+ // Purpur start - feed mushroom to change to mooshroom
-+ } else if (level.purpurConfig.cowFeedMushrooms > 0 && this.getType() != EntityType.MOOSHROOM && isMushroom(itemstack)) {
-+ return this.feedMushroom(player, itemstack);
-+ // Purpur end
- } else {
- return super.mobInteract(player, hand);
- }
-@@ -115,4 +171,69 @@ public class Cow extends Animal {
- protected float getStandingEyeHeight(Pose pose, EntityDimensions dimensions) {
- return this.isBaby() ? dimensions.height * 0.95F : 1.3F;
- }
-+
-+ // Purpur start - feed mushroom to change to mooshroom
-+ private int redMushroomsFed = 0;
-+ private int brownMushroomsFed = 0;
-+
-+ private boolean isMushroom(ItemStack stack) {
-+ return stack.getItem() == Blocks.RED_MUSHROOM.asItem() || stack.getItem() == Blocks.BROWN_MUSHROOM.asItem();
-+ }
-+
-+ private int incrementFeedCount(ItemStack stack) {
-+ if (stack.getItem() == Blocks.RED_MUSHROOM.asItem()) {
-+ return ++redMushroomsFed;
-+ } else {
-+ return ++brownMushroomsFed;
-+ }
-+ }
-+
-+ private InteractionResult feedMushroom(Player player, ItemStack stack) {
-+ level.broadcastEntityEvent(this, (byte) 18); // hearts
-+ playSound(SoundEvents.COW_MILK, 1.0F, 1.0F);
-+ if (incrementFeedCount(stack) < level.purpurConfig.cowFeedMushrooms) {
-+ if (!player.getAbilities().instabuild) {
-+ stack.shrink(1);
-+ }
-+ return InteractionResult.CONSUME; // require 5 mushrooms to transform (prevents mushroom duping)
-+ }
-+ MushroomCow mooshroom = EntityType.MOOSHROOM.create(level);
-+ if (mooshroom == null) {
-+ return InteractionResult.PASS;
-+ }
-+ if (stack.getItem() == Blocks.BROWN_MUSHROOM.asItem()) {
-+ mooshroom.setVariant(MushroomCow.MushroomType.BROWN);
-+ } else {
-+ mooshroom.setVariant(MushroomCow.MushroomType.RED);
-+ }
-+ mooshroom.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot());
-+ mooshroom.setHealth(this.getHealth());
-+ mooshroom.setAge(getAge());
-+ mooshroom.copyPosition(this);
-+ mooshroom.setYBodyRot(this.yBodyRot);
-+ mooshroom.setYHeadRot(this.getYHeadRot());
-+ mooshroom.yRotO = this.yRotO;
-+ mooshroom.xRotO = this.xRotO;
-+ if (this.hasCustomName()) {
-+ mooshroom.setCustomName(this.getCustomName());
-+ }
-+ if (CraftEventFactory.callEntityTransformEvent(this, mooshroom, org.bukkit.event.entity.EntityTransformEvent.TransformReason.INFECTION).isCancelled()) {
-+ return InteractionResult.PASS;
-+ }
-+ if (!new com.destroystokyo.paper.event.entity.EntityTransformedEvent(this.getBukkitEntity(), mooshroom.getBukkitEntity(), com.destroystokyo.paper.event.entity.EntityTransformedEvent.TransformedReason.INFECTED).callEvent()) {
-+ return InteractionResult.PASS;
-+ }
-+ this.level.addFreshEntity(mooshroom);
-+ this.remove(RemovalReason.DISCARDED);
-+ if (!player.getAbilities().instabuild) {
-+ stack.shrink(1);
-+ }
-+ for (int i = 0; i < 15; ++i) {
-+ ((ServerLevel) level).sendParticles(((ServerLevel) level).players(), null, ParticleTypes.HAPPY_VILLAGER,
-+ getX() + random.nextFloat(), getY() + (random.nextFloat() * 2), getZ() + random.nextFloat(), 1,
-+ random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, 0, true);
-+ }
-+ return InteractionResult.SUCCESS;
-+ }
-+ // Purpur 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 e93abb02744b5cd8db88e01b6ccf145498903b11..a077edbe97ce89e11a26fe3ebeb0bdd996593f78 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Dolphin.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Dolphin.java
-@@ -78,19 +78,104 @@ public class Dolphin extends WaterAnimal {
- public static final Predicate ALLOWED_ITEMS = (entityitem) -> {
- return !entityitem.hasPickUpDelay() && entityitem.isAlive() && entityitem.isInWater();
- };
-+ private int spitCooldown; // Purpur
-+ private boolean isNaturallyAggressiveToPlayers; // Purpur
-
- 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
-+ 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
- this.lookControl = new SmoothSwimmingLookControl(this, 10);
- this.setCanPickUpLoot(true);
- }
-
-+ // Purpur start
-+ @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;
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.dolphinMaxHealth);
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.dolphinTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.dolphinAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @Nullable
- @Override
- public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, MobSpawnType spawnReason, @Nullable SpawnGroupData entityData, @Nullable CompoundTag entityNbt) {
- this.setAirSupply(this.getMaxAirSupply());
- this.setXRot(0.0F);
-+ this.isNaturallyAggressiveToPlayers = world.getLevel().purpurConfig.dolphinNaturallyAggressiveToPlayersChance > 0.0D && random.nextDouble() <= world.getLevel().purpurConfig.dolphinNaturallyAggressiveToPlayersChance; // Purpur
- return super.finalizeSpawn(world, difficulty, spawnReason, entityData, entityNbt);
- }
-
-@@ -160,17 +245,21 @@ public class Dolphin extends WaterAnimal {
- 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
-+ this.goalSelector.addGoal(1, new MeleeAttackGoal(this, 1.2000000476837158D, true)); // Purpur
- 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));
- this.goalSelector.addGoal(4, new RandomLookAroundGoal(this));
- this.goalSelector.addGoal(5, new LookAtPlayerGoal(this, Player.class, 6.0F));
- this.goalSelector.addGoal(5, new DolphinJumpGoal(this, 10));
-- this.goalSelector.addGoal(6, new MeleeAttackGoal(this, 1.2000000476837158D, true));
-+ //this.goalSelector.addGoal(6, new MeleeAttackGoal(this, 1.2000000476837158D, true)); // Purpur - moved up
- 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
- this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Guardian.class})).setAlertOthers());
-+ this.targetSelector.addGoal(2, new net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, target -> isNaturallyAggressiveToPlayers)); // Purpur
- }
-
- public static AttributeSupplier.Builder createAttributes() {
-@@ -221,7 +310,7 @@ public class Dolphin extends WaterAnimal {
-
- @Override
- protected boolean canRide(Entity entity) {
-- return true;
-+ return boardingCooldown <= 0; // Purpur - make dolphin honor ride cooldown like all other non-boss mobs;
- }
-
- @Override
-@@ -256,6 +345,11 @@ public class Dolphin extends WaterAnimal {
- @Override
- public void tick() {
- super.tick();
-+ // Purpur start
-+ if (spitCooldown > 0) {
-+ spitCooldown--;
-+ }
-+ // Purpur end
- if (this.isNoAi()) {
- this.setAirSupply(this.getMaxAirSupply());
- } else {
-@@ -401,6 +495,7 @@ public class Dolphin extends WaterAnimal {
-
- @Override
- public boolean canUse() {
-+ if (this.dolphin.level.purpurConfig.dolphinDisableTreasureSearching) return false; // Purpur
- return this.dolphin.gotFish() && this.dolphin.getAirSupply() >= 100 && this.dolphin.level.getWorld().canGenerateStructures(); // MC-151364, SPIGOT-5494: hangs if generate-structures=false
- }
-
-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 89894bc6a55bc7e456a9d49ac48f6a8192b890ae..f0eb5e0c01923f884b1c7c48e8d67ed5cd429854 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Fox.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Fox.java
-@@ -35,6 +35,7 @@ import net.minecraft.util.RandomSource;
- import net.minecraft.util.StringRepresentable;
- import net.minecraft.world.DifficultyInstance;
- import net.minecraft.world.InteractionHand;
-+import net.minecraft.world.InteractionResult;
- import net.minecraft.world.damagesource.DamageSource;
- import net.minecraft.world.entity.AgeableMob;
- import net.minecraft.world.entity.Entity;
-@@ -88,6 +89,7 @@ import net.minecraft.world.level.block.Blocks;
- import net.minecraft.world.level.block.CaveVines;
- import net.minecraft.world.level.block.SweetBerryBushBlock;
- import net.minecraft.world.level.block.state.BlockState;
-+import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.level.pathfinder.BlockPathTypes;
- import net.minecraft.world.phys.Vec3;
-
-@@ -141,6 +143,64 @@ public class Fox extends Animal implements VariantHolder {
- this.setCanPickUpLoot(true);
- }
-
-+ // Purpur start
-+ @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);
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.foxMaxHealth);
-+ }
-+
-+ @Override
-+ public int getPurpurBreedTime() {
-+ return this.level.purpurConfig.foxBreedingTicks;
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.foxTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.foxAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @Override
- protected void defineSynchedData() {
- super.defineSynchedData();
-@@ -160,6 +220,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
- 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));
-@@ -186,6 +247,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
- this.targetSelector.addGoal(3, new Fox.DefendTrustedTargetGoal(LivingEntity.class, false, false, (entityliving) -> {
- return Fox.TRUSTED_TARGET_SELECTOR.test(entityliving) && !this.trusts(entityliving.getUUID());
- }));
-@@ -342,6 +404,11 @@ public class Fox extends Animal implements VariantHolder {
- }
-
- private void setTargetGoals() {
-+ // Purpur start - do not add duplicate goals
-+ this.targetSelector.removeGoal(this.landTargetGoal);
-+ this.targetSelector.removeGoal(this.turtleEggTargetGoal);
-+ this.targetSelector.removeGoal(this.fishTargetGoal);
-+ // Purpur end
- if (this.getVariant() == Fox.Type.RED) {
- this.targetSelector.addGoal(4, this.landTargetGoal);
- this.targetSelector.addGoal(4, this.turtleEggTargetGoal);
-@@ -375,6 +442,7 @@ public class Fox extends Animal implements VariantHolder {
-
- public void setVariant(Fox.Type variant) {
- this.entityData.set(Fox.DATA_TYPE_ID, variant.getId());
-+ this.setTargetGoals(); // Purpur - fix API bug not updating pathfinders on type change
- }
-
- List getTrustedUUIDs() {
-@@ -711,6 +779,29 @@ public class Fox extends Animal implements VariantHolder {
- return this.getTrustedUUIDs().contains(uuid);
- }
-
-+ // Purpur start
-+ @Override
-+ public InteractionResult mobInteract(Player player, InteractionHand hand) {
-+ if (level.purpurConfig.foxTypeChangesWithTulips) {
-+ ItemStack itemstack = player.getItemInHand(hand);
-+ if (getVariant() == Type.RED && itemstack.getItem() == Items.WHITE_TULIP) {
-+ setVariant(Type.SNOW);
-+ if (!player.getAbilities().instabuild) {
-+ itemstack.shrink(1);
-+ }
-+ return InteractionResult.SUCCESS;
-+ } else if (getVariant() == Type.SNOW && itemstack.getItem() == Items.ORANGE_TULIP) {
-+ setVariant(Type.RED);
-+ if (!player.getAbilities().instabuild) {
-+ itemstack.shrink(1);
-+ }
-+ return InteractionResult.SUCCESS;
-+ }
-+ }
-+ return super.mobInteract(player, hand);
-+ }
-+ // Purpur end
-+
- @Override
- // Paper start - Cancellable death event
- protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(DamageSource source) {
-@@ -758,16 +849,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
-
- public FoxLookControl() {
- super(Fox.this);
- }
-
- @Override
-- public void tick() {
-+ public void vanillaTick() { // Purpur
- if (!Fox.this.isSleeping()) {
-- super.tick();
-+ super.vanillaTick(); // Purpur
- }
-
- }
-@@ -778,16 +869,16 @@ public class Fox extends Animal implements VariantHolder {
- }
- }
-
-- private class FoxMoveControl extends MoveControl {
-+ private class FoxMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur
-
- public FoxMoveControl() {
- super(Fox.this);
- }
-
- @Override
-- public void tick() {
-+ public void vanillaTick() { // Purpur
- if (Fox.this.canMove()) {
-- super.tick();
-+ super.vanillaTick(); // Purpur
- }
-
- }
-@@ -905,8 +996,10 @@ public class Fox extends Animal implements VariantHolder {
- CriteriaTriggers.BRED_ANIMALS.trigger(entityplayer2, this.animal, this.partner, entityfox);
- }
-
-- this.animal.setAge(6000);
-- this.partner.setAge(6000);
-+ // Purpur start
-+ this.animal.setAge(this.animal.getPurpurBreedTime());
-+ this.partner.setAge(this.partner.getPurpurBreedTime());
-+ // Purpur end
- this.animal.resetLove();
- this.partner.resetLove();
- worldserver.addFreshEntityWithPassengers(entityfox, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.BREEDING); // CraftBukkit - added SpawnReason
-@@ -1294,7 +1387,7 @@ public class Fox extends Animal implements VariantHolder {
- }
-
- protected void onReachedTarget() {
-- if (Fox.this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
-+ if (Fox.this.level.purpurConfig.foxBypassMobGriefing || Fox.this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur
- BlockState iblockdata = Fox.this.level.getBlockState(this.blockPos);
-
- if (iblockdata.is(Blocks.SWEET_BERRY_BUSH)) {
-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 4fbbd74cda7e4f2c623db46c2c94d9697ca5df05..b5f445750a5ccbe7658396bdcc9648dc41f39ced 100644
---- a/src/main/java/net/minecraft/world/entity/animal/IronGolem.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/IronGolem.java
-@@ -63,14 +63,59 @@ public class IronGolem extends AbstractGolem implements NeutralMob {
- private int remainingPersistentAngerTime;
- @Nullable
- private UUID persistentAngerTarget;
-+ @Nullable private UUID summoner; // Purpur
-
- public IronGolem(EntityType extends IronGolem> type, Level world) {
- super(type, world);
- this.setMaxUpStep(1.0F);
- }
-
-+ // Purpur start
-+ @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;
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.ironGolemMaxHealth);
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.ironGolemTakeDamageFromWater;
-+ }
-+
-+ @Nullable
-+ public UUID getSummoner() {
-+ return summoner;
-+ }
-+
-+ public void setSummoner(@Nullable UUID summoner) {
-+ this.summoner = summoner;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.ironGolemAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @Override
- protected void registerGoals() {
-+ if (level.purpurConfig.ironGolemCanSwim) this.goalSelector.addGoal(0, new net.minecraft.world.entity.ai.goal.FloatGoal(this)); // Purpur
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
-+ if (this.level.purpurConfig.ironGolemPoppyCalm) this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.ReceiveFlower(this)); // Purpur
- 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));
-@@ -78,6 +123,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
- 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));
-@@ -148,6 +194,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob {
- public void addAdditionalSaveData(CompoundTag nbt) {
- super.addAdditionalSaveData(nbt);
- nbt.putBoolean("PlayerCreated", this.isPlayerCreated());
-+ if (getSummoner() != null) nbt.putUUID("Purpur.Summoner", getSummoner()); // Purpur
- this.addPersistentAngerSaveData(nbt);
- }
-
-@@ -155,6 +202,7 @@ public class IronGolem extends AbstractGolem implements NeutralMob {
- public void readAdditionalSaveData(CompoundTag nbt) {
- super.readAdditionalSaveData(nbt);
- this.setPlayerCreated(nbt.getBoolean("PlayerCreated"));
-+ if (nbt.contains("Purpur.Summoner")) setSummoner(nbt.getUUID("Purpur.Summoner")); // Purpur
- this.readPersistentAngerSaveData(this.level, nbt);
- }
-
-@@ -279,13 +327,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
- } else {
- float f = this.getHealth();
-
- this.heal(25.0F);
- if (this.getHealth() == f) {
-- return InteractionResult.PASS;
-+ return tryRide(player, hand); // Purpur
- } else {
- float f1 = 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F;
-
-@@ -294,6 +342,8 @@ public class IronGolem extends AbstractGolem implements NeutralMob {
- itemstack.shrink(1);
- }
-
-+ if (this.level.purpurConfig.ironGolemHealCalm && isAngry() && getHealth() == getMaxHealth()) stopBeingAngry(); // Purpur
-+
- return InteractionResult.sidedSuccess(this.level.isClientSide);
- }
- }
-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 8ee1a0626e5a6c0ad19a25b8f476a2e12d69668d..4d5ebdb368daaa673bff420d7d27dde092f254d5 100644
---- a/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/MushroomCow.java
-@@ -63,6 +63,43 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder {
-@@ -145,7 +182,7 @@ public class MushroomCow extends Cow implements Shearable, VariantHolder> optional = this.getEffectFromItemStack(itemstack);
-
- if (!optional.isPresent()) {
-- return InteractionResult.PASS;
-+ return tryRide(player, hand); // Purpur
- }
-
- Pair pair = (Pair) optional.get();
-@@ -170,7 +207,7 @@ 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 9c1e02c3a990cd0f8bba1c84c170b438278c02a7..d1e45052fc96b6f81a331c6c73cb68ff96238359 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Panda.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java
-@@ -108,6 +108,53 @@ public class Panda extends Animal {
-
- }
-
-+ // Purpur start
-+ @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);
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.pandaMaxHealth);
-+ setAttributes();
-+ }
-+
-+ @Override
-+ public int getPurpurBreedTime() {
-+ return this.level.purpurConfig.pandaBreedingTicks;
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.pandaTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.pandaAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @Override
- public boolean canTakeItem(ItemStack stack) {
- EquipmentSlot enumitemslot = Mob.getEquipmentSlotForItem(stack);
-@@ -269,6 +316,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
- 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));
-@@ -284,6 +332,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
- this.targetSelector.addGoal(1, (new Panda.PandaHurtByTargetGoal(this, new Class[0])).setAlertOthers(new Class[0]));
- }
-
-@@ -607,7 +656,10 @@ public class Panda extends Animal {
-
- public void setAttributes() {
- if (this.isWeak()) {
-- this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(10.0D);
-+ // Purpur start
-+ net.minecraft.world.entity.ai.attributes.AttributeInstance maxHealth = this.getAttribute(Attributes.MAX_HEALTH);
-+ maxHealth.setBaseValue(maxHealth.getValue() / 2);
-+ // Purpur end
- }
-
- if (this.isLazy()) {
-@@ -630,7 +682,7 @@ public class Panda extends Animal {
- ItemStack itemstack = player.getItemInHand(hand);
-
- if (this.isScared()) {
-- return InteractionResult.PASS;
-+ return tryRide(player, hand); // Purpur
- } else if (this.isOnBack()) {
- this.setOnBack(false);
- return InteractionResult.sidedSuccess(this.level.isClientSide);
-@@ -647,7 +699,7 @@ public class Panda extends Animal {
- this.setInLove(player);
- } else {
- if (this.level.isClientSide || this.isSitting() || this.isInWater()) {
-- return InteractionResult.PASS;
-+ return tryRide(player, hand); // Purpur
- }
-
- this.tryToSit();
-@@ -666,7 +718,7 @@ public class Panda extends Animal {
-
- return InteractionResult.SUCCESS;
- } else {
-- return InteractionResult.PASS;
-+ return tryRide(player, hand); // Purpur
- }
- }
-
-@@ -706,7 +758,7 @@ public class Panda extends Animal {
- return !this.isOnBack() && !this.isScared() && !this.isEating() && !this.isRolling() && !this.isSitting();
- }
-
-- private static class PandaMoveControl extends MoveControl {
-+ private static class PandaMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur
-
- private final Panda panda;
-
-@@ -716,9 +768,9 @@ public class Panda extends Animal {
- }
-
- @Override
-- public void tick() {
-+ public void vanillaTick() { // Purpur
- if (this.panda.canPerformAction()) {
-- super.tick();
-+ super.vanillaTick(); // Purpur
- }
- }
- }
-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 e6e40770acf71b9079e8f6ac07025319dd8e2e4e..8ca75f748ac7dcf872b5677648ba384992242a07 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Parrot.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Parrot.java
-@@ -129,12 +129,88 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder type, Level world) {
- super(type, world);
-- this.moveControl = new FlyingMoveControl(this, 10, false);
-+ // Purpur start
-+ 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
- this.setPathfindingMalus(BlockPathTypes.DANGER_FIRE, -1.0F);
- this.setPathfindingMalus(BlockPathTypes.DAMAGE_FIRE, -1.0F);
- this.setPathfindingMalus(BlockPathTypes.COCOA, -1.0F);
- }
-
-+ // Purpur start
-+ @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));
-+ }
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.parrotMaxHealth);
-+ }
-+
-+ @Override
-+ public int getPurpurBreedTime() {
-+ return 6000;
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.parrotTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.parrotAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @Nullable
- @Override
- public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, MobSpawnType spawnReason, @Nullable SpawnGroupData entityData, @Nullable CompoundTag entityNbt) {
-@@ -153,8 +229,11 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder type, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) {
-@@ -306,13 +386,13 @@ public class Parrot extends ShoulderRidingEntity implements VariantHolder type, Level world) {
- super(type, world);
- }
-
-+ // Purpur start
-+ @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;
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.polarBearMaxHealth);
-+ }
-+
-+ public boolean canMate(Animal other) {
-+ if (other == this) {
-+ return false;
-+ } else if (this.isStanding()) {
-+ return false;
-+ } else if (this.getTarget() != null) {
-+ return false;
-+ } else if (!(other instanceof PolarBear)) {
-+ return false;
-+ } else {
-+ PolarBear bear = (PolarBear) other;
-+ if (bear.isStanding()) {
-+ return false;
-+ }
-+ if (bear.getTarget() != null) {
-+ return false;
-+ }
-+ return this.isInLove() && bear.isInLove();
-+ }
-+ }
-+
-+ @Override
-+ public int getPurpurBreedTime() {
-+ return this.level.purpurConfig.polarBearBreedingTicks;
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.polarBearTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.polarBearAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @Nullable
- @Override
- public AgeableMob getBreedOffspring(ServerLevel world, AgeableMob entity) {
-@@ -73,19 +143,27 @@ public class PolarBear extends Animal implements NeutralMob {
-
- @Override
- public boolean isFood(ItemStack stack) {
-- return false;
-+ return level.purpurConfig.polarBearBreedableItem != null && stack.getItem() == level.purpurConfig.polarBearBreedableItem; // Purpur
- }
-
- @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
- this.goalSelector.addGoal(1, new PolarBear.PolarBearMeleeAttackGoal());
- this.goalSelector.addGoal(1, new PolarBear.PolarBearPanicGoal());
-+ // Purpur start
-+ if (level.purpurConfig.polarBearBreedableItem != null) {
-+ this.goalSelector.addGoal(2, new net.minecraft.world.entity.ai.goal.BreedGoal(this, 1.0D));
-+ this.goalSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.TemptGoal(this, 1.0D, net.minecraft.world.item.crafting.Ingredient.of(level.purpurConfig.polarBearBreedableItem), false));
-+ }
-+ // Purpur end
- this.goalSelector.addGoal(4, new FollowParentGoal(this, 1.25D));
- this.goalSelector.addGoal(5, new RandomStrollGoal(this, 1.0D));
- 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
- 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));
-@@ -202,6 +280,11 @@ public class PolarBear extends Animal implements NeutralMob {
- this.updatePersistentAnger((ServerLevel)this.level, true);
- }
-
-+ // Purpur start
-+ if (isStanding() && --standTimer <= 0) {
-+ setStanding(false);
-+ }
-+ // Purpur end
- }
-
- @Override
-@@ -231,6 +314,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
- }
-
- 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 9aa5aa0d66257bf1413a904c516293aea30d2ca8..d152c50f17e2ab7a37b0c295c7f62e63889b8b76 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java
-@@ -45,6 +45,33 @@ public class Pufferfish extends AbstractFish {
- this.refreshDimensions();
- }
-
-+ // Purpur start
-+ @Override
-+ public boolean isRidable() {
-+ return level.purpurConfig.pufferfishRidable;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level.purpurConfig.pufferfishControllable;
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.pufferfishMaxHealth);
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.pufferfishTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.pufferfishAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @Override
- protected void defineSynchedData() {
- super.defineSynchedData();
-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 2e75066db6cf4903f04428b73c4e868988776920..3395bc1d9140ab5496ad998343a963ae12f630d6 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Rabbit.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Rabbit.java
-@@ -83,6 +83,7 @@ public class Rabbit extends Animal implements VariantHolder {
- private boolean wasOnGround;
- private int jumpDelayTicks;
- public int moreCarrotTicks;
-+ private boolean actualJump; // Purpur
-
- public Rabbit(EntityType extends Rabbit> type, Level world) {
- super(type, world);
-@@ -91,6 +92,71 @@ public class Rabbit extends Animal implements VariantHolder {
- this.initializePathFinderGoals(); // CraftBukkit - moved code
- }
-
-+ // Purpur start
-+ @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;
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.rabbitMaxHealth);
-+ }
-+
-+ @Override
-+ public int getPurpurBreedTime() {
-+ return this.level.purpurConfig.rabbitBreedingTicks;
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.rabbitTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.rabbitAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- // CraftBukkit start - code from constructor
- public void initializePathFinderGoals(){
- this.setSpeedModifier(0.0D);
-@@ -100,6 +166,7 @@ public class Rabbit extends Animal implements VariantHolder {
- @Override
- public void registerGoals() {
- this.goalSelector.addGoal(1, new FloatGoal(this));
-+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
- 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 +181,13 @@ public class Rabbit extends Animal implements VariantHolder {
-
- @Override
- protected float getJumpPower() {
-+ if (getRider() != null && this.isControllable()) {
-+ if (getForwardMot() < 0) {
-+ setSpeed(getForwardMot() * 2F);
-+ }
-+ return actualJump ? 0.5F : 0.3F;
-+ }
-+ // Purpur end
- if (!this.horizontalCollision && (!this.moveControl.hasWanted() || this.moveControl.getWantedY() <= this.getY() + 0.5D)) {
- Path pathentity = this.navigation.getPath();
-
-@@ -132,7 +206,7 @@ public class Rabbit extends Animal implements VariantHolder {
- }
-
- @Override
-- protected void jumpFromGround() {
-+ public void jumpFromGround() { // Purpur - protected -> public
- super.jumpFromGround();
- double d0 = this.moveControl.getSpeedModifier();
-
-@@ -182,6 +256,13 @@ public class Rabbit extends Animal implements VariantHolder {
-
- @Override
- public void customServerAiStep() {
-+ // Purpur start
-+ if (getRider() != null && this.isControllable()) {
-+ handleJumping();
-+ return;
-+ }
-+ // Purpur end
-+
- if (this.jumpDelayTicks > 0) {
- --this.jumpDelayTicks;
- }
-@@ -399,10 +480,23 @@ public class Rabbit extends Animal implements VariantHolder {
- }
-
- this.setVariant(entityrabbit_variant);
-+
-+ // Purpur start
-+ if (entityrabbit_variant != Variant.EVIL && world.getLevel().purpurConfig.rabbitNaturalToast > 0D && random.nextDouble() <= world.getLevel().purpurConfig.rabbitNaturalToast) {
-+ setCustomName(Component.translatable("Toast"));
-+ }
-+ // Purpur end
-+
- return super.finalizeSpawn(world, difficulty, spawnReason, (SpawnGroupData) entityData, entityNbt);
- }
-
- private static Rabbit.Variant getRandomRabbitVariant(LevelAccessor world, BlockPos pos) {
-+ // Purpur start
-+ Level level = world.getMinecraftWorld();
-+ if (level.purpurConfig.rabbitNaturalKiller > 0D && world.getRandom().nextDouble() <= level.purpurConfig.rabbitNaturalKiller) {
-+ return Rabbit.Variant.EVIL;
-+ }
-+ // Purpur end
- Holder holder = world.getBiome(pos);
- int i = world.getRandom().nextInt(100);
-
-@@ -466,7 +560,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
-
- private final Rabbit rabbit;
- private double nextJumpSpeed;
-@@ -477,14 +571,14 @@ public class Rabbit extends Animal implements VariantHolder {
- }
-
- @Override
-- public void tick() {
-+ public void vanillaTick() { // Purpur
- if (this.rabbit.onGround && !this.rabbit.jumping && !((Rabbit.RabbitJumpControl) this.rabbit.jumpControl).wantJump()) {
- this.rabbit.setSpeedModifier(0.0D);
- } else if (this.hasWanted()) {
- this.rabbit.setSpeedModifier(this.nextJumpSpeed);
- }
-
-- super.tick();
-+ super.vanillaTick(); // Purpur
- }
-
- @Override
-@@ -546,7 +640,7 @@ public class Rabbit extends Animal implements VariantHolder {
- @Override
- public boolean canUse() {
- if (this.nextStartTick <= 0) {
-- if (!this.rabbit.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
-+ if (!this.rabbit.level.purpurConfig.rabbitBypassMobGriefing && !this.rabbit.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur
- return false;
- }
-
-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 0af79daa357f53a8871e293b57e16c099e5d3f64..e0da8d1974f88e1426034620f78a29f9bdb5adf4 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Salmon.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Salmon.java
-@@ -13,6 +13,33 @@ public class Salmon extends AbstractSchoolingFish {
- super(type, world);
- }
-
-+ // Purpur start
-+ @Override
-+ public boolean isRidable() {
-+ return level.purpurConfig.salmonRidable;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level.purpurConfig.salmonControllable;
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.salmonMaxHealth);
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.salmonTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.salmonAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @Override
- public int getMaxSchoolSize() {
- return 5;
-diff --git a/src/main/java/net/minecraft/world/entity/animal/Sheep.java b/src/main/java/net/minecraft/world/entity/animal/Sheep.java
-index 8b17dd156aa321686570da1e620107274adca56f..ac0b415cfbcbd1641628422e807220d90ecd05d9 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Sheep.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Sheep.java
-@@ -116,10 +116,48 @@ public class Sheep extends Animal implements Shearable {
- super(type, world);
- }
-
-+ // Purpur start
-+ @Override
-+ public boolean isRidable() {
-+ return level.purpurConfig.sheepRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level.purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level.purpurConfig.sheepRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level.purpurConfig.sheepControllable;
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.sheepMaxHealth);
-+ }
-+
-+ @Override
-+ public int getPurpurBreedTime() {
-+ return this.level.purpurConfig.sheepBreedingTicks;
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.sheepTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.sheepAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @Override
- protected void registerGoals() {
- this.eatBlockGoal = new EatBlockGoal(this);
- this.goalSelector.addGoal(0, new FloatGoal(this));
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
- this.goalSelector.addGoal(1, new PanicGoal(this, 1.25D));
- this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D));
- this.goalSelector.addGoal(3, new TemptGoal(this, 1.1D, Ingredient.of(Items.WHEAT), false));
-@@ -254,7 +292,7 @@ public class Sheep extends Animal implements Shearable {
- return InteractionResult.PASS;
- }
- // CraftBukkit end
-- this.shear(SoundSource.PLAYERS);
-+ this.shear(SoundSource.PLAYERS, net.minecraft.world.item.enchantment.EnchantmentHelper.getMobLooting(player)); // Purpur
- this.gameEvent(GameEvent.SHEAR, player);
- itemstack.hurtAndBreak(1, player, (entityhuman1) -> {
- entityhuman1.broadcastBreakEvent(hand);
-@@ -269,14 +307,15 @@ public class Sheep extends Animal implements Shearable {
- }
-
- @Override
-- public void shear(SoundSource shearedSoundCategory) {
-+ public void shear(SoundSource shearedSoundCategory, int looting) { // Purpur
- this.level.playSound((Player) null, (Entity) this, SoundEvents.SHEEP_SHEAR, shearedSoundCategory, 1.0F, 1.0F);
- this.setSheared(true);
- int i = 1 + this.random.nextInt(3);
-+ if (org.purpurmc.purpur.PurpurConfig.allowShearsLooting) i += looting; // Purpur
-
- for (int j = 0; j < i; ++j) {
- this.forceDrops = true; // CraftBukkit
-- ItemEntity entityitem = this.spawnAtLocation((ItemLike) Sheep.ITEM_BY_DYE.get(this.getColor()), 1);
-+ ItemEntity entityitem = this.spawnAtLocation((ItemLike) Sheep.ITEM_BY_DYE.get(this.level.purpurConfig.sheepShearJebRandomColor && hasCustomName() && getCustomName().getString().equals("jeb_") ? DyeColor.random(this.level.random) : this.getColor()), 1); // Purpur
- this.forceDrops = false; // CraftBukkit
-
- if (entityitem != null) {
-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 5437571ce76c62e9cae841e99127867fffb39f43..34fa428268a863e8e36b6340a482ec67f1199efb 100644
---- a/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/SnowGolem.java
-@@ -49,17 +49,56 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM
- private static final EntityDataAccessor DATA_PUMPKIN_ID = SynchedEntityData.defineId(SnowGolem.class, EntityDataSerializers.BYTE);
- private static final byte PUMPKIN_FLAG = 16;
- private static final float EYE_HEIGHT = 1.7F;
-+ @Nullable private java.util.UUID summoner; // Purpur
-
- public SnowGolem(EntityType extends SnowGolem> type, Level world) {
- super(type, world);
- }
-
-+ // Purpur start
-+ @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;
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.snowGolemMaxHealth);
-+ }
-+
-+ @Nullable
-+ public java.util.UUID getSummoner() {
-+ return summoner;
-+ }
-+
-+ public void setSummoner(@Nullable java.util.UUID summoner) {
-+ this.summoner = summoner;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.snowGolemAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @Override
- protected void registerGoals() {
-- this.goalSelector.addGoal(1, new RangedAttackGoal(this, 1.25D, 20, 10.0F));
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
-+ this.goalSelector.addGoal(1, new RangedAttackGoal(this, level.purpurConfig.snowGolemAttackDistance, level.purpurConfig.snowGolemSnowBallMin, level.purpurConfig.snowGolemSnowBallMax, level.purpurConfig.snowGolemSnowBallModifier)); // Purpur
- 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
- this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Mob.class, 10, true, false, (entityliving) -> {
- return entityliving instanceof Enemy;
- }));
-@@ -79,6 +118,7 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM
- public void addAdditionalSaveData(CompoundTag nbt) {
- super.addAdditionalSaveData(nbt);
- nbt.putBoolean("Pumpkin", this.hasPumpkin());
-+ if (getSummoner() != null) nbt.putUUID("Purpur.Summoner", getSummoner()); // Purpur
- }
-
- @Override
-@@ -87,12 +127,13 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM
- if (nbt.contains("Pumpkin")) {
- this.setPumpkin(nbt.getBoolean("Pumpkin"));
- }
-+ if (nbt.contains("Purpur.Summoner")) setSummoner(nbt.getUUID("Purpur.Summoner")); // Purpur
-
- }
-
- @Override
- public boolean isSensitiveToWater() {
-- return true;
-+ return this.level.purpurConfig.snowGolemTakeDamageFromWater; // Purpur
- }
-
- @Override
-@@ -103,10 +144,11 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM
- this.hurt(this.damageSources().melting, 1.0F); // CraftBukkit - DamageSource.BURN -> CraftEventFactory.MELTING
- }
-
-- if (!this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
-+ if (!this.level.purpurConfig.snowGolemBypassMobGriefing && !this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur
- 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) {
-@@ -154,10 +196,10 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM
- if (itemstack.is(Items.SHEARS) && this.readyForShearing()) {
- // CraftBukkit start
- if (!CraftEventFactory.handlePlayerShearEntityEvent(player, this, itemstack, hand)) {
-- return InteractionResult.PASS;
-+ return tryRide(player, hand); // Purpur
- }
- // CraftBukkit end
-- this.shear(SoundSource.PLAYERS);
-+ this.shear(SoundSource.PLAYERS, net.minecraft.world.item.enchantment.EnchantmentHelper.getMobLooting(player)); // Purpur
- this.gameEvent(GameEvent.SHEAR, player);
- if (!this.level.isClientSide) {
- itemstack.hurtAndBreak(1, player, (entityhuman1) -> {
-@@ -166,17 +208,27 @@ public class SnowGolem extends AbstractGolem implements Shearable, RangedAttackM
- }
-
- return InteractionResult.sidedSuccess(this.level.isClientSide);
-+ // Purpur start
-+ } else if (level.purpurConfig.snowGolemPutPumpkinBack && !hasPumpkin() && itemstack.getItem() == Blocks.CARVED_PUMPKIN.asItem()) {
-+ setPumpkin(true);
-+ if (!player.getAbilities().instabuild) {
-+ itemstack.shrink(1);
-+ }
-+ return InteractionResult.SUCCESS;
-+ // Purpur end
- } else {
-- return InteractionResult.PASS;
-+ return tryRide(player, hand); // Purpur
- }
- }
-
- @Override
-- public void shear(SoundSource shearedSoundCategory) {
-+ public void shear(SoundSource shearedSoundCategory, int looting) { // Purpur
- this.level.playSound((Player) null, (Entity) this, SoundEvents.SNOW_GOLEM_SHEAR, shearedSoundCategory, 1.0F, 1.0F);
- if (!this.level.isClientSide()) {
- this.setPumpkin(false);
- this.forceDrops = true; // CraftBukkit
-+ if (level.purpurConfig.snowGolemDropsPumpkin) // Purpur
-+ for (int i = 0; i < 1 + (org.purpurmc.purpur.PurpurConfig.allowShearsLooting ? looting : 0); i++) // Purpur
- this.spawnAtLocation(new ItemStack(Items.CARVED_PUMPKIN), 1.7F);
- this.forceDrops = false; // CraftBukkit
- }
-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 72eea6e512060fc622ca79ca87437f19a64604cc..31c89a6b8f766e1fd03608723c2d03f7f64e2e9b 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Squid.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Squid.java
-@@ -46,13 +46,66 @@ public class Squid extends WaterAnimal {
-
- public Squid(EntityType extends Squid> type, Level world) {
- super(type, world);
-- //this.random.setSeed((long) this.getId()); // Paper - we set the random to shared, do not clobber the seed
-+ if (!world.purpurConfig.entitySharedRandom) this.random.setSeed((long) this.getId()); // Paper - we set the random to shared, do not clobber the seed // Purpur
- this.tentacleSpeed = 1.0F / (this.random.nextFloat() + 1.0F) * 0.2F;
- }
-
-+ // Purpur start
-+ @Override
-+ public boolean isRidable() {
-+ return level.purpurConfig.squidRidable;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level.purpurConfig.squidControllable;
-+ }
-+
-+ protected 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);
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.squidMaxHealth);
-+ }
-+
-+ @Override
-+ public net.minecraft.world.phys.AABB getAxisForFluidCheck() {
-+ // Stops squids from floating just over the water
-+ return super.getAxisForFluidCheck().offsetY(level.purpurConfig.squidOffsetWaterCheck);
-+ }
-+
-+ public boolean canFly() {
-+ return this.level.purpurConfig.squidsCanFly;
-+ }
-+
-+ @Override
-+ public boolean isInWater() {
-+ return this.wasTouchingWater || canFly();
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.squidTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.squidAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @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
- this.goalSelector.addGoal(1, new Squid.SquidFleeGoal());
- }
-
-@@ -121,6 +174,7 @@ public class Squid extends WaterAnimal {
- }
-
- if (this.isInWaterOrBubble()) {
-+ if (canFly()) setNoGravity(!wasTouchingWater); // Purpur
- if (this.tentacleMovement < 3.1415927F) {
- float f = this.tentacleMovement / 3.1415927F;
-
-@@ -244,11 +298,43 @@ public class Squid extends WaterAnimal {
-
- @Override
- public void tick() {
-+ // Purpur start
-+ 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.setMovementVector((float) dir.getX(), (float) dir.getY(), (float) dir.getZ());
-+ } else {
-+ squid.setMovementVector(0.0F, 0.0F, 0.0F);
-+ }
-+ return;
-+ }
-+ // Purpur end
-+
- int i = this.squid.getNoActionTime();
-
- if (i > 100) {
- this.squid.setMovementVector(0.0F, 0.0F, 0.0F);
-- } else if (this.squid.getRandom().nextInt(reducedTickDelay(50)) == 0 || !this.squid.wasTouchingWater || !this.squid.hasMovementVector()) {
-+ } else if (this.squid.getRandom().nextInt(reducedTickDelay(50)) == 0 || !this.squid.isInWater() || !this.squid.hasMovementVector()) { // Purpur
- float f = this.squid.getRandom().nextFloat() * 6.2831855F;
- float f1 = Mth.cos(f) * 0.2F;
- float f2 = -0.1F + this.squid.getRandom().nextFloat() * 0.2F;
-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 b05b560b7570e97bc234b75f26233909fcf575b3..e4b4bf5ef228c0460fdab966d4c9b5c428f78b9a 100644
---- a/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java
-@@ -42,6 +42,33 @@ public class TropicalFish extends AbstractSchoolingFish implements VariantHolder
- super(type, world);
- }
-
-+ // Purpur start
-+ @Override
-+ public boolean isRidable() {
-+ return level.purpurConfig.tropicalFishRidable;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level.purpurConfig.tropicalFishControllable;
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.tropicalFishMaxHealth);
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.tropicalFishTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.tropicalFishAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- 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 81dab77f525ae667614f940c4ff5ec308a9579a2..52eff7a4d3a34a566bc3bc03e6643c494c757156 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java
-@@ -83,6 +83,43 @@ public class Turtle extends Animal {
- this.setMaxUpStep(1.0F);
- }
-
-+ // Purpur start
-+ @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;
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.turtleMaxHealth);
-+ }
-+
-+ @Override
-+ public int getPurpurBreedTime() {
-+ return this.level.purpurConfig.turtleBreedingTicks;
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.turtleTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.turtleAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- public void setHomePos(BlockPos pos) {
- this.entityData.set(Turtle.HOME_POS, pos.immutable()); // Paper - called with mutablepos...
- }
-@@ -185,6 +222,7 @@ public class Turtle extends Animal {
-
- @Override
- protected void registerGoals() {
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
- 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));
-@@ -342,13 +380,15 @@ public class Turtle extends Animal {
- org.bukkit.craftbukkit.event.CraftEventFactory.entityDamage = null; // CraftBukkit
- }
-
-- private static class TurtleMoveControl extends MoveControl {
-+ private static class TurtleMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur
-
- private final Turtle turtle;
-+ private final org.purpurmc.purpur.controller.WaterMoveControllerWASD waterController; // Purpur
-
- TurtleMoveControl(Turtle turtle) {
- super(turtle);
- this.turtle = turtle;
-+ waterController = new org.purpurmc.purpur.controller.WaterMoveControllerWASD(turtle, 0.25D); // Purpur
- }
-
- private void updateSpeed() {
-@@ -367,8 +407,18 @@ public class Turtle extends Animal {
-
- }
-
-+ // Purpur start
-+ public void purpurTick(Player rider) {
-+ if (turtle.isInWater()) {
-+ waterController.purpurTick(rider);
-+ } else {
-+ super.purpurTick(rider);
-+ }
-+ }
-+ // Purpur end
-+
- @Override
-- public void tick() {
-+ public void vanillaTick() { // Purpur
- this.updateSpeed();
- if (this.operation == MoveControl.Operation.MOVE_TO && !this.turtle.getNavigation().isDone()) {
- double d0 = this.wantedX - this.turtle.getX();
-@@ -384,7 +434,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 612601b2536dc522356d4dd2c2ea1192f6435f72..e0ca657b0aea52ab6a91c256c1bfad1e5028f6e0 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Wolf.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Wolf.java
-@@ -10,6 +10,7 @@ import net.minecraft.network.syncher.EntityDataAccessor;
- import net.minecraft.network.syncher.EntityDataSerializers;
- import net.minecraft.network.syncher.SynchedEntityData;
- import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.sounds.SoundEvent;
- import net.minecraft.sounds.SoundEvents;
- import net.minecraft.tags.BlockTags;
-@@ -17,9 +18,12 @@ import net.minecraft.util.Mth;
- import net.minecraft.util.RandomSource;
- import net.minecraft.util.TimeUtil;
- import net.minecraft.util.valueproviders.UniformInt;
-+import net.minecraft.world.DifficultyInstance;
- import net.minecraft.world.InteractionHand;
- import net.minecraft.world.InteractionResult;
- import net.minecraft.world.damagesource.DamageSource;
-+import net.minecraft.world.effect.MobEffectInstance;
-+import net.minecraft.world.effect.MobEffects;
- import net.minecraft.world.entity.AgeableMob;
- import net.minecraft.world.entity.Entity;
- import net.minecraft.world.entity.EntityDimensions;
-@@ -29,6 +33,7 @@ import net.minecraft.world.entity.Mob;
- import net.minecraft.world.entity.MobSpawnType;
- import net.minecraft.world.entity.NeutralMob;
- import net.minecraft.world.entity.Pose;
-+import net.minecraft.world.entity.SpawnGroupData;
- import net.minecraft.world.entity.TamableAnimal;
- import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
- import net.minecraft.world.entity.ai.attributes.Attributes;
-@@ -37,6 +42,7 @@ import net.minecraft.world.entity.ai.goal.BegGoal;
- import net.minecraft.world.entity.ai.goal.BreedGoal;
- import net.minecraft.world.entity.ai.goal.FloatGoal;
- import net.minecraft.world.entity.ai.goal.FollowOwnerGoal;
-+import net.minecraft.world.entity.ai.goal.Goal;
- import net.minecraft.world.entity.ai.goal.LeapAtTargetGoal;
- import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
- import net.minecraft.world.entity.ai.goal.MeleeAttackGoal;
-@@ -64,6 +70,7 @@ import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.item.Items;
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.LevelAccessor;
-+import net.minecraft.world.level.ServerLevelAccessor;
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.gameevent.GameEvent;
- import net.minecraft.world.level.pathfinder.BlockPathTypes;
-@@ -83,6 +90,37 @@ public class Wolf extends TamableAnimal implements NeutralMob {
-
- return entitytypes == EntityType.SHEEP || entitytypes == EntityType.RABBIT || entitytypes == EntityType.FOX;
- };
-+ // Purpur start - rabid wolf spawn chance
-+ private boolean isRabid = false;
-+ private static final Predicate RABID_PREDICATE = entity -> entity instanceof ServerPlayer || entity instanceof Mob;
-+ private final Goal PATHFINDER_VANILLA = new NonTameRandomTargetGoal<>(this, Animal.class, false, PREY_SELECTOR);
-+ private final Goal PATHFINDER_RABID = new NonTameRandomTargetGoal<>(this, LivingEntity.class, false, RABID_PREDICATE);
-+ private static final class AvoidRabidWolfGoal extends AvoidEntityGoal {
-+ private final Wolf wolf;
-+
-+ public AvoidRabidWolfGoal(Wolf wolf, float distance, double minSpeed, double maxSpeed) {
-+ super(wolf, Wolf.class, distance, minSpeed, maxSpeed);
-+ this.wolf = wolf;
-+ }
-+
-+ @Override
-+ public boolean canUse() {
-+ return super.canUse() && !this.wolf.isRabid() && this.toAvoid != null && this.toAvoid.isRabid(); // wolves which are not rabid run away from rabid wolves
-+ }
-+
-+ @Override
-+ public void start() {
-+ this.wolf.setTarget(null);
-+ super.start();
-+ }
-+
-+ @Override
-+ public void tick() {
-+ this.wolf.setTarget(null);
-+ super.tick();
-+ }
-+ }
-+ // Purpur end
- private static final float START_HEALTH = 8.0F;
- private static final float TAME_HEALTH = 20.0F;
- private float interestedAngle;
-@@ -102,12 +140,93 @@ public class Wolf extends TamableAnimal implements NeutralMob {
- this.setPathfindingMalus(BlockPathTypes.DANGER_POWDER_SNOW, -1.0F);
- }
-
-+ // Purpur start
-+ @Override
-+ public boolean isRidable() {
-+ return level.purpurConfig.wolfRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level.purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level.purpurConfig.wolfRidableInWater;
-+ }
-+
-+ public void onMount(Player rider) {
-+ super.onMount(rider);
-+ setInSittingPose(false);
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level.purpurConfig.wolfControllable;
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.wolfMaxHealth);
-+ }
-+
-+ @Override
-+ public int getPurpurBreedTime() {
-+ return this.level.purpurConfig.wolfBreedingTicks;
-+ }
-+
-+ public boolean isRabid() {
-+ return this.isRabid;
-+ }
-+
-+ public void setRabid(boolean isRabid) {
-+ this.isRabid = isRabid;
-+ updatePathfinders(true);
-+ }
-+
-+ public void updatePathfinders(boolean modifyEffects) {
-+ this.targetSelector.removeGoal(PATHFINDER_VANILLA);
-+ this.targetSelector.removeGoal(PATHFINDER_RABID);
-+ if (this.isRabid) {
-+ setTame(false);
-+ setOwnerUUID(null);
-+ this.targetSelector.addGoal(5, PATHFINDER_RABID);
-+ if (modifyEffects) this.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 1200));
-+ } else {
-+ this.targetSelector.addGoal(5, PATHFINDER_VANILLA);
-+ this.stopBeingAngry();
-+ if (modifyEffects) this.removeEffect(MobEffects.CONFUSION);
-+ }
-+ }
-+
-+ @Override
-+ public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, MobSpawnType type, @Nullable SpawnGroupData data, @Nullable CompoundTag nbt) {
-+ this.isRabid = world.getLevel().purpurConfig.wolfNaturalRabid > 0.0D && random.nextDouble() <= world.getLevel().purpurConfig.wolfNaturalRabid;
-+ this.updatePathfinders(false);
-+ return super.finalizeSpawn(world, difficulty, type, data, nbt);
-+ }
-+
-+ @Override
-+ public void tame(Player player) {
-+ setCollarColor(level.purpurConfig.wolfDefaultCollarColor);
-+ super.tame(player);
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.wolfTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.wolfAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @Override
- protected void registerGoals() {
- this.goalSelector.addGoal(1, new FloatGoal(this));
-+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
- this.goalSelector.addGoal(1, new Wolf.WolfPanicGoal(1.5D));
- this.goalSelector.addGoal(2, new SitWhenOrderedToGoal(this));
- this.goalSelector.addGoal(3, new Wolf.WolfAvoidEntityGoal<>(this, Llama.class, 24.0F, 1.5D, 1.5D));
-+ this.goalSelector.addGoal(3, new AvoidRabidWolfGoal(this, 24.0F, 1.5D, 1.5D)); // Purpur
- this.goalSelector.addGoal(4, new LeapAtTargetGoal(this, 0.4F));
- this.goalSelector.addGoal(5, new MeleeAttackGoal(this, 1.0D, true));
- this.goalSelector.addGoal(6, new FollowOwnerGoal(this, 1.0D, 10.0F, 2.0F, false));
-@@ -116,11 +235,12 @@ public class Wolf extends TamableAnimal implements NeutralMob {
- this.goalSelector.addGoal(9, new BegGoal(this, 8.0F));
- this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Player.class, 8.0F));
- this.goalSelector.addGoal(10, new RandomLookAroundGoal(this));
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
- this.targetSelector.addGoal(1, new OwnerHurtByTargetGoal(this));
- this.targetSelector.addGoal(2, new OwnerHurtTargetGoal(this));
- this.targetSelector.addGoal(3, (new HurtByTargetGoal(this, new Class[0])).setAlertOthers());
- this.targetSelector.addGoal(4, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt));
-- this.targetSelector.addGoal(5, new NonTameRandomTargetGoal<>(this, Animal.class, false, Wolf.PREY_SELECTOR));
-+ // this.targetSelector.addGoal(5, new NonTameRandomTargetGoal<>(this, Animal.class, false, Wolf.PREY_SELECTOR)); // Purpur - moved to updatePathfinders()
- this.targetSelector.addGoal(6, new NonTameRandomTargetGoal<>(this, Turtle.class, false, Turtle.BABY_ON_LAND_SELECTOR));
- this.targetSelector.addGoal(7, new NearestAttackableTargetGoal<>(this, AbstractSkeleton.class, false));
- this.targetSelector.addGoal(8, new ResetUniversalAngerTargetGoal<>(this, true));
-@@ -165,6 +285,7 @@ public class Wolf extends TamableAnimal implements NeutralMob {
- public void addAdditionalSaveData(CompoundTag nbt) {
- super.addAdditionalSaveData(nbt);
- nbt.putByte("CollarColor", (byte) this.getCollarColor().getId());
-+ nbt.putBoolean("Purpur.IsRabid", this.isRabid); // Purpur
- this.addPersistentAngerSaveData(nbt);
- }
-
-@@ -174,6 +295,10 @@ public class Wolf extends TamableAnimal implements NeutralMob {
- if (nbt.contains("CollarColor", 99)) {
- this.setCollarColor(DyeColor.byId(nbt.getInt("CollarColor")));
- }
-+ // Purpur start
-+ this.isRabid = nbt.getBoolean("Purpur.IsRabid");
-+ this.updatePathfinders(false);
-+ // Purpur end
-
- this.readPersistentAngerSaveData(this.level, nbt);
- }
-@@ -218,6 +343,11 @@ public class Wolf extends TamableAnimal implements NeutralMob {
- public void tick() {
- super.tick();
- if (this.isAlive()) {
-+ // Purpur start
-+ if (this.age % 300 == 0 && this.isRabid()) {
-+ this.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 400));
-+ }
-+ // Purpur end
- this.interestedAngleO = this.interestedAngle;
- if (this.isInterested()) {
- this.interestedAngle += (1.0F - this.interestedAngle) * 0.4F;
-@@ -412,7 +542,7 @@ public class Wolf extends TamableAnimal implements NeutralMob {
- }
-
- // CraftBukkit - added event call and isCancelled check.
-- if (this.random.nextInt(3) == 0 && !CraftEventFactory.callEntityTameEvent(this, player).isCancelled()) {
-+ if ((this.level.purpurConfig.alwaysTameInCreative && player.getAbilities().instabuild) || (this.random.nextInt(3) == 0 && !CraftEventFactory.callEntityTameEvent(this, player).isCancelled())) { // Purpur
- this.tame(player);
- this.navigation.stop();
- this.setTarget((LivingEntity) null);
-@@ -424,6 +554,20 @@ public class Wolf extends TamableAnimal implements NeutralMob {
-
- return InteractionResult.SUCCESS;
- }
-+ // Purpur start
-+ else if (this.level.purpurConfig.wolfMilkCuresRabies && itemstack.getItem() == Items.MILK_BUCKET && this.isRabid()) {
-+ if (!player.isCreative()) {
-+ player.setItemInHand(hand, new ItemStack(Items.BUCKET));
-+ }
-+ this.setRabid(false);
-+ for (int i = 0; i < 10; ++i) {
-+ ((ServerLevel) level).sendParticles(((ServerLevel) level).players(), null, ParticleTypes.HAPPY_VILLAGER,
-+ getX() + random.nextFloat(), getY() + (random.nextFloat() * 1.5), getZ() + random.nextFloat(), 1,
-+ random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, random.nextGaussian() * 0.05D, 0, true);
-+ }
-+ return InteractionResult.SUCCESS;
-+ }
-+ // Purpur end
-
- return super.mobInteract(player, hand);
- }
-diff --git a/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java b/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java
-index 5b5d3adacda11bb0d38a4a8aebd40a570dfe692f..3dc4373b2181bef0599813723910651d9bba2223 100644
---- a/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/allay/Allay.java
-@@ -101,10 +101,23 @@ public class Allay extends PathfinderMob implements InventoryCarrier {
- private float spinningAnimationTicks;
- private float spinningAnimationTicks0;
- public boolean forceDancing = false; // CraftBukkit
-+ private org.purpurmc.purpur.controller.FlyingMoveControllerWASD purpurController; // Purpur
-
- public Allay(EntityType extends Allay> type, Level world) {
- super(type, world);
-- this.moveControl = new FlyingMoveControl(this, 20, true);
-+ // Purpur start
-+ 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
- this.setCanPickUpLoot(this.canPickUpLoot());
- EntityPositionSource entitypositionsource = new EntityPositionSource(this, this.getEyeHeight());
-
-@@ -119,6 +132,28 @@ public class Allay extends PathfinderMob implements InventoryCarrier {
- }
- // CraftBukkit end
-
-+ // Purpur start
-+ @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
-+ }
-+ // Purpur end
-+
- @Override
- protected Brain.Provider brainProvider() {
- return Brain.provider(Allay.MEMORY_TYPES, Allay.SENSOR_TYPES);
-@@ -370,9 +405,31 @@ public class Allay extends PathfinderMob implements InventoryCarrier {
-
- @Override
- public boolean wantsToPickUp(ItemStack stack) {
-- ItemStack itemstack1 = this.getItemInHand(InteractionHand.MAIN_HAND);
--
-- return !itemstack1.isEmpty() && this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && this.inventory.canAddItem(stack) && this.allayConsidersItemEqual(itemstack1, stack);
-+ // Purpur start
-+ if (!this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
-+ return false;
-+ }
-+ ItemStack itemStack = this.getItemInHand(InteractionHand.MAIN_HAND);
-+ if (itemStack.isEmpty()) {
-+ return false;
-+ }
-+ if (!allayConsidersItemEqual(itemStack, stack)) {
-+ return false;
-+ }
-+ if (!this.inventory.canAddItem(stack)) {
-+ return false;
-+ }
-+ for (String tag : this.level.purpurConfig.allayRespectNBT) {
-+ if (stack.hasTag() && itemStack.hasTag()) {
-+ Tag tag1 = stack.getTag().get(tag);
-+ Tag tag2 = itemStack.getTag().get(tag);
-+ if (!Objects.equals(tag1, tag2)) {
-+ return false;
-+ }
-+ }
-+ }
-+ return true;
-+ // Purpur end
- }
-
- private boolean allayConsidersItemEqual(ItemStack stack, ItemStack stack2) {
-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 77d7c15d328b8dcf7b458a4e4083018bb6aeae46..498f6583838e069ada5fb87eb3c91773d2d8088e 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
-@@ -98,6 +98,43 @@ public class Axolotl extends Animal implements LerpingModel, VariantHolder getModelRotationValues() {
- return this.modelRotationValues;
-@@ -517,14 +554,22 @@ public class Axolotl extends Animal implements LerpingModel, 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
-+ private org.purpurmc.purpur.controller.WaterMoveControllerWASD purpurWaterController; // Purpur
-
- public Frog(EntityType extends Animal> type, Level world) {
- super(type, world);
- this.lookControl = new Frog.FrogLookControl(this);
- this.setPathfindingMalus(BlockPathTypes.WATER, 4.0F);
- this.setPathfindingMalus(BlockPathTypes.TRAPDOOR, -1.0F);
-- this.moveControl = new SmoothSwimmingMoveControl(this, 85, 10, 0.02F, 0.1F, true);
-+ // Purpur start
-+ 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
- this.setMaxUpStep(1.0F);
- }
-
-+ // Purpur start
-+ @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
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
-+ }
-+
-+ @Override
-+ public float getJumpPower() {
-+ return (getRider() != null && isControllable()) ? level.purpurConfig.frogRidableJumpHeight * this.getBlockJumpFactor() : super.getJumpPower();
-+ }
-+
-+ public int getPurpurBreedTime() {
-+ return this.level.purpurConfig.frogBreedingTicks;
-+ }
-+ // Purpur end
-+
- @Override
- protected Brain.Provider brainProvider() {
- return Brain.provider(MEMORY_TYPES, SENSOR_TYPES);
-@@ -372,7 +425,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
- FrogLookControl(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 561ccb25a5c50a51879f0a2b8e4cb90c29ac6d0b..f85210c8d0d3e27398f52509f5e078f1d0d3eb93 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
-@@ -45,13 +45,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
-
- 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
-+ 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
- this.lookControl = new SmoothSwimmingLookControl(this, 10);
- }
-
-+ // Purpur start
-+ @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
-+ }
-+ // Purpur end
-+
- @Override
- protected PathNavigation createNavigation(Level world) {
- return new WaterBoundPathNavigation(this, world);
-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 b8134f660dc0678db6106e7d69a8f1451e3bff2c..33cdac0b727604d351e350a997bdabff2b7797f0 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
-@@ -89,6 +89,38 @@ public class Goat extends Animal {
- return InstrumentItem.create(Items.GOAT_HORN, (Holder) holderset.getRandomElement(randomsource).get());
- }
-
-+ // Purpur start
-+ @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;
-+ }
-+
-+ @Override
-+ public int getPurpurBreedTime() {
-+ return this.level.purpurConfig.goatBreedingTicks;
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.goatTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.goatAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @Override
- protected Brain.Provider brainProvider() {
- return Brain.provider(Goat.MEMORY_TYPES, Goat.SENSOR_TYPES);
-@@ -191,7 +223,7 @@ public class Goat extends Animal {
- private int behaviorTick = 0; // Pufferfish
- @Override
- protected void customServerAiStep() {
-- if (this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish
-+ if ((getRider() == null || !this.isControllable()) && this.behaviorTick++ % this.activatedPriority == 0) // Pufferfish // Purpur - only use brain if no rider
- this.getBrain().tick((ServerLevel) this.level, this);
- GoatAi.updateActivity(this);
- super.customServerAiStep();
-@@ -385,6 +417,7 @@ public class Goat extends Animal {
-
- // Paper start - Goat ram API
- public void ram(net.minecraft.world.entity.LivingEntity entity) {
-+ if(!new org.purpurmc.purpur.event.entity.GoatRamEntityEvent((org.bukkit.entity.Goat) getBukkitEntity(), (org.bukkit.entity.LivingEntity) entity.getBukkitLivingEntity()).callEvent()) return; // Purpur
- Brain brain = this.getBrain();
- brain.setMemory(MemoryModuleType.RAM_TARGET, entity.position());
- brain.eraseMemory(MemoryModuleType.RAM_COOLDOWN_TICKS);
-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 47cd69f91bbc2e2be9ec970674adc522e21593c8..c044ed3a96f10584fd5aec836624bca1b414182d 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
-@@ -144,12 +144,60 @@ 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.setMaxUpStep(1.0F);
- this.createInventory();
- }
-
-+ // Purpur start
-+ @Override
-+ public boolean isRidable() {
-+ return false; // vanilla handles
-+ }
-+
-+ @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
-+
- @Override
- protected void registerGoals() {
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HorseHasRider(this)); // Purpur
- 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));
-@@ -160,6 +208,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
-
- this.addBehaviourGoals();
- }
-@@ -336,7 +385,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener,
-
- @Override
- protected int calculateFallDamage(float fallDistance, float damageMultiplier) {
-- return Mth.ceil((fallDistance * 0.5F - 3.0F) * damageMultiplier);
-+ return Mth.ceil((fallDistance * 0.5F - this.safeFallDistance) * damageMultiplier);
- }
-
- protected int getInventorySize() {
-@@ -1252,7 +1301,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, entityNbt);
- }
-
-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 e0dfee0e0ce091d5ae0ec740e939c2c50915c104..7ad29aacc73ca1cb98b76ad36b92a3edb2256629 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
-@@ -15,6 +15,43 @@ public class Donkey extends AbstractChestedHorse {
- super(type, world);
- }
-
-+ // Purpur start
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level.purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level.purpurConfig.donkeyRidableInWater;
-+ }
-+
-+ @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);
-+ }
-+
-+ @Override
-+ public int getPurpurBreedTime() {
-+ return this.level.purpurConfig.donkeyBreedingTicks;
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.donkeyTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.donkeyAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @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 79a2b3c8df70a9a73ad44560a4a6129f91db8e16..fb433878731b824b4d595b7f28626f25bdfabbeb 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
-@@ -40,6 +40,43 @@ public class Horse extends AbstractHorse implements VariantHolder {
- super(type, world);
- }
-
-+ // Purpur start
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level.purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level.purpurConfig.horseRidableInWater;
-+ }
-+
-+ @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);
-+ }
-+
-+ @Override
-+ public int getPurpurBreedTime() {
-+ return this.level.purpurConfig.horseBreedingTicks;
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.horseTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.horseAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @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 7ae0e4b3aa8e861500ddc7b38aa671258b532fcd..309fd5bccadcc584354d328bd31a6f4591c2d0a0 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
-@@ -73,11 +73,86 @@ public class Llama extends AbstractChestedHorse implements VariantHolder type, Level world) {
- super(type, world);
-+ // Purpur start
-+ this.moveControl = new org.purpurmc.purpur.controller.MoveControllerWASD(this) {
-+ @Override
-+ public void tick() {
-+ if (entity.getRider() != null && entity.isControllable() && isSaddled()) {
-+ purpurTick(entity.getRider());
-+ } else {
-+ vanillaTick();
-+ }
-+ }
-+ };
-+ this.lookControl = new org.purpurmc.purpur.controller.LookControllerWASD(this) {
-+ @Override
-+ public void tick() {
-+ if (entity.getRider() != null && entity.isControllable() && isSaddled()) {
-+ purpurTick(entity.getRider());
-+ } else {
-+ vanillaTick();
-+ }
-+ }
-+ };
-+ // Purpur end
-+ }
-+
-+ // Purpur start
-+ @Override
-+ public boolean isRidable() {
-+ return level.purpurConfig.llamaRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level.purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level.purpurConfig.llamaRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level.purpurConfig.llamaControllable;
-+ }
-+
-+ @Override
-+ public boolean isSaddled() {
-+ return super.isSaddled() || (isTamed() && getSwag() != null);
-+ }
-+
-+ @Override
-+ public float generateMaxHealth(RandomSource random) {
-+ return (float) generateMaxHealth(this.level.purpurConfig.llamaMaxHealthMin, this.level.purpurConfig.llamaMaxHealthMax);
-+ }
-+
-+ @Override
-+ public double generateJumpStrength(RandomSource random) {
-+ return generateJumpStrength(this.level.purpurConfig.llamaJumpStrengthMin, this.level.purpurConfig.llamaJumpStrengthMax);
-+ }
-+
-+ @Override
-+ public double generateSpeed(RandomSource random) {
-+ return generateSpeed(this.level.purpurConfig.llamaMovementSpeedMin, this.level.purpurConfig.llamaMovementSpeedMax);
-+ }
-+
-+ @Override
-+ public int getPurpurBreedTime() {
-+ return this.level.purpurConfig.llamaBreedingTicks;
- }
-
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.llamaTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.llamaAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- public boolean isTraderLlama() {
- return false;
- }
-@@ -110,7 +185,7 @@ public class Llama extends AbstractChestedHorse implements VariantHolder public
- super.jumpFromGround();
- double d0 = this.moveControl.getSpeedModifier();
-
-diff --git a/src/main/java/net/minecraft/world/entity/boss/EnderDragonPart.java b/src/main/java/net/minecraft/world/entity/boss/EnderDragonPart.java
-index de84a00ce2d2b7c654b08164489624e124568346..998c72513df1dcd2b1316b320b3d5e7ca8e69fd4 100644
---- a/src/main/java/net/minecraft/world/entity/boss/EnderDragonPart.java
-+++ b/src/main/java/net/minecraft/world/entity/boss/EnderDragonPart.java
-@@ -24,6 +24,13 @@ public class EnderDragonPart extends Entity {
- this.name = name;
- }
-
-+ // Purpur start
-+ @Override
-+ public net.minecraft.world.InteractionResult interact(net.minecraft.world.entity.player.Player player, net.minecraft.world.InteractionHand hand) {
-+ return parentMob.isAlive() ? parentMob.tryRide(player, hand) : net.minecraft.world.InteractionResult.PASS;
-+ }
-+ // Purpur end
-+
- @Override
- protected void defineSynchedData() {
- }
-diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java
-index 64f17b4a22454b59968787089253eaba0a04c1f2..e3fe5f18c77e36479eaeb7edfd2a3eb919c342d6 100644
---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java
-+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java
-@@ -30,6 +30,12 @@ public class EndCrystal extends Entity {
- private static final EntityDataAccessor DATA_SHOW_BOTTOM = SynchedEntityData.defineId(EndCrystal.class, EntityDataSerializers.BOOLEAN);
- public int time;
- public boolean generatedByDragonFight = false; // Paper - Fix invulnerable end crystals
-+ // Purpur start
-+ private net.minecraft.world.entity.monster.Phantom targetPhantom;
-+ private int phantomBeamTicks = 0;
-+ private int phantomDamageCooldown = 0;
-+ private int idleCooldown = 0;
-+ // Purpur end
-
- public EndCrystal(EntityType extends EndCrystal> type, Level world) {
- super(type, world);
-@@ -77,9 +83,69 @@ public class EndCrystal extends Entity {
- }
- }
- // Paper end
-+ if (this.level.purpurConfig.endCrystalCramming > 0 && this.level.getEntitiesOfClass(EndCrystal.class, getBoundingBox()).size() > this.level.purpurConfig.endCrystalCramming) this.hurt(this.damageSources().cramming(), 6.0F); // Purpur
- }
-
-+ // Purpur start
-+ if (level.purpurConfig.phantomAttackedByCrystalRadius <= 0 || --idleCooldown > 0) {
-+ return; // on cooldown
-+ }
-+
-+ if (targetPhantom == null) {
-+ for (net.minecraft.world.entity.monster.Phantom phantom : level.getEntitiesOfClass(net.minecraft.world.entity.monster.Phantom.class, getBoundingBox().inflate(level.purpurConfig.phantomAttackedByCrystalRadius))) {
-+ if (phantom.hasLineOfSight(this)) {
-+ attackPhantom(phantom);
-+ break;
-+ }
-+ }
-+ } else {
-+ setBeamTarget(new BlockPos(targetPhantom).offset(0, -2, 0));
-+ if (--phantomBeamTicks > 0 && targetPhantom.isAlive()) {
-+ phantomDamageCooldown--;
-+ if (targetPhantom.hasLineOfSight(this)) {
-+ if (phantomDamageCooldown <= 0) {
-+ phantomDamageCooldown = 20;
-+ targetPhantom.hurt(targetPhantom.damageSources().indirectMagic(this, this), level.purpurConfig.phantomAttackedByCrystalDamage);
-+ }
-+ } else {
-+ forgetPhantom(); // no longer in sight
-+ }
-+ } else {
-+ forgetPhantom(); // attacked long enough
-+ }
-+ }
-+ }
-+
-+ private void attackPhantom(net.minecraft.world.entity.monster.Phantom phantom) {
-+ phantomDamageCooldown = 0;
-+ phantomBeamTicks = 60;
-+ targetPhantom = phantom;
-+ }
-+
-+ private void forgetPhantom() {
-+ targetPhantom = null;
-+ setBeamTarget(null);
-+ phantomBeamTicks = 0;
-+ phantomDamageCooldown = 0;
-+ idleCooldown = 60;
-+ }
-+
-+ public boolean shouldExplode() {
-+ return showsBottom() ? level.purpurConfig.basedEndCrystalExplode : level.purpurConfig.baselessEndCrystalExplode;
-+ }
-+
-+ public float getExplosionPower() {
-+ return (float) (showsBottom() ? level.purpurConfig.basedEndCrystalExplosionPower : level.purpurConfig.baselessEndCrystalExplosionPower);
-+ }
-+
-+ public boolean hasExplosionFire() {
-+ return showsBottom() ? level.purpurConfig.basedEndCrystalExplosionFire : level.purpurConfig.baselessEndCrystalExplosionFire;
-+ }
-+
-+ public Level.ExplosionInteraction getExplosionEffect() {
-+ return showsBottom() ? level.purpurConfig.basedEndCrystalExplosionEffect : level.purpurConfig.baselessEndCrystalExplosionEffect;
- }
-+ // Purpur end
-
- @Override
- protected void addAdditionalSaveData(CompoundTag nbt) {
-@@ -124,17 +190,19 @@ public class EndCrystal extends Entity {
- // CraftBukkit end
- this.remove(Entity.RemovalReason.KILLED);
- if (!source.is(DamageTypeTags.IS_EXPLOSION)) {
-+ if (shouldExplode()) {// Purpur
- DamageSource damagesource1 = source.getEntity() != null ? this.damageSources().explosion(this, source.getEntity()) : null;
-
- // CraftBukkit start
-- ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), 6.0F, false);
-+ ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), getExplosionPower(), hasExplosionFire()); // Purpur
- this.level.getCraftServer().getPluginManager().callEvent(event);
- if (event.isCancelled()) {
- this.unsetRemoved();
- return false;
- }
-- this.level.explode(this, damagesource1, (ExplosionDamageCalculator) null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.BLOCK);
-+ this.level.explode(this, damagesource1, (ExplosionDamageCalculator) null, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), getExplosionEffect()); // Purpur
- // CraftBukkit end
-+ } else this.unsetRemoved(); // Purpur
- }
-
- this.onDestroyedBy(source);
-diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
-index 3f66986948d0b43a75454389b7ec8517e2d50899..b6ac41633e91f6ee2755d1f05aac4c8046a4aa8a 100644
---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
-+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
-@@ -104,9 +104,11 @@ public class EnderDragon extends Mob implements Enemy {
- @Nullable
- private BlockPos podium;
- // Paper end
-+ private boolean hadRider; // Purpur
-
- public EnderDragon(EntityType extends EnderDragon> entitytypes, Level world) {
- super(EntityType.ENDER_DRAGON, world);
-+ this.explosionSource = new Explosion(world, this, null, null, Double.NaN, Double.NaN, Double.NaN, Float.NaN, true, Explosion.BlockInteraction.DESTROY); // Purpur - moved instantiation from field
- this.subEntities = new EnderDragonPart[]{this.head, this.neck, this.body, this.tail1, this.tail2, this.tail3, this.wing1, this.wing2};
- this.setHealth(this.getMaxHealth());
- this.noPhysics = true;
-@@ -118,8 +120,59 @@ public class EnderDragon extends Mob implements Enemy {
- }
-
- this.phaseManager = new EnderDragonPhaseManager(this);
-- this.explosionSource = new Explosion(world, this, null, null, Double.NaN, Double.NaN, Double.NaN, Float.NaN, true, Explosion.BlockInteraction.DESTROY); // CraftBukkit
-+
-+ // Purpur start
-+ 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
-+ }
-+
-+ // Purpur start
-+ @Override
-+ public boolean isRidable() {
-+ return level.purpurConfig.enderDragonRidable;
-+ }
-+
-+ @Override
-+ public boolean dismountsUnderwater() {
-+ return level.purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level.purpurConfig.enderDragonRidableInWater;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level.purpurConfig.enderDragonControllable;
-+ }
-+
-+ @Override
-+ public double getMaxY() {
-+ return level.purpurConfig.enderDragonMaxY;
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.enderDragonMaxHealth);
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.enderDragonTakeDamageFromWater;
- }
-+ // Purpur end
-
- public static AttributeSupplier.Builder createAttributes() {
- return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 200.0D);
-@@ -182,6 +235,37 @@ public class EnderDragon extends Mob implements Enemy {
-
- @Override
- public void aiStep() {
-+ // Purpur start
-+ 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
-+
- this.processFlappingMovement();
- if (this.level.isClientSide) {
- this.setHealth(this.getHealth());
-@@ -195,6 +279,8 @@ public class EnderDragon extends Mob implements Enemy {
- float f;
-
- if (this.isDeadOrDying()) {
-+ if (hasRider) ejectPassengers(); // Purpur
-+
- float f1 = (this.random.nextFloat() - 0.5F) * 8.0F;
-
- f = (this.random.nextFloat() - 0.5F) * 4.0F;
-@@ -207,9 +293,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
- this.flapTime += 0.1F;
-- } else if (this.inWall) {
-+ } else if (!hasRider && this.inWall) { // Purpur
- this.flapTime += f * 0.5F;
- } else {
- this.flapTime += f;
-@@ -254,7 +340,7 @@ public class EnderDragon extends Mob implements Enemy {
- }
-
- this.phaseManager.getCurrentPhase().doClientTick();
-- } else {
-+ } else if (!hasRider) { // Purpur
- DragonPhaseInstance idragoncontroller = this.phaseManager.getCurrentPhase();
-
- idragoncontroller.doServerTick();
-@@ -323,7 +409,7 @@ public class EnderDragon extends Mob implements Enemy {
- this.tickPart(this.body, (double) (f11 * 0.5F), 0.0D, (double) (-f12 * 0.5F));
- this.tickPart(this.wing1, (double) (f12 * 4.5F), 2.0D, (double) (f11 * 4.5F));
- this.tickPart(this.wing2, (double) (f12 * -4.5F), 2.0D, (double) (f11 * -4.5F));
-- if (!this.level.isClientSide && this.hurtTime == 0) {
-+ if (!hasRider && !this.level.isClientSide && this.hurtTime == 0) { // Purpur
- this.knockBack(this.level.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(this.level.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(this.level.getEntities((Entity) this, this.head.getBoundingBox().inflate(1.0D), EntitySelector.NO_CREATIVE_OR_SPECTATOR));
-@@ -367,7 +453,7 @@ public class EnderDragon extends Mob implements Enemy {
- }
-
- if (!this.level.isClientSide) {
-- this.inWall = this.checkWalls(this.head.getBoundingBox()) | this.checkWalls(this.neck.getBoundingBox()) | this.checkWalls(this.body.getBoundingBox());
-+ this.inWall = !hasRider && this.checkWalls(this.head.getBoundingBox()) | this.checkWalls(this.neck.getBoundingBox()) | this.checkWalls(this.body.getBoundingBox()); // Purpur
- if (this.dragonFight != null) {
- this.dragonFight.updateDragon(this);
- }
-@@ -499,7 +585,7 @@ public class EnderDragon extends Mob implements Enemy {
- BlockState iblockdata = this.level.getBlockState(blockposition);
-
- if (!iblockdata.isAir() && !iblockdata.is(BlockTags.DRAGON_TRANSPARENT)) {
-- if (this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && !iblockdata.is(BlockTags.DRAGON_IMMUNE)) {
-+ if ((this.level.purpurConfig.enderDragonBypassMobGriefing || this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && !iblockdata.is(BlockTags.DRAGON_IMMUNE)) { // Purpur
- // CraftBukkit start - Add blocks to list rather than destroying them
- // flag1 = this.level.removeBlock(blockposition, false) || flag1;
- flag1 = true;
-@@ -634,7 +720,7 @@ public class EnderDragon extends Mob implements Enemy {
- boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT);
- short short0 = 500;
-
-- if (this.dragonFight != null && !this.dragonFight.hasPreviouslyKilledDragon()) {
-+ if (this.dragonFight != null && (level.purpurConfig.enderDragonAlwaysDropsFullExp || !this.dragonFight.hasPreviouslyKilledDragon())) {
- short0 = 12000;
- }
-
-@@ -1069,6 +1155,7 @@ public class EnderDragon extends Mob implements Enemy {
-
- @Override
- protected boolean canRide(Entity entity) {
-+ if (this.level.purpurConfig.enderDragonCanRideVehicles) return this.boardingCooldown <= 0; // Purpur
- return false;
- }
-
-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 e81e8f050bd9df34b6a64c741428503b434f03a3..4781bdd3b6c7d6b686f2fe6af530e82861385342 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
-@@ -84,16 +84,31 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob
- return entityliving.getMobType() != MobType.UNDEAD && entityliving.attackable();
- };
- private static final TargetingConditions TARGETING_CONDITIONS = TargetingConditions.forCombat().range(20.0D).selector(WitherBoss.LIVING_ENTITY_SELECTOR);
-+ private int shootCooldown = 0; // Purpur
-+ @Nullable private java.util.UUID summoner; // Purpur
- // Paper start
- private boolean canPortal = false;
-
- public void setCanTravelThroughPortals(boolean canPortal) { this.canPortal = canPortal; }
- // Paper end
-+ private org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD purpurController; // Purpur
-
- 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
-+ 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
- this.setHealth(this.getMaxHealth());
- this.xpReward = 50;
- }
-@@ -108,13 +123,148 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob
- return navigationflying;
- }
-
-+ // Purpur start
-+ @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);
-+ WitherSkull skull = new WitherSkull(level, this, x - headX, y - headY, z - headZ) {
-+ @Override
-+ public boolean canHitEntity(Entity target) {
-+ // do not hit rider
-+ return target != rider && super.canHitEntity(target);
-+ }
-+
-+ @Override
-+ public boolean canSaveToDisk() {
-+ return false;
-+ }
-+ };
-+ skull.setPosRaw(headX, headY, headZ);
-+ level.addFreshEntity(skull);
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.witherMaxHealth);
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.witherTakeDamageFromWater;
-+ }
-+
-+ @Nullable
-+ public java.util.UUID getSummoner() {
-+ return summoner;
-+ }
-+
-+ public void setSummoner(@Nullable java.util.UUID summoner) {
-+ this.summoner = summoner;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.witherAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @Override
- protected void registerGoals() {
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
- 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
- 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));
- }
-@@ -132,6 +282,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob
- public void addAdditionalSaveData(CompoundTag nbt) {
- super.addAdditionalSaveData(nbt);
- nbt.putInt("Invul", this.getInvulnerableTicks());
-+ if (getSummoner() != null) nbt.putUUID("Purpur.Summoner", getSummoner()); // Purpur
- }
-
- @Override
-@@ -141,6 +292,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob
- if (this.hasCustomName()) {
- this.bossEvent.setName(this.getDisplayName());
- }
-+ if (nbt.contains("Purpur.Summoner")) setSummoner(nbt.getUUID("Purpur.Summoner")); // Purpur
-
- }
-
-@@ -256,6 +408,16 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob
-
- @Override
- protected void customServerAiStep() {
-+ // Purpur start
-+ 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
-+
- int i;
-
- if (this.getInvulnerableTicks() > 0) {
-@@ -272,7 +434,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob
- }
- // CraftBukkit end
-
-- if (!this.isSilent()) {
-+ if (!this.isSilent() && level.purpurConfig.witherPlaySpawnSound) {
- // CraftBukkit start - Use relative location for far away sounds
- // this.world.globalLevelEvent(1023, new BlockPosition(this), 0);
- int viewDistance = ((ServerLevel) this.level).getCraftServer().getViewDistance() * 16;
-@@ -296,7 +458,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob
-
- this.setInvulnerableTicks(i);
- if (this.tickCount % 10 == 0) {
-- this.heal(10.0F, EntityRegainHealthEvent.RegainReason.WITHER_SPAWN); // CraftBukkit
-+ this.heal(this.getMaxHealth() / 33, EntityRegainHealthEvent.RegainReason.WITHER_SPAWN); // CraftBukkit // Purpur
- }
-
- } else {
-@@ -356,7 +518,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob
-
- if (this.destroyBlocksTick > 0) {
- --this.destroyBlocksTick;
-- if (this.destroyBlocksTick == 0 && this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
-+ if (this.destroyBlocksTick == 0 && (this.level.purpurConfig.witherBypassMobGriefing || this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // Purpur
- i = Mth.floor(this.getY());
- j = Mth.floor(this.getX());
- int i1 = Mth.floor(this.getZ());
-@@ -389,8 +551,10 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob
- }
- }
-
-- if (this.tickCount % 20 == 0) {
-- this.heal(1.0F, EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit
-+ // Purpur start - customizable heal rate and amount
-+ if (this.tickCount % level.purpurConfig.witherHealthRegenDelay == 0) {
-+ this.heal(level.purpurConfig.witherHealthRegenAmount, EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit
-+ // Purpur end
- }
-
- this.bossEvent.setProgress(this.getHealth() / this.getMaxHealth());
-@@ -576,11 +740,11 @@ public class WitherBoss extends Monster implements PowerableMob, 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
- }
-
- 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
- }
-
- @Override
-@@ -595,6 +759,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob
-
- @Override
- protected boolean canRide(Entity entity) {
-+ if (this.level.purpurConfig.witherCanRideVehicles) return this.boardingCooldown <= 0; // Purpur
- return false;
- }
-
-diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java
-index 3677dd991ae73428984e62e4d6fb757317987887..0545a39af0f21210ff1f5e53f6d712ae24ce43e4 100644
---- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java
-+++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java
-@@ -99,10 +99,12 @@ public class ArmorStand extends LivingEntity {
- private boolean noTickPoseDirty = false;
- private boolean noTickEquipmentDirty = false;
- // Paper end
-+ public boolean canMovementTick = true; // Purpur
-
- public ArmorStand(EntityType extends ArmorStand> type, Level world) {
- super(type, world);
- if (world != null) this.canTick = world.paperConfig().entities.armorStands.tick; // Paper - armour stand ticking
-+ if (world != null) this.canMovementTick = world.purpurConfig.armorstandMovement; // Purpur
- this.handItems = NonNullList.withSize(2, ItemStack.EMPTY);
- this.armorItems = NonNullList.withSize(4, ItemStack.EMPTY);
- this.headPose = ArmorStand.DEFAULT_HEAD_POSE;
-@@ -112,6 +114,7 @@ public class ArmorStand extends LivingEntity {
- this.leftLegPose = ArmorStand.DEFAULT_LEFT_LEG_POSE;
- this.rightLegPose = ArmorStand.DEFAULT_RIGHT_LEG_POSE;
- this.setMaxUpStep(0.0F);
-+ this.setShowArms(world != null && world.purpurConfig.armorstandPlaceWithArms); // Purpur
- }
-
- public ArmorStand(Level world, double x, double y, double z) {
-@@ -609,7 +612,7 @@ public class ArmorStand extends LivingEntity {
- private org.bukkit.event.entity.EntityDeathEvent brokenByPlayer(DamageSource damageSource) { // Paper
- ItemStack itemstack = new ItemStack(Items.ARMOR_STAND);
-
-- if (this.hasCustomName()) {
-+ if (this.level.purpurConfig.persistentDroppableEntityDisplayNames && this.hasCustomName()) { // Purpur
- itemstack.setHoverName(this.getCustomName());
- }
-
-@@ -685,6 +688,7 @@ public class ArmorStand extends LivingEntity {
-
- @Override
- public void tick() {
-+ maxUpStep = level.purpurConfig.armorstandStepHeight;
- // Paper start
- if (!this.canTick) {
- if (this.noTickPoseDirty) {
-@@ -1006,4 +1010,18 @@ public class ArmorStand extends LivingEntity {
- }
- // Paper end
- // Paper end
-+
-+ // Purpur start
-+ @Override
-+ public void updateInWaterStateAndDoWaterCurrentPushing() {
-+ if (this.level.purpurConfig.armorstandWaterMovement &&
-+ (this.level.purpurConfig.armorstandWaterFence || !(level.getBlockState(blockPosition().below()).getBlock() instanceof net.minecraft.world.level.block.FenceBlock)))
-+ super.updateInWaterStateAndDoWaterCurrentPushing();
-+ }
-+
-+ @Override
-+ public void aiStep() {
-+ if (this.canMovementTick && this.canMove) super.aiStep();
-+ }
-+ // Purpur end
- }
-diff --git a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java
-index c58fab79c9425a56cd9ffdfa81e4ea97d5dff941..d47a9de3b29f4df562be55596c363cb39f41b4ec 100644
---- a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java
-+++ b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java
-@@ -272,7 +272,13 @@ public class ItemFrame extends HangingEntity {
- }
-
- if (alwaysDrop) {
-- this.spawnAtLocation(this.getFrameItemStack());
-+ // Purpur start
-+ final ItemStack itemFrame = this.getFrameItemStack();
-+ if (this.level.purpurConfig.persistentDroppableEntityDisplayNames && this.hasCustomName()) {
-+ itemFrame.setHoverName(this.getCustomName());
-+ }
-+ this.spawnAtLocation(itemFrame);
-+ // Purpur end
- }
-
- if (!itemstack.isEmpty()) {
-diff --git a/src/main/java/net/minecraft/world/entity/decoration/Painting.java b/src/main/java/net/minecraft/world/entity/decoration/Painting.java
-index ad0df80d1adb1d945f40e1b5f7732bb36b2ca2ff..90cab3586d3e3e290475fe8d59a69d89d3c24add 100644
---- a/src/main/java/net/minecraft/world/entity/decoration/Painting.java
-+++ b/src/main/java/net/minecraft/world/entity/decoration/Painting.java
-@@ -121,7 +121,7 @@ public class Painting extends HangingEntity implements VariantHolder holder = loadVariant(nbt).orElseGet(Painting::getDefaultVariant);
-+ Holder holder = loadVariant(nbt).orElseGet(() -> (Holder.Reference) getDefaultVariant()); // Purpur - decompile error TODO: still needed?
- this.setVariant(holder);
- this.direction = Direction.from2DDataValue(nbt.getByte("facing"));
- super.readAdditionalSaveData(nbt);
-@@ -159,7 +159,13 @@ public class Painting extends HangingEntity implements VariantHolder type, Level world) {
- super(type, world);
-@@ -377,6 +383,15 @@ public class ItemEntity extends Entity implements TraceableEntity {
- return false;
- } else if (!this.getItem().getItem().canBeHurtBy(source)) {
- return false;
-+ // Purpur start
-+ } else if (
-+ (immuneToCactus && source.is(net.minecraft.world.damagesource.DamageTypes.CACTUS)) ||
-+ (immuneToFire && (source.is(DamageTypeTags.IS_FIRE) && source.is(net.minecraft.world.damagesource.DamageTypes.IN_FIRE)) ||
-+ (immuneToLightning && source.is(net.minecraft.world.damagesource.DamageTypes.LIGHTNING_BOLT)) ||
-+ (immuneToExplosion && source.is(DamageTypeTags.IS_EXPLOSION)))
-+ ) {
-+ return false;
-+ // Purpur end
- } else if (this.level.isClientSide) {
- return true;
- } else {
-@@ -575,6 +590,12 @@ public class ItemEntity extends Entity implements TraceableEntity {
- this.getEntityData().set(ItemEntity.DATA_ITEM, stack);
- this.getEntityData().markDirty(ItemEntity.DATA_ITEM); // CraftBukkit - SPIGOT-4591, must mark dirty
- this.despawnRate = level.paperConfig().entities.spawning.altItemDespawnRate.enabled ? level.paperConfig().entities.spawning.altItemDespawnRate.items.getOrDefault(stack.getItem(), level.spigotConfig.itemDespawnRate) : level.spigotConfig.itemDespawnRate; // Paper
-+ // Purpur start
-+ if (level.purpurConfig.itemImmuneToCactus.contains(stack.getItem())) immuneToCactus = true;
-+ if (level.purpurConfig.itemImmuneToExplosion.contains(stack.getItem())) immuneToExplosion = true;
-+ if (level.purpurConfig.itemImmuneToFire.contains(stack.getItem())) immuneToFire = true;
-+ if (level.purpurConfig.itemImmuneToLightning.contains(stack.getItem())) immuneToLightning = true;
-+ // level end
- }
-
- @Override
-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 f2094c52196b45adfd51d8aebcc4c46b779b0925..95098518422ea51f1b8ac56835769bfc740ee8fa 100644
---- a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java
-@@ -66,16 +66,19 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo
- protected AbstractSkeleton(EntityType extends AbstractSkeleton> type, Level world) {
- super(type, world);
- this.reassessWeaponGoal();
-+ this.setShouldBurnInDay(true); // Purpur
- }
-
- @Override
- protected void registerGoals() {
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
- 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
- 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));
-@@ -99,35 +102,14 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo
- }
-
- // Paper start
-- private boolean shouldBurnInDay = true;
-+ // private boolean shouldBurnInDay = true; // Purpur - moved to LivingEntity - keep methods for ABI compatibility
- public boolean shouldBurnInDay() { return shouldBurnInDay; }
- public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; }
- // Paper end
-
- @Override
- public void aiStep() {
-- boolean flag = shouldBurnInDay && this.isSunBurnTick(); // Paper - Configurable Burning
--
-- if (flag) {
-- ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD);
--
-- if (!itemstack.isEmpty()) {
-- if (itemstack.isDamageableItem()) {
-- itemstack.setDamageValue(itemstack.getDamageValue() + this.random.nextInt(2));
-- if (itemstack.getDamageValue() >= itemstack.getMaxDamage()) {
-- this.broadcastBreakEvent(EquipmentSlot.HEAD);
-- this.setItemSlot(EquipmentSlot.HEAD, ItemStack.EMPTY);
-- }
-- }
--
-- flag = false;
-- }
--
-- if (flag) {
-- this.setSecondsOnFire(8);
-- }
-- }
--
-+ // Purpur start - implemented in LivingEntity
- super.aiStep();
- }
-
-@@ -192,7 +174,6 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo
- } else {
- this.goalSelector.addGoal(4, this.meleeGoal);
- }
--
- }
- }
-
-@@ -205,7 +186,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo
- double d2 = target.getZ() - this.getZ();
- double d3 = Math.sqrt(d0 * d0 + d2 * d2);
-
-- entityarrow.shoot(d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, (float) (14 - this.level.getDifficulty().getId() * 4));
-+ entityarrow.shoot(d0, d1 + d3 * 0.20000000298023224D, d2, 1.6F, this.level.purpurConfig.skeletonBowAccuracyMap.getOrDefault(this.level.getDifficulty().getId(), (float) (14 - this.level.getDifficulty().getId() * 4))); // Purpur
- // CraftBukkit start
- org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(this, this.getMainHandItem(), entityarrow.getPickupItem(), entityarrow, net.minecraft.world.InteractionHand.MAIN_HAND, 0.8F, true); // Paper
- if (event.isCancelled()) {
-@@ -236,7 +217,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo
- this.reassessWeaponGoal();
- // Paper start
- if (nbt.contains("Paper.ShouldBurnInDay")) {
-- this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay");
-+ // this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay"); // Purpur - implemented in LivingEntity
- }
- // Paper end
- }
-@@ -245,7 +226,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo
- @Override
- public void addAdditionalSaveData(CompoundTag nbt) {
- super.addAdditionalSaveData(nbt);
-- nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay);
-+ // nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Purpur - implemented in LivingEntity
- }
- // Paper end
-
-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 5ae34ded698e501dc5cb97b1d7028863e95742a1..2ad81368f731a937303f17ede20f18c978b6479c 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Blaze.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Blaze.java
-@@ -32,26 +32,73 @@ public class Blaze extends Monster {
-
- public Blaze(EntityType extends Blaze> type, Level world) {
- super(type, world);
-- this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F);
-+ this.moveControl = new org.purpurmc.purpur.controller.FlyingWithSpacebarMoveControllerWASD(this, 0.3F); // Purpur
-+ if (isSensitiveToWater()) this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F); // Purpur
- this.setPathfindingMalus(BlockPathTypes.LAVA, 8.0F);
- this.setPathfindingMalus(BlockPathTypes.DANGER_FIRE, 0.0F);
- this.setPathfindingMalus(BlockPathTypes.DAMAGE_FIRE, 0.0F);
- this.xpReward = 10;
- }
-
-+ // Purpur start
-+ @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));
-+ }
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.blazeMaxHealth);
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.blazeAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @Override
- protected void registerGoals() {
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
- this.goalSelector.addGoal(4, new Blaze.BlazeAttackGoal(this));
- this.goalSelector.addGoal(5, new MoveTowardsRestrictionGoal(this, 1.0D));
- this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D, 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
- 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.0D).add(Attributes.MOVEMENT_SPEED, (double)0.23F).add(Attributes.FOLLOW_RANGE, 48.0D);
-+ return Monster.createMonsterAttributes().add(Attributes.ATTACK_DAMAGE, 6.0D).add(Attributes.MOVEMENT_SPEED, (double)0.23F).add(Attributes.FOLLOW_RANGE, 48.0D).add(Attributes.FLYING_SPEED, 0.6D); // Purpur
- }
-
- @Override
-@@ -101,11 +148,19 @@ public class Blaze extends Monster {
-
- @Override
- public boolean isSensitiveToWater() {
-- return true;
-+ return this.level.purpurConfig.blazeTakeDamageFromWater; // Purpur
- }
-
- @Override
- protected void customServerAiStep() {
-+ // Purpur start
-+ if (getRider() != null && this.isControllable()) {
-+ Vec3 mot = getDeltaMovement();
-+ setDeltaMovement(mot.x(), getVerticalMot() > 0 ? 0.07D : -0.07D, mot.z());
-+ return;
-+ }
-+ // Purpur end
-+
- --this.nextHeightOffsetChangeTick;
- if (this.nextHeightOffsetChangeTick <= 0) {
- this.nextHeightOffsetChangeTick = 100;
-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 d980b906d9206560741576fa4153c57212f307a0..d23141c44a11050de6ffd12d95a0c2820c3f71e3 100644
---- a/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/CaveSpider.java
-@@ -28,6 +28,38 @@ public class CaveSpider extends Spider {
- return Spider.createAttributes().add(Attributes.MAX_HEALTH, 12.0D);
- }
-
-+ // Purpur start
-+ @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;
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.caveSpiderMaxHealth);
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.caveSpiderTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.caveSpiderAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @Override
- public boolean doHurtTarget(Entity target) {
- if (super.doHurtTarget(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 29c62525241e2e03686d1bceee740d4f54f33c54..c32eda28be3eb2c6a6933463d496ea7b6510f27e 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Creeper.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Creeper.java
-@@ -59,21 +59,130 @@ public class Creeper extends Monster implements PowerableMob {
- public int maxSwell = 30;
- public int explosionRadius = 3;
- private int droppedSkulls;
-+ // Purpur start
-+ private int spacebarCharge = 0;
-+ private int prevSpacebarCharge = 0;
-+ private int powerToggleDelay = 0;
-+ private boolean exploding = false;
-+ // Purpur end
-
- public Creeper(EntityType extends Creeper> type, Level world) {
- super(type, world);
- }
-
-+ // Purpur start
-+ @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() {
-+ 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();
-+ }
-+
-+ @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
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.creeperMaxHealth);
-+ }
-+
-+ public net.minecraft.world.entity.SpawnGroupData finalizeSpawn(net.minecraft.world.level.ServerLevelAccessor world, net.minecraft.world.DifficultyInstance difficulty, net.minecraft.world.entity.MobSpawnType spawnReason, @Nullable net.minecraft.world.entity.SpawnGroupData entityData, @Nullable CompoundTag entityNbt) {
-+ double chance = world.getLevel().purpurConfig.creeperChargedChance;
-+ if (chance > 0D && random.nextDouble() <= chance) {
-+ setPowered(true);
-+ }
-+ return super.finalizeSpawn(world, difficulty, spawnReason, entityData, entityNbt);
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.creeperTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(DamageSource damagesource) {
-+ if (!exploding && this.level.purpurConfig.creeperExplodeWhenKilled && damagesource.getEntity() instanceof net.minecraft.server.level.ServerPlayer) {
-+ this.explodeCreeper();
-+ }
-+ return super.dropAllDeathLoot(damagesource);
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.creeperAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @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
- 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
- this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, true));
- this.targetSelector.addGoal(2, new HurtByTargetGoal(this, new Class[0]));
- }
-@@ -263,15 +372,17 @@ public class Creeper extends Monster implements PowerableMob {
- }
-
- public void explodeCreeper() {
-+ this.exploding = true; // Purpur
- if (!this.level.isClientSide) {
- float f = this.isPowered() ? 2.0F : 1.0F;
-+ float multiplier = this.level.purpurConfig.creeperHealthRadius ? this.getHealth() / this.getMaxHealth() : 1; // Purpur
-
- // CraftBukkit start
-- ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), this.explosionRadius * f, false);
-+ ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), (this.explosionRadius * f) * multiplier, false); // Purpur
- this.level.getCraftServer().getPluginManager().callEvent(event);
- if (!event.isCancelled()) {
- this.dead = true;
-- this.level.explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), Level.ExplosionInteraction.MOB);
-+ this.level.explode(this, this.getX(), this.getY(), this.getZ(), event.getRadius(), event.getFire(), this.level.getGameRules().getBoolean(net.minecraft.world.level.GameRules.RULE_MOBGRIEFING) && level.purpurConfig.creeperAllowGriefing ? Level.ExplosionInteraction.MOB : Level.ExplosionInteraction.NONE); // Purpur
- this.discard();
- this.spawnLingeringCloud();
- } else {
-@@ -280,7 +391,7 @@ public class Creeper extends Monster implements PowerableMob {
- }
- // CraftBukkit end
- }
--
-+ this.exploding = false; // Purpur
- }
-
- private void spawnLingeringCloud() {
-@@ -322,6 +433,7 @@ public class Creeper extends Monster implements PowerableMob {
- 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
- }
- }
- // Paper end
-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 f00773e05654bdeb5463f448293aac99d2208813..a6980d85455234d4f89ff423e013f3c479bd3fe8 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Drowned.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Drowned.java
-@@ -29,6 +29,7 @@ import net.minecraft.world.entity.ai.goal.MoveToBlockGoal;
- import net.minecraft.world.entity.ai.goal.RandomStrollGoal;
- import net.minecraft.world.entity.ai.goal.RangedAttackGoal;
- import net.minecraft.world.entity.ai.goal.ZombieAttackGoal;
-+import net.minecraft.world.entity.ai.goal.MoveThroughVillageGoal;
- import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal;
- import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal;
- import net.minecraft.world.entity.ai.navigation.GroundPathNavigation;
-@@ -68,6 +69,58 @@ public class Drowned extends Zombie implements RangedAttackMob {
- this.groundNavigation = new GroundPathNavigation(this, world);
- }
-
-+ // Purpur start
-+ @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;
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.drownedMaxHealth);
-+ }
-+
-+ @Override
-+ protected void randomizeReinforcementsChance() {
-+ this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level.purpurConfig.drownedSpawnReinforcements);
-+ }
-+
-+ @Override
-+ public boolean jockeyOnlyBaby() {
-+ return level.purpurConfig.drownedJockeyOnlyBaby;
-+ }
-+
-+ @Override
-+ public double jockeyChance() {
-+ return level.purpurConfig.drownedJockeyChance;
-+ }
-+
-+ @Override
-+ public boolean jockeyTryExistingChickens() {
-+ return level.purpurConfig.drownedJockeyTryExistingChickens;
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.drownedTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.drownedAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @Override
- protected void addBehaviourGoals() {
- this.goalSelector.addGoal(1, new Drowned.DrownedGoToWaterGoal(this, 1.0D));
-@@ -75,10 +128,23 @@ public class Drowned extends Zombie implements RangedAttackMob {
- this.goalSelector.addGoal(2, new Drowned.DrownedAttackGoal(this, 1.0D, false));
- this.goalSelector.addGoal(5, new Drowned.DrownedGoToBeachGoal(this, 1.0D));
- this.goalSelector.addGoal(6, new Drowned.DrownedSwimUpGoal(this, 1.0D, this.level.getSeaLevel()));
-+ if (level.purpurConfig.drownedBreakDoors) this.goalSelector.addGoal(6, new MoveThroughVillageGoal(this, 1.0D, true, 4, this::canBreakDoors));
- this.goalSelector.addGoal(7, new RandomStrollGoal(this, 1.0D));
- this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Drowned.class})).setAlertOthers(ZombifiedPiglin.class));
- this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::okTarget));
-- if (this.level.spigotConfig.zombieAggressiveTowardsVillager) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); // Paper
-+ // Purpur start
-+ if ( level.spigotConfig.zombieAggressiveTowardsVillager ) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal(this, AbstractVillager.class, false) { // Spigot
-+ @Override
-+ public boolean canUse() {
-+ return (level.purpurConfig.zombieAggressiveTowardsVillagerWhenLagging || !level.getServer().server.isLagging()) && super.canUse();
-+ }
-+
-+ @Override
-+ public boolean canContinueToUse() {
-+ return (level.purpurConfig.zombieAggressiveTowardsVillagerWhenLagging || !level.getServer().server.isLagging()) && super.canContinueToUse();
-+ }
-+ });
-+ // Purpur end
- this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, IronGolem.class, true));
- this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Axolotl.class, true, false));
- this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR));
-@@ -112,7 +178,7 @@ public class Drowned extends Zombie implements RangedAttackMob {
-
- @Override
- public boolean supportsBreakDoorGoal() {
-- return false;
-+ return level.purpurConfig.drownedBreakDoors ? true : false;
- }
-
- @Override
-@@ -259,8 +325,7 @@ public class Drowned extends Zombie implements RangedAttackMob {
- this.searchingForLand = targetingUnderwater;
- }
-
-- private static class DrownedMoveControl extends MoveControl {
--
-+ private static class DrownedMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur
- private final Drowned drowned;
-
- public DrownedMoveControl(Drowned drowned) {
-@@ -269,7 +334,7 @@ public class Drowned extends Zombie implements RangedAttackMob {
- }
-
- @Override
-- public void tick() {
-+ public void vanillaTick() { // Purpur
- LivingEntity entityliving = this.drowned.getTarget();
-
- if (this.drowned.wantsToSwim() && this.drowned.isInWater()) {
-@@ -292,7 +357,7 @@ public class Drowned extends Zombie implements RangedAttackMob {
-
- this.drowned.setYRot(this.rotlerp(this.drowned.getYRot(), f, 90.0F));
- this.drowned.yBodyRot = this.drowned.getYRot();
-- float f1 = (float) (this.speedModifier * this.drowned.getAttributeValue(Attributes.MOVEMENT_SPEED));
-+ float f1 = (float) (this.getSpeedModifier() * this.drowned.getAttributeValue(Attributes.MOVEMENT_SPEED)); // Purpur
- float f2 = Mth.lerp(0.125F, this.drowned.getSpeed(), f1);
-
- this.drowned.setSpeed(f2);
-@@ -302,7 +367,7 @@ public class Drowned extends Zombie implements RangedAttackMob {
- this.drowned.setDeltaMovement(this.drowned.getDeltaMovement().add(0.0D, -0.008D, 0.0D));
- }
-
-- super.tick();
-+ super.vanillaTick(); // Purpur
- }
-
- }
-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 d02286d553c600fe7e75f48e278e380d21c5b868..916cf5137808003058a787210fc3343d75caf3d9 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,33 @@ public class ElderGuardian extends Guardian {
-
- }
-
-+ // Purpur start
-+ @Override
-+ public boolean isRidable() {
-+ return level.purpurConfig.elderGuardianRidable;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level.purpurConfig.elderGuardianControllable;
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.elderGuardianMaxHealth);
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.elderGuardianTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.elderGuardianAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- 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 079bd9d0d3c73da61297723aea8e79edf531004b..36ac793570d2f3202e74fde106efab17158b3081 100644
---- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java
-@@ -89,12 +89,40 @@ public class EnderMan extends Monster implements NeutralMob {
- public EnderMan(EntityType extends EnderMan> type, Level world) {
- super(type, world);
- this.setMaxUpStep(1.0F);
-- this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F);
-+ if (isSensitiveToWater()) this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F); // Purpur
- }
-
-+ // Purpur start
-+ @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;
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.endermanMaxHealth);
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.endermanAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @Override
- protected void registerGoals() {
- this.goalSelector.addGoal(0, new FloatGoal(this));
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
- 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));
-@@ -102,9 +130,10 @@ 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
- 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));
-+ this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Endermite.class, 10, true, false, (entityliving) -> entityliving.level.purpurConfig.endermanAggroEndermites && entityliving instanceof Endermite endermite && (!entityliving.level.purpurConfig.endermanAggroEndermitesOnlyIfPlayerSpawned || endermite.isPlayerSpawned()))); // Purpur
- this.targetSelector.addGoal(4, new ResetUniversalAngerTargetGoal<>(this, false));
- }
-
-@@ -241,7 +270,7 @@ public class EnderMan extends Monster implements NeutralMob {
- // Paper end
- ItemStack itemstack = (ItemStack) player.getInventory().armor.get(3);
-
-- if (itemstack.is(Blocks.CARVED_PUMPKIN.asItem())) {
-+ if (this.level.purpurConfig.endermanDisableStareAggro || itemstack.is(Blocks.CARVED_PUMPKIN.asItem()) || (this.level.purpurConfig.endermanIgnorePlayerDragonHead && itemstack.is(net.minecraft.world.item.Items.DRAGON_HEAD))) { // Purpur
- return false;
- } else {
- Vec3 vec3d = player.getViewVector(1.0F).normalize();
-@@ -278,12 +307,12 @@ public class EnderMan extends Monster implements NeutralMob {
-
- @Override
- public boolean isSensitiveToWater() {
-- return true;
-+ return this.level.purpurConfig.endermanTakeDamageFromWater; // Purpur
- }
-
- @Override
- protected void customServerAiStep() {
-- if (this.level.isDay() && this.tickCount >= this.targetChangeTime + 600) {
-+ if ((getRider() == null || !this.isControllable()) && this.level.isDay() && this.tickCount >= this.targetChangeTime + 600) { // Purpur - no random teleporting
- float f = this.getLightLevelDependentMagicValue();
-
- if (f > 0.5F && this.level.canSeeSky(this.blockPosition()) && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && this.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.RUNAWAY)) { // Paper
-@@ -404,6 +433,8 @@ public class EnderMan extends Monster implements NeutralMob {
- public boolean hurt(DamageSource source, float amount) {
- if (this.isInvulnerableTo(source)) {
- return false;
-+ } else if (getRider() != null && this.isControllable()) { return super.hurt(source, amount); // Purpur - no teleporting on damage
-+ } else if (org.purpurmc.purpur.PurpurConfig.endermanShortHeight && source.is(net.minecraft.world.damagesource.DamageTypes.IN_WALL)) { return false; // Purpur - no suffocation damage if short height
- } else {
- boolean flag = source.getDirectEntity() instanceof ThrownPotion;
- boolean flag1;
-@@ -418,6 +449,7 @@ public class EnderMan extends Monster implements NeutralMob {
- } else {
- flag1 = flag && this.hurtWithCleanWater(source, (ThrownPotion) source.getDirectEntity(), amount);
-
-+ if (!flag1 && this.level.purpurConfig.endermanIgnoreProjectiles) return super.hurt(source, amount); // Purpur
- if (this.tryEscape(com.destroystokyo.paper.event.entity.EndermanEscapeEvent.Reason.INDIRECT)) { // Paper start
- for (int i = 0; i < 64; ++i) {
- if (this.teleport()) {
-@@ -464,7 +496,7 @@ public class EnderMan extends Monster implements NeutralMob {
-
- @Override
- public boolean requiresCustomPersistence() {
-- return super.requiresCustomPersistence() || this.getCarriedBlock() != null;
-+ return super.requiresCustomPersistence() || (!this.level.purpurConfig.endermanDespawnEvenWithBlock && this.getCarriedBlock() != null); // Purpur
- }
-
- private static class EndermanFreezeWhenLookedAt extends Goal {
-@@ -511,7 +543,16 @@ public class EnderMan extends Monster implements NeutralMob {
-
- @Override
- public boolean canUse() {
-- return this.enderman.getCarriedBlock() == null ? false : (!this.enderman.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? false : this.enderman.getRandom().nextInt(reducedTickDelay(2000)) == 0);
-+ if (!enderman.level.purpurConfig.endermanAllowGriefing) return false; // Purpur
-+ // Purpur start
-+ if (this.enderman.getCarriedBlock() == null) {
-+ return false;
-+ }
-+ if (!this.enderman.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && !this.enderman.level.purpurConfig.endermanBypassMobGriefing) {
-+ return false;
-+ }
-+ return this.enderman.getRandom().nextInt(reducedTickDelay(2000)) == 0;
-+ // Purpur end
- }
-
- @Override
-@@ -558,7 +599,16 @@ public class EnderMan extends Monster implements NeutralMob {
-
- @Override
- public boolean canUse() {
-- return this.enderman.getCarriedBlock() != null ? false : (!this.enderman.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? false : this.enderman.getRandom().nextInt(reducedTickDelay(20)) == 0);
-+ if (!enderman.level.purpurConfig.endermanAllowGriefing) return false; // Purpur
-+ // Purpur start
-+ if (this.enderman.getCarriedBlock() != null) {
-+ return false;
-+ }
-+ if (!this.enderman.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && !this.enderman.level.purpurConfig.endermanBypassMobGriefing) {
-+ return false;
-+ }
-+ return this.enderman.getRandom().nextInt(reducedTickDelay(20)) == 0;
-+ // Purpur end
- }
-
- @Override
-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 e8c3972b889fd6b348a5b0d18444d28faa813879..e6ecc47828fea09c80ed3a4c39f0d85f4d820571 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Endermite.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Endermite.java
-@@ -31,20 +31,63 @@ import net.minecraft.world.level.block.state.BlockState;
- public class Endermite extends Monster {
- private static final int MAX_LIFE = 2400;
- public int life;
-+ private boolean isPlayerSpawned; // Purpur
-
- public Endermite(EntityType extends Endermite> type, Level world) {
- super(type, world);
- this.xpReward = 3;
- }
-
-+ // Purpur start
-+ @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;
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.endermiteMaxHealth);
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.endermiteTakeDamageFromWater;
-+ }
-+
-+ public boolean isPlayerSpawned() {
-+ return this.isPlayerSpawned;
-+ }
-+
-+ public void setPlayerSpawned(boolean playerSpawned) {
-+ this.isPlayerSpawned = playerSpawned;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.endermiteAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @Override
- protected void registerGoals() {
- this.goalSelector.addGoal(1, new FloatGoal(this));
-+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
- 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
- this.targetSelector.addGoal(1, (new HurtByTargetGoal(this)).setAlertOthers());
- this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
- }
-@@ -87,12 +130,14 @@ public class Endermite extends Monster {
- public void readAdditionalSaveData(CompoundTag nbt) {
- super.readAdditionalSaveData(nbt);
- this.life = nbt.getInt("Lifetime");
-+ this.isPlayerSpawned = nbt.getBoolean("PlayerSpawned"); // Purpur
- }
-
- @Override
- public void addAdditionalSaveData(CompoundTag nbt) {
- super.addAdditionalSaveData(nbt);
- nbt.putInt("Lifetime", this.life);
-+ nbt.putBoolean("PlayerSpawned", this.isPlayerSpawned); // Purpur
- }
-
- @Override
-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 1935f1eb28724d8f03a9612a9b4ddefbbc557157..892e0c0306a21ea638649c1324b8115f24c01bd2 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Evoker.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Evoker.java
-@@ -48,10 +48,43 @@ public class Evoker extends SpellcasterIllager {
- this.xpReward = 10;
- }
-
-+ // Purpur start
-+ @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;
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.evokerMaxHealth);
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.evokerTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.evokerAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @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
- 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(4, new Evoker.EvokerSummonSpellGoal());
-@@ -60,6 +93,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
- 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));
-@@ -317,7 +351,7 @@ public class Evoker extends SpellcasterIllager {
- return false;
- } else if (Evoker.this.tickCount < this.nextAttackTickCount) {
- return false;
-- } else if (!Evoker.this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
-+ } else if (!Evoker.this.level.purpurConfig.evokerBypassMobGriefing && !Evoker.this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur
- return false;
- } else {
- List list = Evoker.this.level.getNearbyEntities(Sheep.class, this.wololoTargeting, Evoker.this, Evoker.this.getBoundingBox().inflate(16.0D, 4.0D, 16.0D));
-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 bb2cb17e4e5ce142eeec18951c8948e3d6b3209c..77dcae6ecd87fade2b529386ba1360836363593a 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,62 @@ public class Ghast extends FlyingMob implements Enemy {
- this.moveControl = new Ghast.GhastMoveControl(this);
- }
-
-+ // Purpur start
-+ @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));
-+ }
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.ghastMaxHealth);
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.ghastTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.ghastAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @Override
- protected void registerGoals() {
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur
- 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
- this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (entityliving) -> {
- return Math.abs(entityliving.getY() - this.getY()) <= 4.0D;
- }));
-@@ -103,7 +154,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
- }
-
- @Override
-@@ -160,7 +211,7 @@ public class Ghast extends FlyingMob implements Enemy {
- return 2.6F;
- }
-
-- private static class GhastMoveControl extends MoveControl {
-+ private static class GhastMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur
-
- private final Ghast ghast;
- private int floatDuration;
-@@ -171,7 +222,7 @@ public class Ghast extends FlyingMob implements Enemy {
- }
-
- @Override
-- public void tick() {
-+ public void vanillaTick() { // Purpur
- 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 41004c28edb748e12c4f868aa07b4672891197c1..4e5b9f772ba587b4e108add3758dffa665c1c3f3 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Giant.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Giant.java
-@@ -1,18 +1,123 @@
- package net.minecraft.world.entity.monster;
-
- import net.minecraft.core.BlockPos;
-+import net.minecraft.nbt.CompoundTag;
-+import net.minecraft.world.Difficulty;
-+import net.minecraft.world.DifficultyInstance;
- import net.minecraft.world.entity.EntityDimensions;
- import net.minecraft.world.entity.EntityType;
-+import net.minecraft.world.entity.EquipmentSlot;
-+import net.minecraft.world.entity.MobSpawnType;
- import net.minecraft.world.entity.Pose;
-+import net.minecraft.world.entity.SpawnGroupData;
- import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
- import net.minecraft.world.entity.ai.attributes.Attributes;
-+import net.minecraft.world.entity.ai.goal.FloatGoal;
-+import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
-+import net.minecraft.world.entity.ai.goal.MeleeAttackGoal;
-+import net.minecraft.world.entity.ai.goal.MoveTowardsRestrictionGoal;
-+import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
-+import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal;
-+import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal;
-+import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal;
-+import net.minecraft.world.entity.animal.IronGolem;
-+import net.minecraft.world.entity.animal.Turtle;
-+import net.minecraft.world.entity.npc.Villager;
-+import net.minecraft.world.entity.player.Player;
-+import net.minecraft.world.item.ItemStack;
-+import net.minecraft.world.item.Items;
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.LevelReader;
-+import net.minecraft.world.level.ServerLevelAccessor;
-+
-+import javax.annotation.Nullable;
-
- public class Giant extends Monster {
- public Giant(EntityType extends Giant> type, Level world) {
- super(type, world);
-+ this.safeFallDistance = 10.0F; // Purpur
-+ }
-+
-+ // Purpur start
-+ @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 initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.giantMaxHealth);
-+ this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(this.level.purpurConfig.giantMovementSpeed);
-+ this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(this.level.purpurConfig.giantAttackDamage);
-+ }
-+
-+ @Override
-+ protected void registerGoals() {
-+ if (level.purpurConfig.giantHaveAI) {
-+ this.goalSelector.addGoal(0, new FloatGoal(this));
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this));
-+ this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D));
-+ this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 16.0F));
-+ this.goalSelector.addGoal(8, new RandomLookAroundGoal(this));
-+ this.goalSelector.addGoal(5, new MoveTowardsRestrictionGoal(this, 1.0D));
-+ if (level.purpurConfig.giantHaveHostileAI) {
-+ this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.0D, false));
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this));
-+ this.targetSelector.addGoal(1, new HurtByTargetGoal(this).setAlertOthers(ZombifiedPiglin.class));
-+ this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true));
-+ this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Villager.class, false));
-+ this.targetSelector.addGoal(4, new NearestAttackableTargetGoal<>(this, IronGolem.class, true));
-+ this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, true));
-+ }
-+ }
-+ }
-+
-+ @Override
-+ public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, MobSpawnType spawnReason, @Nullable SpawnGroupData entityData, @Nullable CompoundTag entityNbt) {
-+ SpawnGroupData groupData = super.finalizeSpawn(world, difficulty, spawnReason, entityData, entityNbt);
-+ if (groupData == null) {
-+ populateDefaultEquipmentSlots(this.random, difficulty);
-+ populateDefaultEquipmentEnchantments(this.random, difficulty);
-+ }
-+ return groupData;
-+ }
-+
-+ @Override
-+ protected void populateDefaultEquipmentSlots(net.minecraft.util.RandomSource random, DifficultyInstance difficulty) {
-+ super.populateDefaultEquipmentSlots(this.random, difficulty);
-+ // TODO make configurable
-+ if (random.nextFloat() < (level.getDifficulty() == Difficulty.HARD ? 0.1F : 0.05F)) {
-+ this.setItemSlot(EquipmentSlot.MAINHAND, new ItemStack(Items.IRON_SWORD));
-+ }
-+ }
-+
-+ @Override
-+ public float getJumpPower() {
-+ // make giants jump as high as everything else relative to their size
-+ // 1.0 makes bottom of feet about as high as their waist when they jump
-+ return level.purpurConfig.giantJumpHeight;
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.giantTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.giantAlwaysDropExp;
- }
-+ // Purpur end
-
- @Override
- protected float getStandingEyeHeight(Pose pose, EntityDimensions dimensions) {
-@@ -25,6 +130,6 @@ public class Giant extends Monster {
-
- @Override
- public float getWalkTargetValue(BlockPos pos, LevelReader world) {
-- return world.getPathfindingCostFromLightLevels(pos);
-+ return super.getWalkTargetValue(pos, world); // Purpur - fix light requirements for natural spawns
- }
- }
-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 cf7e9c1db229f9e2cc05ce3046540db1d4fc4ec4..f10304b38e904528907cb36c342acf9d49935edd 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Guardian.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Guardian.java
-@@ -69,15 +69,51 @@ public class Guardian extends Monster {
- this.xpReward = 10;
- this.setPathfindingMalus(BlockPathTypes.WATER, 0.0F);
- this.moveControl = new Guardian.GuardianMoveControl(this);
-+ // Purpur start
-+ 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
- this.clientSideTailAnimation = this.random.nextFloat();
- this.clientSideTailAnimationO = this.clientSideTailAnimation;
- }
-
-+ // Purpur start
-+ @Override
-+ public boolean isRidable() {
-+ return level.purpurConfig.guardianRidable;
-+ }
-+
-+ @Override
-+ public boolean isControllable() {
-+ return level.purpurConfig.guardianControllable;
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.guardianMaxHealth);
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.guardianTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.guardianAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @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
- this.goalSelector.addGoal(4, this.guardianAttackGoal = new Guardian.GuardianAttackGoal(this)); // CraftBukkit - assign field
- this.goalSelector.addGoal(5, pathfindergoalmovetowardsrestriction);
- this.goalSelector.addGoal(7, this.randomStrollGoal);
-@@ -86,6 +122,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
- this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, LivingEntity.class, 10, true, false, new Guardian.GuardianAttackSelector(this)));
- }
-
-@@ -351,7 +388,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
- this.move(MoverType.SELF, this.getDeltaMovement());
- this.setDeltaMovement(this.getDeltaMovement().scale(0.9D));
- if (!this.isMoving() && this.getTarget() == null) {
-@@ -363,7 +400,7 @@ public class Guardian extends Monster {
-
- }
-
-- private static class GuardianMoveControl extends MoveControl {
-+ private static class GuardianMoveControl extends org.purpurmc.purpur.controller.WaterMoveControllerWASD { // Purpur
-
- private final Guardian guardian;
-
-@@ -372,8 +409,17 @@ public class Guardian extends Monster {
- this.guardian = guardian;
- }
-
-+ // Purpur start
- @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
-+
-+ @Override
-+ public void vanillaTick() { // Purpur
- 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();
-@@ -384,7 +430,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
- 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 4996347c6dde85a2dc9aa37fdf495160093fac64..a7b690c0730d0b10133f24d7ce2d9f6a0e4a7c04 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Husk.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Husk.java
-@@ -20,15 +20,68 @@ public class Husk extends Zombie {
-
- public Husk(EntityType extends Husk> type, Level world) {
- super(type, world);
-+ this.setShouldBurnInDay(false); // Purpur
- }
-
-+ // Purpur start
-+ @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;
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.huskMaxHealth);
-+ }
-+
-+ @Override
-+ protected void randomizeReinforcementsChance() {
-+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level.purpurConfig.huskSpawnReinforcements);
-+ }
-+
-+ @Override
-+ public boolean jockeyOnlyBaby() {
-+ return level.purpurConfig.huskJockeyOnlyBaby;
-+ }
-+
-+ @Override
-+ public double jockeyChance() {
-+ return level.purpurConfig.huskJockeyChance;
-+ }
-+
-+ @Override
-+ public boolean jockeyTryExistingChickens() {
-+ return level.purpurConfig.huskJockeyTryExistingChickens;
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.huskTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.huskAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- public static boolean checkHuskSpawnRules(EntityType type, ServerLevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) {
- return checkMonsterSpawnRules(type, world, spawnReason, pos, random) && (spawnReason == MobSpawnType.SPAWNER || world.canSeeSky(pos));
- }
-
- @Override
- public boolean isSunSensitive() {
-- return false;
-+ return this.shouldBurnInDay; // Purpur - moved to LivingEntity - keep methods for ABI compatibility
- }
-
- @Override
-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 10573602c9bc73713cbd6989762d3dbb6f6fcf8c..63577d941dbd21cf93bc6f88bb50922618b6b5d5 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,45 @@ public class Illusioner extends SpellcasterIllager implements RangedAttackMob {
-
- }
-
-+ // Purpur start
-+ @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;
-+ }
-+
-+ @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);
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.illusionerTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.illusionerAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @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
- this.goalSelector.addGoal(1, new SpellcasterIllager.SpellcasterCastingSpellGoal());
- this.goalSelector.addGoal(4, new Illusioner.IllusionerMirrorSpellGoal());
- this.goalSelector.addGoal(5, new Illusioner.IllusionerBlindnessSpellGoal());
-@@ -70,6 +105,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
- 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 f23d8796aec3e02a3bb23f338903f39b6ef9dcf1..11af95207f5aff52427dc216fb9929b0f536f411 100644
---- a/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/MagmaCube.java
-@@ -25,6 +25,58 @@ public class MagmaCube extends Slime {
- super(type, world);
- }
-
-+ // Purpur start
-+ @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
-+ }
-+
-+ @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;
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.magmaCubeTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.magmaCubeAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- public static AttributeSupplier.Builder createAttributes() {
- return Monster.createMonsterAttributes().add(Attributes.MOVEMENT_SPEED, (double)0.2F);
- }
-@@ -70,10 +122,11 @@ public class MagmaCube extends Slime {
- }
-
- @Override
-- protected void jumpFromGround() {
-+ public void jumpFromGround() { // Purpur - protected -> public
- Vec3 vec3 = this.getDeltaMovement();
- this.setDeltaMovement(vec3.x, (double)(this.getJumpPower() + (float)this.getSize() * 0.1F), vec3.z);
- this.hasImpulse = true;
-+ this.actualJump = false; // Purpur
- }
-
- @Override
-diff --git a/src/main/java/net/minecraft/world/entity/monster/Monster.java b/src/main/java/net/minecraft/world/entity/monster/Monster.java
-index 55c245d0dfa369dc6de2197ae37335fba4fae4ae..c9b40515f4c2ff1eedfc9510930c3baebc078ebd 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Monster.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Monster.java
-@@ -89,6 +89,14 @@ public abstract class Monster extends PathfinderMob implements Enemy {
- }
-
- public static boolean isDarkEnoughToSpawn(ServerLevelAccessor world, BlockPos pos, RandomSource random) {
-+ // Purpur start
-+ if (!world.getMinecraftWorld().purpurConfig.mobsSpawnOnPackedIce || !world.getMinecraftWorld().purpurConfig.mobsSpawnOnBlueIce) {
-+ net.minecraft.world.level.block.state.BlockState spawnBlock = world.getBlockState(pos.below());
-+ if ((!world.getMinecraftWorld().purpurConfig.mobsSpawnOnPackedIce && spawnBlock.is(net.minecraft.world.level.block.Blocks.PACKED_ICE)) || (!world.getMinecraftWorld().purpurConfig.mobsSpawnOnBlueIce && spawnBlock.is(net.minecraft.world.level.block.Blocks.BLUE_ICE))) {
-+ return false;
-+ }
-+ }
-+ // Purpur end
- if (world.getBrightness(LightLayer.SKY, pos) > random.nextInt(32)) {
- return false;
- } else {
-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 97fb1d2110a51498f6419841081b500b3f190370..2c00a9fdd3a6ea16ee765339857cf58521c85797 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Phantom.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Phantom.java
-@@ -49,6 +49,8 @@ public class Phantom extends FlyingMob implements Enemy {
- Vec3 moveTargetPoint;
- public BlockPos anchorPoint;
- Phantom.AttackPhase attackPhase;
-+ Vec3 crystalPosition; // Purpur
-+ private static final net.minecraft.world.item.crafting.Ingredient TORCH = net.minecraft.world.item.crafting.Ingredient.of(net.minecraft.world.item.Items.TORCH, net.minecraft.world.item.Items.SOUL_TORCH); // Purpur
-
- public Phantom(EntityType extends Phantom> type, Level world) {
- super(type, world);
-@@ -58,8 +60,110 @@ public class Phantom extends FlyingMob implements Enemy {
- this.xpReward = 5;
- this.moveControl = new Phantom.PhantomMoveControl(this);
- this.lookControl = new Phantom.PhantomLookControl(this);
-+ this.setShouldBurnInDay(true); // Purpur
- }
-
-+ // Purpur start
-+ @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;
-+ }
-+
-+ 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;
-+ }
-+
-+ @Override
-+ protected net.minecraft.world.level.storage.loot.LootContext.Builder createLootContext(boolean causedByPlayer, DamageSource source) {
-+ boolean dropped = false;
-+ if (lastHurtByPlayer == null && source.getEntity() instanceof net.minecraft.world.entity.boss.enderdragon.EndCrystal) {
-+ if (random.nextInt(5) < 1) {
-+ dropped = spawnAtLocation(new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.PHANTOM_MEMBRANE)) != null;
-+ }
-+ }
-+ if (!dropped) {
-+ return super.createLootContext(causedByPlayer, source);
-+ }
-+ return new net.minecraft.world.level.storage.loot.LootContext.Builder((net.minecraft.server.level.ServerLevel) level);
-+ }
-+
-+ public boolean isCirclingCrystal() {
-+ return crystalPosition != null;
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.phantomTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.phantomAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @Override
- public boolean isFlapping() {
- return (this.getUniqueFlapTickOffset() + this.tickCount) % Phantom.TICKS_PER_FLAP == 0;
-@@ -72,9 +176,17 @@ public class Phantom extends FlyingMob implements Enemy {
-
- @Override
- protected void registerGoals() {
-- this.goalSelector.addGoal(1, new Phantom.PhantomAttackStrategyGoal());
-- this.goalSelector.addGoal(2, new Phantom.PhantomSweepAttackGoal());
-- this.goalSelector.addGoal(3, new Phantom.PhantomCircleAroundAnchorGoal());
-+ // Purpur start
-+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this));
-+ if (level.purpurConfig.phantomOrbitCrystalRadius > 0) {
-+ this.goalSelector.addGoal(1, new FindCrystalGoal(this));
-+ this.goalSelector.addGoal(2, new OrbitCrystalGoal(this));
-+ }
-+ this.goalSelector.addGoal(3, new Phantom.PhantomAttackStrategyGoal());
-+ this.goalSelector.addGoal(4, new Phantom.PhantomSweepAttackGoal());
-+ this.goalSelector.addGoal(5, new Phantom.PhantomCircleAroundAnchorGoal());
-+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this));
-+ // Purpur end
- this.targetSelector.addGoal(1, new Phantom.PhantomAttackPlayerTargetGoal());
- }
-
-@@ -90,7 +202,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
-+ 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
- }
-
- public int getPhantomSize() {
-@@ -140,14 +255,12 @@ public class Phantom extends FlyingMob implements Enemy {
- this.level.addParticle(ParticleTypes.MYCELIUM, this.getX() - (double) f2, this.getY() + (double) f4, this.getZ() - (double) f3, 0.0D, 0.0D, 0.0D);
- }
-
-+ if (level.purpurConfig.phantomFlamesOnSwoop && attackPhase == AttackPhase.SWOOP) shoot(); // Purpur
- }
-
- @Override
- public void aiStep() {
-- if (this.isAlive() && shouldBurnInDay && this.isSunBurnTick()) { // Paper - Configurable Burning
-- this.setSecondsOnFire(8);
-- }
--
-+ // Purpur - moved down to shouldBurnInDay()
- super.aiStep();
- }
-
-@@ -159,7 +272,11 @@ public class Phantom extends FlyingMob implements Enemy {
- @Override
- public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, MobSpawnType spawnReason, @Nullable SpawnGroupData entityData, @Nullable CompoundTag entityNbt) {
- this.anchorPoint = this.blockPosition().above(5);
-- this.setPhantomSize(0);
-+ // Purpur start
-+ int min = world.getLevel().purpurConfig.phantomMinSize;
-+ int max = world.getLevel().purpurConfig.phantomMaxSize;
-+ this.setPhantomSize(min == max ? min : world.getRandom().nextInt(max + 1 - min) + min);
-+ // Purpur end
- return super.finalizeSpawn(world, difficulty, spawnReason, entityData, entityNbt);
- }
-
-@@ -175,7 +292,7 @@ public class Phantom extends FlyingMob implements Enemy {
- if (nbt.hasUUID("Paper.SpawningEntity")) {
- this.spawningEntity = nbt.getUUID("Paper.SpawningEntity");
- }
-- if (nbt.contains("Paper.ShouldBurnInDay")) {
-+ if (false && nbt.contains("Paper.ShouldBurnInDay")) { // Purpur - implemented in LivingEntity
- this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay");
- }
- // Paper end
-@@ -192,7 +309,7 @@ public class Phantom extends FlyingMob implements Enemy {
- if (this.spawningEntity != null) {
- nbt.putUUID("Paper.SpawningEntity", this.spawningEntity);
- }
-- nbt.putBoolean("Paper.ShouldBurnInDay", shouldBurnInDay);
-+ // nbt.putBoolean("Paper.ShouldBurnInDay", shouldBurnInDay); // Purpur - implemented in LivingEntity
- // Paper end
- }
-
-@@ -258,8 +375,14 @@ public class Phantom extends FlyingMob implements Enemy {
- }
- public void setSpawningEntity(java.util.UUID entity) { this.spawningEntity = entity; }
-
-- private boolean shouldBurnInDay = true;
-- public boolean shouldBurnInDay() { return shouldBurnInDay; }
-+ // private boolean shouldBurnInDay = true; // Purpur - moved to LivingEntity - keep methods for ABI compatibility
-+ // Purpur start
-+ public boolean shouldBurnInDay() {
-+ boolean burnFromDaylight = this.shouldBurnInDay && this.level.purpurConfig.phantomBurnInDaylight;
-+ boolean burnFromLightSource = this.level.purpurConfig.phantomBurnInLight > 0 && this.level.getMaxLocalRawBrightness(blockPosition()) >= this.level.purpurConfig.phantomBurnInLight;
-+ return burnFromDaylight || burnFromLightSource;
-+ }
-+ // Purpur End
- public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; }
- // Paper end
- private static enum AttackPhase {
-@@ -269,7 +392,125 @@ public class Phantom extends FlyingMob implements Enemy {
- private AttackPhase() {}
- }
-
-- private class PhantomMoveControl extends MoveControl {
-+ // Purpur start
-+ class FindCrystalGoal extends Goal {
-+ private final Phantom phantom;
-+ private net.minecraft.world.entity.boss.enderdragon.EndCrystal crystal;
-+ private Comparator comparator;
-+
-+ FindCrystalGoal(Phantom phantom) {
-+ this.phantom = phantom;
-+ this.comparator = Comparator.comparingDouble(phantom::distanceToSqr);
-+ this.setFlags(EnumSet.of(Flag.LOOK));
-+ }
-+
-+ @Override
-+ public boolean canUse() {
-+ double range = maxTargetRange();
-+ List crystals = level.getEntitiesOfClass(net.minecraft.world.entity.boss.enderdragon.EndCrystal.class, phantom.getBoundingBox().inflate(range));
-+ if (crystals.isEmpty()) {
-+ return false;
-+ }
-+ crystals.sort(comparator);
-+ crystal = crystals.get(0);
-+ if (phantom.distanceToSqr(crystal) > range * range) {
-+ crystal = null;
-+ return false;
-+ }
-+ return true;
-+ }
-+
-+ @Override
-+ public boolean canContinueToUse() {
-+ if (crystal == null || !crystal.isAlive()) {
-+ return false;
-+ }
-+ double range = maxTargetRange();
-+ return phantom.distanceToSqr(crystal) <= (range * range) * 2;
-+ }
-+
-+ @Override
-+ public void start() {
-+ phantom.crystalPosition = new Vec3(crystal.getX(), crystal.getY() + (phantom.random.nextInt(10) + 10), crystal.getZ());
-+ }
-+
-+ @Override
-+ public void stop() {
-+ crystal = null;
-+ phantom.crystalPosition = null;
-+ super.stop();
-+ }
-+
-+ private double maxTargetRange() {
-+ return phantom.level.purpurConfig.phantomOrbitCrystalRadius;
-+ }
-+ }
-+
-+ class OrbitCrystalGoal extends Goal {
-+ private final Phantom phantom;
-+ private float offset;
-+ private float radius;
-+ private float verticalChange;
-+ private float direction;
-+
-+ OrbitCrystalGoal(Phantom phantom) {
-+ this.phantom = phantom;
-+ this.setFlags(EnumSet.of(Flag.MOVE));
-+ }
-+
-+ @Override
-+ public boolean canUse() {
-+ return phantom.isCirclingCrystal();
-+ }
-+
-+ @Override
-+ public void start() {
-+ this.radius = 5.0F + phantom.random.nextFloat() * 10.0F;
-+ this.verticalChange = -4.0F + phantom.random.nextFloat() * 9.0F;
-+ this.direction = phantom.random.nextBoolean() ? 1.0F : -1.0F;
-+ updateOffset();
-+ }
-+
-+ @Override
-+ public void tick() {
-+ if (phantom.random.nextInt(350) == 0) {
-+ this.verticalChange = -4.0F + phantom.random.nextFloat() * 9.0F;
-+ }
-+ if (phantom.random.nextInt(250) == 0) {
-+ ++this.radius;
-+ if (this.radius > 15.0F) {
-+ this.radius = 5.0F;
-+ this.direction = -this.direction;
-+ }
-+ }
-+ if (phantom.random.nextInt(450) == 0) {
-+ this.offset = phantom.random.nextFloat() * 2.0F * 3.1415927F;
-+ updateOffset();
-+ }
-+ if (phantom.moveTargetPoint.distanceToSqr(phantom.getX(), phantom.getY(), phantom.getZ()) < 4.0D) {
-+ updateOffset();
-+ }
-+ if (phantom.moveTargetPoint.y < phantom.getY() && !phantom.level.isEmptyBlock(new BlockPos(phantom).below(1))) {
-+ this.verticalChange = Math.max(1.0F, this.verticalChange);
-+ updateOffset();
-+ }
-+ if (phantom.moveTargetPoint.y > phantom.getY() && !phantom.level.isEmptyBlock(new BlockPos(phantom).above(1))) {
-+ this.verticalChange = Math.min(-1.0F, this.verticalChange);
-+ updateOffset();
-+ }
-+ }
-+
-+ private void updateOffset() {
-+ this.offset += this.direction * 15.0F * 0.017453292F;
-+ phantom.moveTargetPoint = phantom.crystalPosition.add(
-+ this.radius * Mth.cos(this.offset),
-+ -4.0F + this.verticalChange,
-+ this.radius * Mth.sin(this.offset));
-+ }
-+ }
-+ // Purpur end
-+
-+ private class PhantomMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur
-
- private float speed = 0.1F;
-
-@@ -277,8 +518,19 @@ public class Phantom extends FlyingMob implements Enemy {
- super(entity);
- }
-
-+ // Purpur start
-+ 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
-+
- @Override
-- public void tick() {
-+ public void vanillaTick() { // Purpur
- if (Phantom.this.horizontalCollision) {
- Phantom.this.setYRot(Phantom.this.getYRot() + 180.0F);
- this.speed = 0.1F;
-@@ -324,14 +576,20 @@ public class Phantom extends FlyingMob implements Enemy {
- }
- }
-
-- private class PhantomLookControl extends LookControl {
-+ private class PhantomLookControl extends org.purpurmc.purpur.controller.LookControllerWASD { // Purpur
-
- public PhantomLookControl(Mob entity) {
- super(entity);
- }
-
-+ // Purpur start
-+ public void purpurTick(Player rider) {
-+ setYawPitch(rider.getYRot(), -rider.xRotO * 0.75F);
-+ }
-+ // Purpur end
-+
- @Override
-- public void tick() {}
-+ public void vanillaTick() {} // Purpur
- }
-
- private class PhantomBodyRotationControl extends BodyRotationControl {
-@@ -418,6 +676,12 @@ public class Phantom extends FlyingMob implements Enemy {
- return false;
- } else if (!entityliving.isAlive()) {
- return false;
-+ // Purpur start
-+ } else if (level.purpurConfig.phantomBurnInLight > 0 && level.getLightEmission(new BlockPos(Phantom.this)) >= level.purpurConfig.phantomBurnInLight) {
-+ return false;
-+ } else if (level.purpurConfig.phantomIgnorePlayersWithTorch && (TORCH.test(entityliving.getItemInHand(net.minecraft.world.InteractionHand.MAIN_HAND)) || TORCH.test(entityliving.getItemInHand(net.minecraft.world.InteractionHand.OFF_HAND)))) {
-+ return false;
-+ // Purpur end
- } else {
- if (entityliving instanceof Player) {
- Player entityhuman = (Player) entityliving;
-@@ -563,6 +827,7 @@ public class Phantom extends FlyingMob implements Enemy {
- this.nextScanTick = reducedTickDelay(60);
- List list = Phantom.this.level.getNearbyPlayers(this.attackTargeting, Phantom.this, Phantom.this.getBoundingBox().inflate(16.0D, 64.0D, 16.0D));
-
-+ if (level.purpurConfig.phantomIgnorePlayersWithTorch) list.removeIf(human -> TORCH.test(human.getItemInHand(net.minecraft.world.InteractionHand.MAIN_HAND)) || TORCH.test(human.getItemInHand(net.minecraft.world.InteractionHand.OFF_HAND)));// Purpur
- if (!list.isEmpty()) {
- list.sort(Comparator.comparing((Entity e) -> { return e.getY(); }).reversed()); // CraftBukkit - decompile error
- Iterator iterator = list.iterator();
-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 cec545c3baa6599d47b9cf1a4b97de8771062a22..06a96eb0ef40462932892c611f308eb31411d099 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Pillager.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Pillager.java
-@@ -62,15 +62,49 @@ public class Pillager extends AbstractIllager implements CrossbowAttackMob, Inve
- super(type, world);
- }
-
-+ // Purpur start
-+ @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;
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.pillagerMaxHealth);
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.pillagerTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.pillagerAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @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
- 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
- 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, 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 9258d0f7c5c27b6d3d8f99db947169d6800d8ea9..0099595a5daa9c0ca9e3fd35933038c1c8ecf009 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Ravager.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Ravager.java
-@@ -65,14 +65,54 @@ public class Ravager extends Raider {
- this.setPathfindingMalus(BlockPathTypes.LEAVES, 0.0F);
- }
-
-+ // Purpur start
-+ @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();
-+ }
-+
-+ @Override
-+ public void initAttributes() {
-+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.ravagerMaxHealth);
-+ }
-+
-+ @Override
-+ public boolean isSensitiveToWater() {
-+ return this.level.purpurConfig.ravagerTakeDamageFromWater;
-+ }
-+
-+ @Override
-+ protected boolean isAlwaysExperienceDropper() {
-+ return this.level.purpurConfig.ravagerAlwaysDropExp;
-+ }
-+ // Purpur end
-+
- @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
- this.goalSelector.addGoal(4, new Ravager.RavagerMeleeAttackGoal());
- 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
- 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) -> {
-@@ -150,7 +190,7 @@ public class Ravager extends Raider {
- @Override
- public void aiStep() {
- super.aiStep();
-- if (this.isAlive()) {
-+ if (this.isAlive() && (getRider() == null || !this.isControllable())) { // Purpur
- if (this.isImmobile()) {
- this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(0.0D);
- } else {
-@@ -160,7 +200,7 @@ public class Ravager extends Raider {
- this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(Mth.lerp(0.1D, d1, d0));
- }
-
-- if (this.horizontalCollision && this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
-+ if (this.horizontalCollision && (this.level.purpurConfig.ravagerBypassMobGriefing || this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // Purpur
- boolean flag = false;
- AABB axisalignedbb = this.getBoundingBox().inflate(0.2D);
- Iterator iterator = BlockPos.betweenClosed(Mth.floor(axisalignedbb.minX), Mth.floor(axisalignedbb.minY), Mth.floor(axisalignedbb.minZ), Mth.floor(axisalignedbb.maxX), Mth.floor(axisalignedbb.maxY), Mth.floor(axisalignedbb.maxZ)).iterator();
-@@ -170,7 +210,7 @@ public class Ravager extends Raider {
- BlockState iblockdata = this.level.getBlockState(blockposition);
- Block block = iblockdata.getBlock();
-
-- if (block instanceof LeavesBlock && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.getFluidState().createLegacyBlock()).isCancelled()) { // CraftBukkit // Paper
-+ if (this.level.purpurConfig.ravagerGriefableBlocks.contains(block) && !org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.getFluidState().createLegacyBlock()).isCancelled()) { // CraftBukkit // Paper
- flag = this.level.destroyBlock(blockposition, true, this) || flag;
- }
- }
-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 8cb910da17d75a9d9c7dbeb3c9e24b6de657a2f7..8b03a027bff592b2257e065f328da6d86e11db98 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Shulker.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Shulker.java
-@@ -22,6 +22,8 @@ import net.minecraft.tags.DamageTypeTags;
- import net.minecraft.util.Mth;
- import net.minecraft.world.Difficulty;
- import net.minecraft.world.DifficultyInstance;
-+import net.minecraft.world.InteractionHand;
-+import net.minecraft.world.InteractionResult;
- import net.minecraft.world.damagesource.DamageSource;
- import net.minecraft.world.entity.Entity;
- import net.minecraft.world.entity.EntityDimensions;
-@@ -50,6 +52,8 @@ import net.minecraft.world.entity.projectile.AbstractArrow;
- import net.minecraft.world.entity.projectile.ShulkerBullet;
- import net.minecraft.world.entity.vehicle.Boat;
- import net.minecraft.world.item.DyeColor;
-+import net.minecraft.world.item.DyeItem;
-+import net.minecraft.world.item.ItemStack;
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.ServerLevelAccessor;
- import net.minecraft.world.level.block.Blocks;
-@@ -98,12 +102,59 @@ public class Shulker extends AbstractGolem implements VariantHolder