diff --git a/.github/workflows/build-1194.yml b/.github/workflows/build-120.yml similarity index 83% rename from .github/workflows/build-1194.yml rename to .github/workflows/build-120.yml index 5f35aa3d..adbd3abc 100644 --- a/.github/workflows/build-1194.yml +++ b/.github/workflows/build-120.yml @@ -1,4 +1,4 @@ -name: Build Leaf 1.19.4 +name: Build Leaf 1.20 on: [ push, pull_request ] jobs: build: @@ -22,18 +22,18 @@ jobs: - name: Create ReobfPaperclipJar run: ./gradlew -Dorg.gradle.jvmargs="-Dgraal.CompilerConfiguration=enterprise -Dgraal.UsePriorityInlining=true -Dgraal.Vectorization=true -Dgraal.OptDuplication=true --add-modules jdk.incubator.vector" createReobfPaperclipJar --stacktrace --no-daemon - name: Rename Paperclip Jar - run: mv build/libs/leaf-paperclip-1.19.4-R0.1-SNAPSHOT-reobf.jar ./leaf-1.19.4.jar + run: mv build/libs/leaf-paperclip-1.20-R0.1-SNAPSHOT-reobf.jar ./leaf-1.20.jar - name: Upload Leaf uses: actions/upload-artifact@v3 with: name: Leaf - path: ./leaf-1.19.4.jar + path: ./leaf-1.20.jar - name: Release Leaf - if: github.ref_name == 'ver/1.19.4' + if: github.ref_name == 'ver/1.20' uses: softprops/action-gh-release@master with: - name: "Leaf 1.19.4" - tag_name: "ver-1.19.4" + name: "Leaf 1.20" + tag_name: "ver-1.20" token: "${{ secrets.GITHUB_TOKEN }}" - files: "./leaf-1.19.4.jar" + files: "./leaf-1.20.jar" prerelease: false diff --git a/README.md b/README.md index 9eb2a525..386be9ca 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ## Leaf -[![Github Actions Build](https://img.shields.io/github/actions/workflow/status/Winds-Studio/Leaf/build-1194.yml?branch=ver%2F1.19.4&style=flat-square)](https://github.com/Winds-Studio/Leaf/releases) +[![Github Actions Build](https://img.shields.io/github/actions/workflow/status/Winds-Studio/Leaf/build-120.yml?branch=ver%2F1.20&style=flat-square)](https://github.com/Winds-Studio/Leaf/releases) [![MIT License](https://img.shields.io/github/license/Winds-Studio/Leaf?style=flat-square)](LICENSE)
Leaf is a drop-in replacement designed for removing some checks, customized, and high-performance built on top of Gale with optimization from other forks.
diff --git a/build.gradle.kts b/build.gradle.kts index 40539e6e..5ff56f1b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,7 @@ plugins { java `maven-publish` id("com.github.johnrengelman.shadow") version "8.1.1" apply false - id("io.papermc.paperweight.patcher") version "1.5.5" + id("io.papermc.paperweight.patcher") version "1.5.6-SNAPSHOT" } val paperMavenPublicUrl = "https://repo.papermc.io/repository/maven-public/" diff --git a/gradle.properties b/gradle.properties index ff8b19bf..981dbcb4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ group = org.dreeam.leaf -version = 1.19.4-R0.1-SNAPSHOT +version = 1.20-R0.1-SNAPSHOT -galeCommit = dad62237f8f020d6e48b4c639492f4918f905220 +galeCommit = 3efde1600dbbef211a31f71f7b6b53cdbcc46d74 org.gradle.caching = true org.gradle.parallel = true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index c1962a79..f03f475e 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37aef8d3..38f7c5ac 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-rc-2-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index aeb74cbb..fcb6fca1 100644 --- a/gradlew +++ b/gradlew @@ -130,10 +130,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. diff --git a/patches/api/0001-Pufferfish-Sentry.patch b/patches/api/0001-Pufferfish-Sentry.patch index 80a2b36d..e9a31046 100644 --- a/patches/api/0001-Pufferfish-Sentry.patch +++ b/patches/api/0001-Pufferfish-Sentry.patch @@ -7,10 +7,10 @@ Original license: GPL v3 Original project: https://github.com/pufferfish-gg/Pufferfish diff --git a/build.gradle.kts b/build.gradle.kts -index 0719e49dde343c80d18daf82d7fed926150d7d6d..e23ed86c4288ecdfef37bf5b2cd132f348bf852a 100644 +index c5b725b28b11878f31b358ea9719acb53aacac41..b1c11eaee0d53ed9ece3ddf768bded98c92dde98 100644 --- a/build.gradle.kts +++ b/build.gradle.kts -@@ -42,6 +42,7 @@ dependencies { +@@ -44,6 +44,7 @@ dependencies { apiAndDocs("net.kyori:adventure-text-logger-slf4j") api("org.apache.logging.log4j:log4j-api:2.17.1") api("org.slf4j:slf4j-api:1.8.0-beta4") diff --git a/patches/api/0003-Leaf-config-files.patch b/patches/api/0002-Leaf-config-files.patch similarity index 79% rename from patches/api/0003-Leaf-config-files.patch rename to patches/api/0002-Leaf-config-files.patch index f3dcbc39..c4fd875b 100644 --- a/patches/api/0003-Leaf-config-files.patch +++ b/patches/api/0002-Leaf-config-files.patch @@ -5,12 +5,12 @@ Subject: [PATCH] Leaf config files diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index 61cd83e6797e84679063a31aff1609e1168352c5..59f78d0fa224a416eed57bec85fad3bff71bdc56 100644 +index 007e23a9383ab8eda12c6dffb385256215356040..c890e9655551865f7cea3c92782ee6a09e90f88e 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java -@@ -2059,6 +2059,14 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi - } - // Purpur end +@@ -2047,6 +2047,14 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi + + // Paper end + // Leaf start + @NotNull diff --git a/patches/api/0002-Purpur-API-Changes.patch b/patches/api/0002-Purpur-API-Changes.patch deleted file mode 100644 index 8bf3be65..00000000 --- a/patches/api/0002-Purpur-API-Changes.patch +++ /dev/null @@ -1,4096 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Github Actions -Date: Tue, 23 May 2023 21:25:15 +0000 -Subject: [PATCH] Purpur API Changes - -Original license: MIT -Original project: https://github.com/PurpurMC/Purpur - -diff --git a/build.gradle.kts b/build.gradle.kts -index e23ed86c4288ecdfef37bf5b2cd132f348bf852a..3e148458cc78a3225e8d6572b43e1d358791eec2 100644 ---- a/build.gradle.kts -+++ b/build.gradle.kts -@@ -108,6 +108,8 @@ tasks.jar { - } - - tasks.withType { -+ (options as StandardJavadocDocletOptions).addStringOption("-add-modules", "jdk.incubator.vector") // Purpur - our javadocs need this for pufferfish's SIMD patch -+ (options as StandardJavadocDocletOptions).addStringOption("Xdoclint:none", "-quiet") // Purpur - silence Paper's bajillion javadoc warnings - val options = options as StandardJavadocDocletOptions - options.overview = "src/main/javadoc/overview.html" - options.use() -diff --git a/src/main/java/co/aikar/timings/TimedEventExecutor.java b/src/main/java/co/aikar/timings/TimedEventExecutor.java -index 8f29c1561ba5916cb5634392edd8bd2a5a294a51..6fbc64e0f214d0c8e5afcbe385e414a4e1fe1c72 100644 ---- a/src/main/java/co/aikar/timings/TimedEventExecutor.java -+++ b/src/main/java/co/aikar/timings/TimedEventExecutor.java -@@ -77,9 +77,9 @@ public class TimedEventExecutor implements EventExecutor { - executor.execute(listener, event); - return; - } -- try (Timing ignored = timings.startTiming()){ -+ //try (Timing ignored = timings.startTiming()){ // Purpur - executor.execute(listener, event); -- } -+ //} // Purpur - } - - @Override -diff --git a/src/main/java/co/aikar/timings/Timing.java b/src/main/java/co/aikar/timings/Timing.java -index 7514fad26f955329f8bf17ff17db75f0c8301ee5..1d866e980abc542bdfee1ce082cd9cdd7761e9f7 100644 ---- a/src/main/java/co/aikar/timings/Timing.java -+++ b/src/main/java/co/aikar/timings/Timing.java -@@ -39,6 +39,7 @@ public interface Timing extends AutoCloseable { - * @return Timing - */ - @NotNull -+ @io.papermc.paper.annotation.DoNotUse // Purpur - Timing startTiming(); - - /** -@@ -46,6 +47,7 @@ public interface Timing extends AutoCloseable { - * - * Will automatically be called when this Timing is used with try-with-resources - */ -+ @io.papermc.paper.annotation.DoNotUse // Purpur - void stopTiming(); - - /** -@@ -56,6 +58,7 @@ public interface Timing extends AutoCloseable { - * @return Timing - */ - @NotNull -+ @io.papermc.paper.annotation.DoNotUse // Purpur - Timing startTimingIfSync(); - - /** -@@ -65,12 +68,14 @@ public interface Timing extends AutoCloseable { - * - * But only if we are on the primary thread. - */ -+ @io.papermc.paper.annotation.DoNotUse // Purpur - void stopTimingIfSync(); - - /** - * @deprecated Doesn't do anything - Removed - */ - @Deprecated -+ @io.papermc.paper.annotation.DoNotUse // Purpur - void abort(); - - /** -@@ -82,5 +87,6 @@ public interface Timing extends AutoCloseable { - TimingHandler getTimingHandler(); - - @Override -+ @io.papermc.paper.annotation.DoNotUse // Purpur - void close(); - } -diff --git a/src/main/java/co/aikar/timings/Timings.java b/src/main/java/co/aikar/timings/Timings.java -index 9812d668ad945aba486fbf6d5bf83c4292cb5d03..752d54830aa8baa1450bf72da03ae55ed30293c2 100644 ---- a/src/main/java/co/aikar/timings/Timings.java -+++ b/src/main/java/co/aikar/timings/Timings.java -@@ -124,7 +124,7 @@ public final class Timings { - @NotNull - public static Timing ofStart(@NotNull Plugin plugin, @NotNull String name, @Nullable Timing groupHandler) { - Timing timing = of(plugin, name, groupHandler); -- timing.startTiming(); -+ //timing.startTiming(); // Purpur - return timing; - } - -@@ -145,9 +145,11 @@ public final class Timings { - * @param enabled Should timings be reported - */ - public static void setTimingsEnabled(boolean enabled) { -- timingsEnabled = enabled; -- warnAboutDeprecationOnEnable(); -- reset(); -+ // Purpur start - we don't do that here... -+ timingsEnabled = false; -+ //warnAboutDeprecationOnEnable(); -+ //reset(); -+ // Purpur end - } - - private static void warnAboutDeprecationOnEnable() { -diff --git a/src/main/java/co/aikar/timings/TimingsCommand.java b/src/main/java/co/aikar/timings/TimingsCommand.java -index e801e79fa57c44b2e5d359647c920f88064826f1..1abfcee0f6d632f4cd8d74b4994a90c9ea9d254c 100644 ---- a/src/main/java/co/aikar/timings/TimingsCommand.java -+++ b/src/main/java/co/aikar/timings/TimingsCommand.java -@@ -45,7 +45,7 @@ public class TimingsCommand extends BukkitCommand { - public TimingsCommand(@NotNull String name) { - super(name); - this.description = "Manages Spigot Timings data to see performance of the server."; -- this.usageMessage = "/timings "; -+ this.usageMessage = "/timings";// "; // Purpur - this.setPermission("bukkit.command.timings"); - } - -@@ -54,8 +54,12 @@ public class TimingsCommand extends BukkitCommand { - if (!testPermission(sender)) { - return true; - } -- if (false) { -- sender.sendMessage(Timings.deprecationMessage()); -+ if (true) { -+ net.kyori.adventure.text.minimessage.MiniMessage mm = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage(); -+ sender.sendMessage(mm.deserialize("Purpur has removed timings to save your performance. Please use /spark instead")); -+ sender.sendMessage(mm.deserialize("For more information, view its documentation at")); -+ sender.sendMessage(mm.deserialize("https://spark.lucko.me/docs/Command-Usage")); -+ return true; - } - if (args.length < 1) { - sender.sendMessage(text("Usage: " + this.usageMessage, NamedTextColor.RED)); -@@ -115,7 +119,7 @@ public class TimingsCommand extends BukkitCommand { - Preconditions.checkNotNull(args, "Arguments cannot be null"); - Preconditions.checkNotNull(alias, "Alias cannot be null"); - -- if (args.length == 1) { -+ if (false && args.length == 1) { // Purpur - return StringUtil.copyPartialMatches(args[0], TIMINGS_SUBCOMMANDS, - new ArrayList(TIMINGS_SUBCOMMANDS.size())); - } -diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java b/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java -index b7a2cecb334ce39fa09d8ab949a29eedbdc44c36..b1f35c68373edfe666ca05b50f0ec022a1859ce9 100644 ---- a/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java -+++ b/src/main/java/com/destroystokyo/paper/entity/ai/VanillaGoal.java -@@ -201,6 +201,18 @@ public interface VanillaGoal extends Goal { - GoalKey CLIMB_ON_TOP_OF_POWDER_SNOW = GoalKey.of(Mob.class, NamespacedKey.minecraft("climb_on_top_of_powder_snow")); - GoalKey WOLF_PANIC = GoalKey.of(Wolf.class, NamespacedKey.minecraft("wolf_panic")); - -+ // Purpur start -+ GoalKey MOB_HAS_RIDER = GoalKey.of(Mob.class, NamespacedKey.minecraft("has_rider")); -+ GoalKey HORSE_HAS_RIDER = GoalKey.of(AbstractHorse.class, NamespacedKey.minecraft("horse_has_rider")); -+ GoalKey LLAMA_HAS_RIDER = GoalKey.of(Llama.class, NamespacedKey.minecraft("llama_has_rider")); -+ GoalKey FIND_CRYSTAL = GoalKey.of(Phantom.class, NamespacedKey.minecraft("find_crystal")); -+ GoalKey ORBIT_CRYSTAL = GoalKey.of(Phantom.class, NamespacedKey.minecraft("orbit_crystal")); -+ GoalKey DROWNED_ATTACK_VILLAGER = GoalKey.of(Drowned.class, NamespacedKey.minecraft("drowned_attack_villager")); -+ GoalKey ZOMBIE_ATTACK_VILLAGER = GoalKey.of(Zombie.class, NamespacedKey.minecraft("zombie_attack_villager")); -+ GoalKey AVOID_RABID_WOLF = GoalKey.of(Wolf.class, NamespacedKey.minecraft("avoid_rabid_wolf")); -+ GoalKey RECEIVE_FLOWER = GoalKey.of(IronGolem.class, NamespacedKey.minecraft("receive_flower")); -+ // Purpur end -+ - /** - * @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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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= f) { -+ if ((!this.level.purpurConfig.shulkerSpawnFromBulletRequireOpenLid || !this.isClosed()) && this.teleportSomewhere()) { -+ // Purpur start -+ float chance = this.level.purpurConfig.shulkerSpawnFromBulletBaseChance; -+ if (!this.level.purpurConfig.shulkerSpawnFromBulletNearbyEquation.isBlank()) { -+ int nearby = this.level.getEntities((EntityTypeTest) EntityType.SHULKER, axisalignedbb.inflate(this.level.purpurConfig.shulkerSpawnFromBulletNearbyRange), Entity::isAlive).size(); -+ try { -+ chance -= ((Number) scriptEngine.eval("let nearby = " + nearby + "; " + this.level.purpurConfig.shulkerSpawnFromBulletNearbyEquation)).floatValue(); -+ } catch (javax.script.ScriptException e) { -+ e.printStackTrace(); -+ chance -= (nearby - 1) / 5.0F; -+ } -+ } -+ if (this.level.random.nextFloat() <= chance) { -+ // Purpur end - Shulker entityshulker = (Shulker) EntityType.SHULKER.create(this.level); - - if (entityshulker != null) { -@@ -601,7 +661,7 @@ public class Shulker extends AbstractGolem implements VariantHolder getVariant() { -- return Optional.ofNullable(this.getColor()); -+ return Optional.ofNullable(this.level.purpurConfig.shulkerSpawnFromBulletRandomColor ? DyeColor.random(this.level.random) : this.getColor()); // Purpur - } - - @Nullable -@@ -611,7 +671,7 @@ public class Shulker extends AbstractGolem implements VariantHolder(this, Player.class, true)); - } -@@ -184,7 +218,7 @@ public class Silverfish extends Monster { - continue; - } - // CraftBukkit end -- if (world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { -+ if (world.purpurConfig.silverfishBypassMobGriefing || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) { // Purpur - world.destroyBlock(blockposition1, true, this.silverfish); - } else { - world.setBlock(blockposition1, ((InfestedBlock) block).hostStateByInfested(world.getBlockState(blockposition1)), 3); -@@ -222,7 +256,7 @@ public class Silverfish extends Monster { - } else { - RandomSource randomsource = this.mob.getRandom(); - -- if (this.mob.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && randomsource.nextInt(reducedTickDelay(10)) == 0) { -+ if ((this.mob.level.purpurConfig.silverfishBypassMobGriefing || this.mob.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && randomsource.nextInt(reducedTickDelay(10)) == 0) { // Purpur - this.selectedDirection = Direction.getRandom(randomsource); - BlockPos blockposition = BlockPos.containing(this.mob.getX(), this.mob.getY() + 0.5D, this.mob.getZ()).relative(this.selectedDirection); - BlockState iblockdata = this.mob.level.getBlockState(blockposition); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java -index badde621357a567965f0ef203e402e21bed09059..64a5e000adbfa5de2abc32ea9182847dbf83293d 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java -@@ -14,6 +14,16 @@ import net.minecraft.world.item.Items; - import net.minecraft.world.level.ItemLike; - import net.minecraft.world.level.Level; - -+// Purpur start -+import net.minecraft.world.item.ItemStack; -+import net.minecraft.world.level.block.Blocks; -+import org.bukkit.craftbukkit.event.CraftEventFactory; -+import net.minecraft.world.InteractionHand; -+import net.minecraft.world.InteractionResult; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.core.particles.ParticleTypes; -+// Purpur end -+ - public class Skeleton extends AbstractSkeleton { - - private static final int TOTAL_CONVERSION_TIME = 300; -@@ -26,6 +36,38 @@ public class Skeleton extends AbstractSkeleton { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.skeletonRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level.purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level.purpurConfig.skeletonRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.skeletonControllable; -+ } -+ -+ @Override -+ public void initAttributes() { -+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.skeletonMaxHealth); -+ } -+ -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level.purpurConfig.skeletonTakeDamageFromWater; -+ } -+ -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level.purpurConfig.skeletonAlwaysDropExp; -+ } -+ // Purpur end -+ - @Override - protected void defineSynchedData() { - super.defineSynchedData(); -@@ -142,4 +184,67 @@ public class Skeleton extends AbstractSkeleton { - } - - } -+ -+ // Purpur start -+ private int witherRosesFed = 0; -+ -+ @Override -+ public InteractionResult mobInteract(Player player, InteractionHand hand) { -+ ItemStack stack = player.getItemInHand(hand); -+ -+ if (level.purpurConfig.skeletonFeedWitherRoses > 0 && this.getType() != EntityType.WITHER_SKELETON && stack.getItem() == Blocks.WITHER_ROSE.asItem()) { -+ return this.feedWitherRose(player, stack); -+ } -+ -+ return super.mobInteract(player, hand); -+ } -+ -+ private InteractionResult feedWitherRose(Player player, ItemStack stack) { -+ if (++witherRosesFed < level.purpurConfig.skeletonFeedWitherRoses) { -+ if (!player.getAbilities().instabuild) { -+ stack.shrink(1); -+ } -+ return InteractionResult.CONSUME; -+ } -+ -+ WitherSkeleton skeleton = EntityType.WITHER_SKELETON.create(level); -+ if (skeleton == null) { -+ return InteractionResult.PASS; -+ } -+ -+ skeleton.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); -+ skeleton.setHealth(this.getHealth()); -+ skeleton.setAggressive(this.isAggressive()); -+ skeleton.copyPosition(this); -+ skeleton.setYBodyRot(this.yBodyRot); -+ skeleton.setYHeadRot(this.getYHeadRot()); -+ skeleton.yRotO = this.yRotO; -+ skeleton.xRotO = this.xRotO; -+ -+ if (this.hasCustomName()) { -+ skeleton.setCustomName(this.getCustomName()); -+ } -+ -+ if (CraftEventFactory.callEntityTransformEvent(this, skeleton, org.bukkit.event.entity.EntityTransformEvent.TransformReason.INFECTION).isCancelled()) { -+ return InteractionResult.PASS; -+ } -+ -+ if (!new com.destroystokyo.paper.event.entity.EntityTransformedEvent(this.getBukkitEntity(), skeleton.getBukkitEntity(), com.destroystokyo.paper.event.entity.EntityTransformedEvent.TransformedReason.INFECTED).callEvent()) { -+ return InteractionResult.PASS; -+ } -+ -+ this.level.addFreshEntity(skeleton); -+ 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/monster/Slime.java b/src/main/java/net/minecraft/world/entity/monster/Slime.java -index c41c1c2712920c6b7d822cd0f37a5d8d725e4054..89978fcb14362af2527693f3e6ec57e169080c9f 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Slime.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java -@@ -64,6 +64,7 @@ public class Slime extends Mob implements Enemy { - public float squish; - public float oSquish; - private boolean wasOnGround; -+ protected boolean actualJump; // Purpur - - public Slime(EntityType type, Level world) { - super(type, world); -@@ -71,12 +72,89 @@ public class Slime extends Mob implements Enemy { - this.moveControl = new Slime.SlimeMoveControl(this); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.slimeRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level.purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level.purpurConfig.slimeRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.slimeControllable; -+ } -+ -+ @Override -+ public float getJumpPower() { -+ float height = super.getJumpPower(); -+ return getRider() != null && this.isControllable() && actualJump ? height * 1.5F : height; -+ } -+ -+ @Override -+ public boolean onSpacebar() { -+ if (onGround && getRider() != null && this.isControllable()) { -+ actualJump = true; -+ if (getRider().getForwardMot() == 0 || getRider().getStrafeMot() == 0) { -+ jumpFromGround(); // jump() here if not moving -+ } -+ } -+ return true; // do not jump() in wasd controller, let vanilla controller handle -+ } -+ -+ protected String getMaxHealthEquation() { -+ return level.purpurConfig.slimeMaxHealth; -+ } -+ -+ protected String getAttackDamageEquation() { -+ return level.purpurConfig.slimeAttackDamage; -+ } -+ -+ protected java.util.Map getMaxHealthCache() { -+ return level.purpurConfig.slimeMaxHealthCache; -+ } -+ -+ protected java.util.Map getAttackDamageCache() { -+ return level.purpurConfig.slimeAttackDamageCache; -+ } -+ -+ protected double getFromCache(java.util.function.Supplier equation, java.util.function.Supplier> cache, java.util.function.Supplier defaultValue) { -+ int size = getSize(); -+ Double value = cache.get().get(size); -+ if (value == null) { -+ try { -+ value = ((Number) scriptEngine.eval("let size = " + size + "; " + equation.get())).doubleValue(); -+ } catch (javax.script.ScriptException e) { -+ e.printStackTrace(); -+ value = defaultValue.get(); -+ } -+ cache.get().put(size, value); -+ } -+ return value; -+ } -+ -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level.purpurConfig.slimeTakeDamageFromWater; -+ } -+ -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level.purpurConfig.slimeAlwaysDropExp; -+ } -+ // Purpur end -+ - @Override - protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(1, new Slime.SlimeFloatGoal(this)); - this.goalSelector.addGoal(2, new Slime.SlimeAttackGoal(this)); - this.goalSelector.addGoal(3, new Slime.SlimeRandomDirectionGoal(this)); - this.goalSelector.addGoal(5, new Slime.SlimeKeepOnJumpingGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.targetSelector.addGoal(1, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, (entityliving) -> { - return Math.abs(entityliving.getY() - this.getY()) <= 4.0D; - })); -@@ -96,9 +174,9 @@ public class Slime extends Mob implements Enemy { - this.entityData.set(Slime.ID_SIZE, j); - this.reapplyPosition(); - this.refreshDimensions(); -- this.getAttribute(Attributes.MAX_HEALTH).setBaseValue((double) (j * j)); -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(getFromCache(this::getMaxHealthEquation, this::getMaxHealthCache, () -> (double) size * size)); // Purpur - this.getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue((double) (0.2F + 0.1F * (float) j)); -- this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue((double) j); -+ this.getAttribute(Attributes.ATTACK_DAMAGE).setBaseValue(getFromCache(this::getAttackDamageEquation, this::getAttackDamageCache, () -> (double) j)); // Purpur - if (heal) { - this.setHealth(this.getMaxHealth()); - } -@@ -368,11 +446,12 @@ public class Slime extends Mob implements Enemy { - } - - @Override -- protected void jumpFromGround() { -+ public void jumpFromGround() { // Purpur - protected -> public - Vec3 vec3d = this.getDeltaMovement(); - - this.setDeltaMovement(vec3d.x, (double) this.getJumpPower(), vec3d.z); - this.hasImpulse = true; -+ this.actualJump = false; // Purpur - } - - @Nullable -@@ -406,7 +485,7 @@ public class Slime extends Mob implements Enemy { - return super.getDimensions(pose).scale(0.255F * (float) this.getSize()); - } - -- private static class SlimeMoveControl extends MoveControl { -+ private static class SlimeMoveControl extends org.purpurmc.purpur.controller.MoveControllerWASD { // Purpur - - private float yRot; - private int jumpDelay; -@@ -425,21 +504,33 @@ public class Slime extends Mob implements Enemy { - } - - public void setWantedMovement(double speed) { -- this.speedModifier = speed; -+ this.setSpeedModifier(speed); // Purpur - this.operation = MoveControl.Operation.MOVE_TO; - } - - @Override - public void tick() { -+ // Purpur start -+ if (slime.getRider() != null && slime.isControllable()) { -+ purpurTick(slime.getRider()); -+ if (slime.getForwardMot() != 0 || slime.getStrafeMot() != 0) { -+ if (jumpDelay > 10) { -+ jumpDelay = 6; -+ } -+ } else { -+ jumpDelay = 20; -+ } -+ } else { -+ // Purpur end - this.mob.setYRot(this.rotlerp(this.mob.getYRot(), this.yRot, 90.0F)); - this.mob.yHeadRot = this.mob.getYRot(); - this.mob.yBodyRot = this.mob.getYRot(); -- if (this.operation != MoveControl.Operation.MOVE_TO) { -+ } if ((slime.getRider() == null || !slime.isControllable()) && this.operation != MoveControl.Operation.MOVE_TO) { // Purpur - this.mob.setZza(0.0F); - } else { - this.operation = MoveControl.Operation.WAIT; - if (this.mob.isOnGround()) { -- this.mob.setSpeed((float) (this.speedModifier * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED))); -+ this.mob.setSpeed((float) (this.getSpeedModifier() * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED) * (slime.getRider() != null && slime.isControllable() && (slime.getRider().getForwardMot() != 0 || slime.getRider().getStrafeMot() != 0) ? 2.0D : 1.0D))); // Purpur - if (this.jumpDelay-- <= 0) { - this.jumpDelay = this.slime.getJumpDelay(); - if (this.isAggressive) { -@@ -456,7 +547,7 @@ public class Slime extends Mob implements Enemy { - this.mob.setSpeed(0.0F); - } - } else { -- this.mob.setSpeed((float) (this.speedModifier * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED))); -+ this.mob.setSpeed((float) (this.getSpeedModifier() * this.mob.getAttributeValue(Attributes.MOVEMENT_SPEED) * (slime.getRider() != null && slime.isControllable() && (slime.getRider().getForwardMot() != 0 || slime.getRider().getStrafeMot() != 0) ? 2.0D : 1.0D))); // Purpur - } - - } -diff --git a/src/main/java/net/minecraft/world/entity/monster/Spider.java b/src/main/java/net/minecraft/world/entity/monster/Spider.java -index 0c36bb47bd7040f1544817810e1c87157cdaff96..8e071a0922164970e033029c12058db9e8da261a 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Spider.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Spider.java -@@ -51,14 +51,48 @@ public class Spider extends Monster { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.spiderRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level.purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level.purpurConfig.spiderRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.spiderControllable; -+ } -+ -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.spiderMaxHealth); -+ } -+ -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level.purpurConfig.spiderTakeDamageFromWater; -+ } -+ -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level.purpurConfig.spiderAlwaysDropExp; -+ } -+ // 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(3, new LeapAtTargetGoal(this, 0.4F)); - this.goalSelector.addGoal(4, new Spider.SpiderAttackGoal(this)); - 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 HurtByTargetGoal(this, new Class[0])); - this.targetSelector.addGoal(2, new Spider.SpiderTargetGoal<>(this, Player.class)); - this.targetSelector.addGoal(3, new Spider.SpiderTargetGoal<>(this, IronGolem.class)); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Stray.java b/src/main/java/net/minecraft/world/entity/monster/Stray.java -index 118b636a44e4b062e812e433f603b039276337da..677b304c177a1e2bdaddf3044b44a06395ee6b6e 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Stray.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Stray.java -@@ -21,6 +21,38 @@ public class Stray extends AbstractSkeleton { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.strayRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level.purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level.purpurConfig.strayRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.strayControllable; -+ } -+ -+ @Override -+ public void initAttributes() { -+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.strayMaxHealth); -+ } -+ -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level.purpurConfig.strayTakeDamageFromWater; -+ } -+ -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level.purpurConfig.strayAlwaysDropExp; -+ } -+ // Purpur end -+ - public static boolean checkStraySpawnRules(EntityType type, ServerLevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) { - BlockPos blockPos = pos; - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Strider.java b/src/main/java/net/minecraft/world/entity/monster/Strider.java -index 66755efc54059dfb8625f028bf0548d188a57aa2..0b5d3837536d526c25ba1e12be142bb476d03519 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Strider.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Strider.java -@@ -94,12 +94,44 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { - super(type, world); - this.steering = new ItemBasedSteering(this.entityData, Strider.DATA_BOOST_TIME, Strider.DATA_SADDLE_ID); - this.blocksBuilding = true; -- this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F); -+ if (isSensitiveToWater()) this.setPathfindingMalus(BlockPathTypes.WATER, -1.0F); // Purpur - this.setPathfindingMalus(BlockPathTypes.LAVA, 0.0F); - this.setPathfindingMalus(BlockPathTypes.DANGER_FIRE, 0.0F); - this.setPathfindingMalus(BlockPathTypes.DAMAGE_FIRE, 0.0F); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.striderRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level.purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level.purpurConfig.striderRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.striderControllable; -+ } -+ -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.striderMaxHealth); -+ } -+ -+ @Override -+ public int getPurpurBreedTime() { -+ return this.level.purpurConfig.striderBreedingTicks; -+ } -+ -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level.purpurConfig.striderAlwaysDropExp; -+ } -+ // Purpur end -+ - public static boolean checkStriderSpawnRules(EntityType type, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) { - BlockPos.MutableBlockPos blockposition_mutableblockposition = pos.mutable(); - -@@ -161,6 +193,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { - @Override - protected void registerGoals() { - this.panicGoal = new PanicGoal(this, 1.65D); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(1, this.panicGoal); - this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D)); - this.temptGoal = new TemptGoal(this, 1.4D, Strider.TEMPT_ITEMS, false); -@@ -416,7 +449,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { - - @Override - public boolean isSensitiveToWater() { -- return true; -+ return this.level.purpurConfig.striderTakeDamageFromWater; // Purpur - } - - @Override -@@ -458,6 +491,19 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { - public InteractionResult mobInteract(Player player, InteractionHand hand) { - boolean flag = this.isFood(player.getItemInHand(hand)); - -+ // Purpur start -+ if (level.purpurConfig.striderGiveSaddleBack && player.isSecondaryUseActive() && !flag && isSaddled() && !isVehicle()) { -+ this.steering.setSaddle(false); -+ if (!player.getAbilities().instabuild) { -+ ItemStack saddle = new ItemStack(Items.SADDLE); -+ if (!player.getInventory().add(saddle)) { -+ player.drop(saddle, false); -+ } -+ } -+ return InteractionResult.SUCCESS; -+ } -+ // Purpur end -+ - if (!flag && this.isSaddled() && !this.isVehicle() && !player.isSecondaryUseActive()) { - if (!this.level.isClientSide) { - player.startRiding(this); -@@ -470,7 +516,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { - if (!enuminteractionresult.consumesAction()) { - ItemStack itemstack = player.getItemInHand(hand); - -- return itemstack.is(Items.SADDLE) ? itemstack.interactLivingEntity(player, this, hand) : InteractionResult.PASS; -+ return itemstack.is(Items.SADDLE) ? itemstack.interactLivingEntity(player, this, hand) : tryRide(player, hand); // Purpur - } else { - if (flag && !this.isSilent()) { - this.level.playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.STRIDER_EAT, this.getSoundSource(), 1.0F, 1.0F + (this.random.nextFloat() - this.random.nextFloat()) * 0.2F); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Vex.java b/src/main/java/net/minecraft/world/entity/monster/Vex.java -index bb5c2f90bef5e3c57ffde996853e122d108b2789..4c4c4d52e2be963024106783b4d28713f125e2e6 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Vex.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Vex.java -@@ -63,6 +63,65 @@ public class Vex extends Monster implements TraceableEntity { - this.xpReward = 3; - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.vexRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level.purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level.purpurConfig.vexRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.vexControllable; -+ } -+ -+ @Override -+ public double getMaxY() { -+ return level.purpurConfig.vexMaxY; -+ } -+ -+ @Override -+ public void travel(Vec3 vec3) { -+ super.travel(vec3); -+ if (getRider() != null && this.isControllable()) { -+ float speed; -+ if (onGround) { -+ speed = (float) getAttributeValue(Attributes.MOVEMENT_SPEED) * 0.1F; -+ } else { -+ speed = (float) getAttributeValue(Attributes.FLYING_SPEED); -+ } -+ setSpeed(speed); -+ Vec3 mot = getDeltaMovement(); -+ move(MoverType.SELF, mot.multiply(speed, 1.0, speed)); -+ setDeltaMovement(mot.scale(0.9D)); -+ } -+ } -+ -+ @Override -+ public boolean causeFallDamage(float fallDistance, float damageMultiplier, DamageSource damageSource) { -+ return false; // no fall damage please -+ } -+ -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.vexMaxHealth); -+ } -+ -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level.purpurConfig.vexTakeDamageFromWater; -+ } -+ -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level.purpurConfig.vexAlwaysDropExp; -+ } -+ // Purpur end -+ - @Override - protected float getStandingEyeHeight(Pose pose, EntityDimensions dimensions) { - return dimensions.height - 0.28125F; -@@ -81,7 +140,7 @@ public class Vex extends Monster implements TraceableEntity { - - @Override - public void tick() { -- this.noPhysics = true; -+ this.noPhysics = getRider() == null || !this.isControllable(); // Purpur - super.tick(); - this.noPhysics = false; - this.setNoGravity(true); -@@ -96,17 +155,19 @@ public class Vex extends Monster implements TraceableEntity { - protected void registerGoals() { - super.registerGoals(); - this.goalSelector.addGoal(0, new FloatGoal(this)); -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(4, new Vex.VexChargeAttackGoal()); - this.goalSelector.addGoal(8, new Vex.VexRandomMoveGoal()); - this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 3.0F, 1.0F)); - this.goalSelector.addGoal(10, new LookAtPlayerGoal(this, Mob.class, 8.0F)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[]{Raider.class})).setAlertOthers()); - this.targetSelector.addGoal(2, new Vex.VexCopyOwnerTargetGoal(this)); - this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Player.class, true)); - } - - public static AttributeSupplier.Builder createAttributes() { -- return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 14.0D).add(Attributes.ATTACK_DAMAGE, 4.0D); -+ return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 14.0D).add(Attributes.ATTACK_DAMAGE, 4.0D).add(Attributes.FLYING_SPEED, 0.6D); // Purpur; - } - - @Override -@@ -235,14 +296,14 @@ public class Vex extends Monster implements TraceableEntity { - return 0.4D; - } - -- private class VexMoveControl extends MoveControl { -+ private class VexMoveControl extends org.purpurmc.purpur.controller.FlyingMoveControllerWASD { // Purpur - - public VexMoveControl(Vex entityvex) { - super(entityvex); - } - - @Override -- public void tick() { -+ public void vanillaTick() { // Purpur - if (this.operation == MoveControl.Operation.MOVE_TO) { - Vec3 vec3d = new Vec3(this.wantedX - Vex.this.getX(), this.wantedY - Vex.this.getY(), this.wantedZ - Vex.this.getZ()); - double d0 = vec3d.length(); -@@ -251,7 +312,7 @@ public class Vex extends Monster implements TraceableEntity { - this.operation = MoveControl.Operation.WAIT; - Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().scale(0.5D)); - } else { -- Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().add(vec3d.scale(this.speedModifier * 0.05D / d0))); -+ Vex.this.setDeltaMovement(Vex.this.getDeltaMovement().add(vec3d.scale(this.getSpeedModifier() * 0.05D / d0))); // Purpur - if (Vex.this.getTarget() == null) { - Vec3 vec3d1 = Vex.this.getDeltaMovement(); - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java -index a9e75a16a7dc0ff5d4f0faa92ebc444559a39325..1a333dce35a13b88cb0afdea192585e0bae38442 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java -@@ -58,14 +58,48 @@ public class Vindicator extends AbstractIllager { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.vindicatorRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level.purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level.purpurConfig.vindicatorRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.vindicatorControllable; -+ } -+ -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.vindicatorMaxHealth); -+ } -+ -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level.purpurConfig.vindicatorTakeDamageFromWater; -+ } -+ -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level.purpurConfig.vindicatorAlwaysDropExp; -+ } -+ // 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 Vindicator.VindicatorBreakDoorGoal(this)); - this.goalSelector.addGoal(2, new AbstractIllager.RaiderOpenDoorGoal(this)); - this.goalSelector.addGoal(3, new Raider.HoldGroundAttackGoal(this, 10.0F)); - this.goalSelector.addGoal(4, new Vindicator.VindicatorMeleeAttackGoal(this)); -+ 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, true)); -@@ -130,6 +164,12 @@ public class Vindicator extends AbstractIllager { - RandomSource randomSource = world.getRandom(); - this.populateDefaultEquipmentSlots(randomSource, difficulty); - this.populateDefaultEquipmentEnchantments(randomSource, difficulty); -+ // Purpur start -+ Level level = world.getMinecraftWorld(); -+ if (level.purpurConfig.vindicatorJohnnySpawnChance > 0D && random.nextDouble() <= level.purpurConfig.vindicatorJohnnySpawnChance) { -+ setCustomName(Component.translatable("Johnny")); -+ } -+ // Purpur end - return spawnGroupData; - } - -diff --git a/src/main/java/net/minecraft/world/entity/monster/Witch.java b/src/main/java/net/minecraft/world/entity/monster/Witch.java -index 096546d7a97f031060bda7545aa620d522766719..dcf8cdb8343706b55df206fed70fe3a8373e27a6 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Witch.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Witch.java -@@ -57,6 +57,38 @@ public class Witch extends Raider implements RangedAttackMob { - super(type, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.witchRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level.purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level.purpurConfig.witchRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.witchControllable; -+ } -+ -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.witchMaxHealth); -+ } -+ -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level.purpurConfig.witchTakeDamageFromWater; -+ } -+ -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level.purpurConfig.witchAlwaysDropExp; -+ } -+ // Purpur end -+ - @Override - protected void registerGoals() { - super.registerGoals(); -@@ -65,10 +97,12 @@ public class Witch extends Raider implements RangedAttackMob { - }); - this.attackPlayersGoal = new NearestAttackableWitchTargetGoal<>(this, Player.class, 10, true, false, (Predicate) null); - this.goalSelector.addGoal(1, new FloatGoal(this)); -+ this.goalSelector.addGoal(1, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.goalSelector.addGoal(2, new RangedAttackGoal(this, 1.0D, 60, 10.0F)); - this.goalSelector.addGoal(2, new WaterAvoidingRandomStrollGoal(this, 1.0D)); - this.goalSelector.addGoal(3, new LookAtPlayerGoal(this, Player.class, 8.0F)); - this.goalSelector.addGoal(3, new RandomLookAroundGoal(this)); -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[]{Raider.class})); - this.targetSelector.addGoal(2, this.healRaidersGoal); - this.targetSelector.addGoal(3, this.attackPlayersGoal); -diff --git a/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java -index 6449213d717271bcc516e393a78dfe1e5c762d68..9016fb6da9c86ca9906f6beb2f6927cede50c804 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java -+++ b/src/main/java/net/minecraft/world/entity/monster/WitherSkeleton.java -@@ -35,6 +35,38 @@ public class WitherSkeleton extends AbstractSkeleton { - this.setPathfindingMalus(BlockPathTypes.LAVA, 8.0F); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.witherSkeletonRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level.purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level.purpurConfig.witherSkeletonRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.witherSkeletonControllable; -+ } -+ -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.witherSkeletonMaxHealth); -+ } -+ -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level.purpurConfig.witherSkeletonTakeDamageFromWater; -+ } -+ -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level.purpurConfig.witherSkeletonAlwaysDropExp; -+ } -+ // Purpur end -+ - @Override - protected void registerGoals() { - this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractPiglin.class, true)); -diff --git a/src/main/java/net/minecraft/world/entity/monster/Zoglin.java b/src/main/java/net/minecraft/world/entity/monster/Zoglin.java -index 818d1f09b63ad0df9e3fae059a05801571a5606d..5f92444447c5f0af0fddab79794f83b58e1f9387 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Zoglin.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Zoglin.java -@@ -67,6 +67,38 @@ public class Zoglin extends Monster implements Enemy, HoglinBase { - this.xpReward = 5; - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.zoglinRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level.purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level.purpurConfig.zoglinRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.zoglinControllable; -+ } -+ -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.zoglinMaxHealth); -+ } -+ -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level.purpurConfig.zoglinTakeDamageFromWater; -+ } -+ -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level.purpurConfig.zoglinAlwaysDropExp; -+ } -+ // Purpur end -+ - @Override - protected Brain.Provider brainProvider() { - return Brain.provider(MEMORY_TYPES, SENSOR_TYPES); -@@ -198,6 +230,7 @@ public class Zoglin extends Monster implements Enemy, HoglinBase { - - @Override - protected void customServerAiStep() { -+ if (getRider() == null || !this.isControllable()) // Purpur - only use brain if no rider - this.getBrain().tick((ServerLevel)this.level, this); - this.updateActivity(); - } -diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -index 9976205537cfe228735687f1e9c52c74ac025690..d8392f2dd7c111bd5badb3aeb28a1e20db2caf77 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java -+++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -@@ -95,22 +95,69 @@ public class Zombie extends Monster { - private int inWaterTime; - public int conversionTime; - private int lastTick = MinecraftServer.currentTick; // CraftBukkit - add field -- private boolean shouldBurnInDay = true; // Paper -+ // private boolean shouldBurnInDay = true; // Paper // Purpur - implemented in LivingEntity - - public Zombie(EntityType type, Level world) { - super(type, world); - this.breakDoorGoal = new BreakDoorGoal(this, com.google.common.base.Predicates.in(world.paperConfig().entities.behavior.doorBreakingDifficulty.getOrDefault(type, world.paperConfig().entities.behavior.doorBreakingDifficulty.get(EntityType.ZOMBIE)))); // Paper -+ this.setShouldBurnInDay(true); // Purpur - } - - public Zombie(Level world) { - this(EntityType.ZOMBIE, world); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.zombieRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level.purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level.purpurConfig.zombieRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.zombieControllable; -+ } -+ -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.zombieMaxHealth); -+ } -+ -+ public boolean jockeyOnlyBaby() { -+ return level.purpurConfig.zombieJockeyOnlyBaby; -+ } -+ -+ public double jockeyChance() { -+ return level.purpurConfig.zombieJockeyChance; -+ } -+ -+ public boolean jockeyTryExistingChickens() { -+ return level.purpurConfig.zombieJockeyTryExistingChickens; -+ } -+ -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level.purpurConfig.zombieTakeDamageFromWater; -+ } -+ -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level.purpurConfig.zombieAlwaysDropExp; -+ } -+ // Purpur end -+ - @Override - protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur - if (level.paperConfig().entities.behavior.zombiesTargetTurtleEggs) this.goalSelector.addGoal(4, new Zombie.ZombieAttackTurtleEggGoal(this, 1.0D, 3)); // Paper - 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.addBehaviourGoals(); - } - -@@ -120,7 +167,19 @@ public class Zombie extends Monster { - this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D)); - this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[0])).setAlertOthers(ZombifiedPiglin.class)); - this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); -- if ( level.spigotConfig.zombieAggressiveTowardsVillager ) this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, AbstractVillager.class, false)); // Spigot -+ // 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(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR)); - } -@@ -242,30 +301,7 @@ public class Zombie extends Monster { - - @Override - public void aiStep() { -- if (this.isAlive()) { -- boolean flag = this.isSunSensitive() && 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 - implemented in LivingEntity - super.aiStep(); - } - -@@ -303,6 +339,7 @@ public class Zombie extends Monster { - - } - -+ public boolean shouldBurnInDay() { return isSunSensitive(); } // Purpur - for ABI compatibility - public boolean isSunSensitive() { - return this.shouldBurnInDay; // Paper - use api value instead - } -@@ -432,7 +469,7 @@ public class Zombie extends Monster { - nbt.putBoolean("CanBreakDoors", this.canBreakDoors()); - nbt.putInt("InWaterTime", this.isInWater() ? this.inWaterTime : -1); - nbt.putInt("DrownedConversionTime", this.isUnderWaterConverting() ? this.conversionTime : -1); -- nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Paper -+ // nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay); // Paper // Purpur - implemented in LivingEntity - } - - @Override -@@ -446,7 +483,7 @@ public class Zombie extends Monster { - } - // 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 - -@@ -527,19 +564,20 @@ public class Zombie extends Monster { - if (object instanceof Zombie.ZombieGroupData) { - Zombie.ZombieGroupData entityzombie_groupdatazombie = (Zombie.ZombieGroupData) object; - -- if (entityzombie_groupdatazombie.isBaby) { -- this.setBaby(true); -+ // Purpur start -+ if (!jockeyOnlyBaby() || entityzombie_groupdatazombie.isBaby) { -+ this.setBaby(entityzombie_groupdatazombie.isBaby); - if (entityzombie_groupdatazombie.canSpawnJockey) { -- if ((double) randomsource.nextFloat() < 0.05D) { -- List list = world.getEntitiesOfClass(Chicken.class, this.getBoundingBox().inflate(5.0D, 3.0D, 5.0D), EntitySelector.ENTITY_NOT_BEING_RIDDEN); -+ if ((double) randomsource.nextFloat() < jockeyChance()) { -+ List list = jockeyTryExistingChickens() ? world.getEntitiesOfClass(Chicken.class, this.getBoundingBox().inflate(5.0D, 3.0D, 5.0D), EntitySelector.ENTITY_NOT_BEING_RIDDEN) : java.util.Collections.emptyList(); -+ // Purpur end - - if (!list.isEmpty()) { - Chicken entitychicken = (Chicken) list.get(0); - - entitychicken.setChickenJockey(true); - this.startRiding(entitychicken); -- } -- } else if ((double) randomsource.nextFloat() < 0.05D) { -+ } else { // Purpur - Chicken entitychicken1 = (Chicken) EntityType.CHICKEN.create(this.level); - - if (entitychicken1 != null) { -@@ -549,6 +587,7 @@ public class Zombie extends Monster { - this.startRiding(entitychicken1); - world.addFreshEntity(entitychicken1, CreatureSpawnEvent.SpawnReason.MOUNT); // CraftBukkit - } -+ } // Purpur - } - } - } -@@ -595,7 +634,7 @@ public class Zombie extends Monster { - } - - protected void randomizeReinforcementsChance() { -- this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * 0.10000000149011612D); -+ this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level.purpurConfig.zombieSpawnReinforcements); // Purpur - } - - @Override -diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -index 201b0e1b25d0773bbcf9c1ed69fd888a61c6a16f..044d94980841f73e147c8e23c534912c58bd54a9 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -+++ b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -@@ -79,6 +79,58 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { - }); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.zombieVillagerRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level.purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level.purpurConfig.zombieVillagerRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.zombieVillagerControllable; -+ } -+ -+ @Override -+ public void initAttributes() { -+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.zombieVillagerMaxHealth); -+ } -+ -+ @Override -+ protected void randomizeReinforcementsChance() { -+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level.purpurConfig.zombieVillagerSpawnReinforcements); -+ } -+ -+ @Override -+ public boolean jockeyOnlyBaby() { -+ return level.purpurConfig.zombieVillagerJockeyOnlyBaby; -+ } -+ -+ @Override -+ public double jockeyChance() { -+ return level.purpurConfig.zombieVillagerJockeyChance; -+ } -+ -+ @Override -+ public boolean jockeyTryExistingChickens() { -+ return level.purpurConfig.zombieVillagerJockeyTryExistingChickens; -+ } -+ -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level.purpurConfig.zombieVillagerTakeDamageFromWater; -+ } -+ -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level.purpurConfig.zombieVillagerAlwaysDropExp; -+ } -+ // Purpur end -+ - @Override - protected void defineSynchedData() { - super.defineSynchedData(); -@@ -165,13 +217,13 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { - ItemStack itemstack = player.getItemInHand(hand); - - if (itemstack.is(Items.GOLDEN_APPLE)) { -- if (this.hasEffect(MobEffects.WEAKNESS)) { -+ if (this.hasEffect(MobEffects.WEAKNESS) && level.purpurConfig.zombieVillagerCureEnabled) { // Purpur - if (!player.getAbilities().instabuild) { - itemstack.shrink(1); - } - - if (!this.level.isClientSide) { -- this.startConverting(player.getUUID(), this.random.nextInt(2401) + 3600); -+ this.startConverting(player.getUUID(), this.random.nextInt(level.purpurConfig.zombieVillagerCuringTimeMax - level.purpurConfig.zombieVillagerCuringTimeMin + 1) + level.purpurConfig.zombieVillagerCuringTimeMin); // Purpur - } - - return InteractionResult.SUCCESS; -diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java -index b75945807b425609394c343da56c316a769f0a29..d3bcfa017967db0a20c18c65e27c2a0471d2214e 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java -+++ b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java -@@ -63,6 +63,53 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { - this.setPathfindingMalus(BlockPathTypes.LAVA, 8.0F); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.zombifiedPiglinRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level.purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level.purpurConfig.zombifiedPiglinRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.zombifiedPiglinControllable; -+ } -+ -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.zombifiedPiglinMaxHealth); -+ } -+ -+ @Override -+ public boolean jockeyOnlyBaby() { -+ return level.purpurConfig.zombifiedPiglinJockeyOnlyBaby; -+ } -+ -+ @Override -+ public double jockeyChance() { -+ return level.purpurConfig.zombifiedPiglinJockeyChance; -+ } -+ -+ @Override -+ public boolean jockeyTryExistingChickens() { -+ return level.purpurConfig.zombifiedPiglinJockeyTryExistingChickens; -+ } -+ -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level.purpurConfig.zombifiedPiglinTakeDamageFromWater; -+ } -+ -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level.purpurConfig.zombifiedPiglinAlwaysDropExp; -+ } -+ // Purpur end -+ - @Override - public void setPersistentAngerTarget(@Nullable UUID angryAt) { - this.persistentAngerTarget = angryAt; -@@ -115,7 +162,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { - this.maybeAlertOthers(); - } - -- if (this.isAngry()) { -+ if (this.isAngry() && this.level.purpurConfig.zombifiedPiglinCountAsPlayerKillWhenAngry) { // Purpur - this.lastHurtByPlayerTime = this.tickCount; - } - -@@ -170,7 +217,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { - this.ticksUntilNextAlert = ZombifiedPiglin.ALERT_INTERVAL.sample(this.random); - } - -- if (entityliving instanceof Player) { -+ if (entityliving instanceof Player && this.level.purpurConfig.zombifiedPiglinCountAsPlayerKillWhenAngry) { // Purpur - this.setLastHurtByPlayer((Player) entityliving); - } - -@@ -250,7 +297,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { - - @Override - protected void randomizeReinforcementsChance() { -- this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(0.0D); -+ this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * this.level.purpurConfig.zombifiedPiglinSpawnReinforcements); // Purpur - } - - @Nullable -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 2fac1ded7ad16a186dd2c1ebef3ad70715ddffa3..02943d3a7c69984a0ad3fbfa345525e4d6e7d8db 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 -@@ -67,6 +67,43 @@ public class Hoglin extends Animal implements Enemy, HoglinBase { - this.xpReward = 5; - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.hoglinRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level.purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level.purpurConfig.hoglinRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.hoglinControllable; -+ } -+ -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.hoglinMaxHealth); -+ } -+ -+ @Override -+ public int getPurpurBreedTime() { -+ return this.level.purpurConfig.hoglinBreedingTicks; -+ } -+ -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level.purpurConfig.hoglinTakeDamageFromWater; -+ } -+ -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level.purpurConfig.hoglinAlwaysDropExp; -+ } -+ // Purpur end -+ - @Override - public boolean canBeLeashed(Player player) { - return !this.isLeashed(); -@@ -129,7 +166,7 @@ public class Hoglin extends Animal implements Enemy, HoglinBase { - private int behaviorTick; // 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); - 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 fb78f9bff486bf8aba17f02f2378a4852b1f7cca..4306522aba0368afc0ab104ca4fe8327f73135f7 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 -@@ -97,6 +97,38 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento - this.xpReward = 5; - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.piglinRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level.purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level.purpurConfig.piglinRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.piglinControllable; -+ } -+ -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.piglinMaxHealth); -+ } -+ -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level.purpurConfig.piglinTakeDamageFromWater; -+ } -+ -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level.purpurConfig.piglinAlwaysDropExp; -+ } -+ // Purpur end -+ - @Override - public void addAdditionalSaveData(CompoundTag nbt) { - super.addAdditionalSaveData(nbt); -@@ -311,7 +343,7 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento - private int behaviorTick; // 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); - PiglinAi.updateActivity(this); - super.customServerAiStep(); -@@ -408,7 +440,7 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento - - @Override - public boolean wantsToPickUp(ItemStack stack) { -- return this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) && this.canPickUpLoot() && PiglinAi.wantsToPickup(this, stack); -+ return (this.level.purpurConfig.piglinBypassMobGriefing || this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && this.canPickUpLoot() && PiglinAi.wantsToPickup(this, stack); - } - - protected boolean canReplaceCurrentItem(ItemStack stack) { -diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java -index edee6a47e67a9e02ac269dcc16b1932cedb4d8aa..5bec0a5f1ff40471d9811852a8a6347f08f2e764 100644 ---- a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java -+++ b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinBrute.java -@@ -41,6 +41,38 @@ public class PiglinBrute extends AbstractPiglin { - this.xpReward = 20; - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.piglinBruteRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level.purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level.purpurConfig.piglinBruteRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.piglinBruteControllable; -+ } -+ -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.piglinBruteMaxHealth); -+ } -+ -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level.purpurConfig.piglinBruteTakeDamageFromWater; -+ } -+ -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level.purpurConfig.piglinBruteAlwaysDropExp; -+ } -+ // Purpur end -+ - public static AttributeSupplier.Builder createAttributes() { - return Monster.createMonsterAttributes().add(Attributes.MAX_HEALTH, 50.0D).add(Attributes.MOVEMENT_SPEED, (double)0.35F).add(Attributes.ATTACK_DAMAGE, 7.0D); - } -@@ -85,6 +117,7 @@ public class PiglinBrute extends AbstractPiglin { - - @Override - protected void customServerAiStep() { -+ if (getRider() == null || this.isControllable()) // Purpur - only use brain if no rider - this.getBrain().tick((ServerLevel)this.level, this); - PiglinBruteAi.updateActivity(this); - PiglinBruteAi.maybePlayActivitySound(this); -diff --git a/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java b/src/main/java/net/minecraft/world/entity/monster/warden/Warden.java -index d33a240b9dbd3cebe179096407c3da69f2df884a..3095780e9538bf3a6fefaf8db5df4033596aebbc 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 -@@ -120,8 +120,32 @@ public class Warden extends Monster implements VibrationListener.VibrationListen - this.setPathfindingMalus(BlockPathTypes.LAVA, 8.0F); - this.setPathfindingMalus(BlockPathTypes.DAMAGE_FIRE, 0.0F); - this.setPathfindingMalus(BlockPathTypes.DANGER_FIRE, 0.0F); -+ this.moveControl = new org.purpurmc.purpur.controller.MoveControllerWASD(this, 0.5F); // Purpur - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.wardenRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level.purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level.purpurConfig.wardenRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.wardenControllable; -+ } -+ -+ @Override -+ protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur -+ this.targetSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); // Purpur -+ } -+ // Purpur end -+ - @Override - public Packet getAddEntityPacket() { - return new ClientboundAddEntityPacket(this, this.hasPose(Pose.EMERGING) ? 1 : 0); -@@ -403,19 +427,16 @@ public class Warden extends Monster implements VibrationListener.VibrationListen - - @Contract("null->false") - public boolean canTargetEntity(@Nullable Entity entity) { -- boolean flag; -- -+ if (getRider() != null && isControllable()) return false; // Purpur - if (entity instanceof LivingEntity) { - LivingEntity entityliving = (LivingEntity) entity; - - if (this.level == entity.level && EntitySelector.NO_CREATIVE_OR_SPECTATOR.test(entity) && !this.isAlliedTo(entity) && entityliving.getType() != EntityType.ARMOR_STAND && entityliving.getType() != EntityType.WARDEN && !entityliving.isInvulnerable() && !entityliving.isDeadOrDying() && this.level.getWorldBorder().isWithinBounds(entityliving.getBoundingBox())) { -- flag = true; -- return flag; -+ return true; // Purpur - wtf - } - } - -- flag = false; -- return flag; -+ return false; // Purpur - wtf - } - - public static void applyDarknessAround(ServerLevel world, Vec3 pos, @Nullable Entity entity, int range) { -diff --git a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -index ca96b893e22de3ae7c11d5cded51edf70bdcb6f2..d4ab27de59e9c533789f062e74ceb453483e2e39 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -+++ b/src/main/java/net/minecraft/world/entity/npc/AbstractVillager.java -@@ -43,6 +43,7 @@ import org.bukkit.event.entity.VillagerAcquireTradeEvent; - // CraftBukkit end - - public abstract class AbstractVillager extends AgeableMob implements InventoryCarrier, Npc, Merchant { -+ static final net.minecraft.world.item.crafting.Ingredient TEMPT_ITEMS = net.minecraft.world.item.crafting.Ingredient.of(net.minecraft.world.level.block.Blocks.EMERALD_BLOCK.asItem()); // Purpur - - // CraftBukkit start - private CraftMerchant craftMerchant; -diff --git a/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java b/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java -index 5f407535298a31a34cfe114dd863fd6a9b977707..29c7e33fe961020e5a0007287fe9b6631689f1b8 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java -+++ b/src/main/java/net/minecraft/world/entity/npc/CatSpawner.java -@@ -30,7 +30,7 @@ public class CatSpawner implements CustomSpawner { - if (this.nextTick > 0) { - return 0; - } else { -- this.nextTick = 1200; -+ this.nextTick = world.purpurConfig.catSpawnDelay; // Purpur - Player player = world.getRandomPlayer(); - if (player == null) { - return 0; -@@ -63,11 +63,15 @@ public class CatSpawner implements CustomSpawner { - } - - private int spawnInVillage(ServerLevel world, BlockPos pos) { -- int i = 48; -+ // Purpur start -+ int range = world.purpurConfig.catSpawnVillageScanRange; -+ if (range <= 0) return 0; -+ - if (world.getPoiManager().getCountInRange((entry) -> { - return entry.is(PoiTypes.HOME); -- }, pos, 48, PoiManager.Occupancy.IS_OCCUPIED) > 4L) { -- List list = world.getEntitiesOfClass(Cat.class, (new AABB(pos)).inflate(48.0D, 8.0D, 48.0D)); -+ }, pos, range, PoiManager.Occupancy.IS_OCCUPIED) > 4L) { -+ List list = world.getEntitiesOfClass(Cat.class, (new AABB(pos)).inflate(range, 8.0D, range)); -+ // Purpur end - if (list.size() < 5) { - return this.spawnCat(pos, world); - } -@@ -77,8 +81,11 @@ public class CatSpawner implements CustomSpawner { - } - - private int spawnInHut(ServerLevel world, BlockPos pos) { -- int i = 16; -- List list = world.getEntitiesOfClass(Cat.class, (new AABB(pos)).inflate(16.0D, 8.0D, 16.0D)); -+ // Purpur start -+ int range = world.purpurConfig.catSpawnSwampHutScanRange; -+ if (range <= 0) return 0; -+ List list = world.getEntitiesOfClass(Cat.class, (new AABB(pos)).inflate(range, 8.0D, range)); -+ // Purpur end - return list.size() < 1 ? this.spawnCat(pos, world) : 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 474de6f93631c07c75aa78ad95b71a4f89b0dcc5..1c309d51570a63264f0a86b385f0604c8eddeced 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/Villager.java -+++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java -@@ -141,6 +141,8 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - }, MemoryModuleType.MEETING_POINT, (entityvillager, holder) -> { - return holder.is(PoiTypes.MEETING); - }); -+ private boolean isLobotomized = false; public boolean isLobotomized() { return this.isLobotomized; } // Purpur -+ private int notLobotomizedCount = 0; // Purpur - - public long nextGolemPanic = -1; // Pufferfish - -@@ -157,6 +159,90 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - this.setVillagerData(this.getVillagerData().setType(type).setProfession(VillagerProfession.NONE)); - } - -+ // Purpur start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.villagerRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level.purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level.purpurConfig.villagerRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.villagerControllable; -+ } -+ -+ @Override -+ protected void registerGoals() { -+ this.goalSelector.addGoal(0, new org.purpurmc.purpur.entity.ai.HasRider(this)); -+ if (level.purpurConfig.villagerFollowEmeraldBlock) this.goalSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.TemptGoal(this, 1.0D, TEMPT_ITEMS, false)); -+ } -+ -+ @Override -+ public void initAttributes() { -+ this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.villagerMaxHealth); -+ } -+ -+ @Override -+ public boolean canBeLeashed(Player player) { -+ return level.purpurConfig.villagerCanBeLeashed && !this.isLeashed(); -+ } -+ -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level.purpurConfig.villagerTakeDamageFromWater; -+ } -+ -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level.purpurConfig.villagerAlwaysDropExp; -+ } -+ -+ private boolean checkLobotomized() { -+ int interval = this.level.purpurConfig.villagerLobotomizeCheckInterval; -+ if (this.notLobotomizedCount > 3) { -+ // check half as often if not lobotomized for the last 3+ consecutive checks -+ interval *= 2; -+ } -+ if (this.level.getGameTime() % interval == 0) { -+ // offset Y for short blocks like dirt_path/farmland -+ this.isLobotomized = !canTravelFrom(BlockPos.containing(this.position().x, this.getBoundingBox().minY + 0.0625D, this.position().z)); -+ -+ if (this.isLobotomized) { -+ this.notLobotomizedCount = 0; -+ } else { -+ this.notLobotomizedCount++; -+ } -+ } -+ return this.isLobotomized; -+ } -+ -+ private boolean canTravelFrom(BlockPos pos) { -+ return canTravelTo(pos.east()) || canTravelTo(pos.west()) || canTravelTo(pos.north()) || canTravelTo(pos.south()); -+ } -+ -+ private boolean canTravelTo(BlockPos pos) { -+ net.minecraft.world.level.block.state.BlockState state = this.level.getBlockStateIfLoaded(pos); -+ if (state == null) { -+ // chunk not loaded -+ return false; -+ } -+ net.minecraft.world.level.block.Block bottom = state.getBlock(); -+ if (bottom instanceof net.minecraft.world.level.block.FenceBlock || -+ bottom instanceof net.minecraft.world.level.block.FenceGateBlock || -+ bottom instanceof net.minecraft.world.level.block.WallBlock) { -+ // bottom block is too tall to get over -+ return false; -+ } -+ net.minecraft.world.level.block.Block top = level.getBlockState(pos.above()).getBlock(); -+ // only if both blocks have no collision -+ return !bottom.hasCollision && !top.hasCollision; -+ } -+ // Purpur end -+ - @Override - public Brain getBrain() { - return (Brain) super.getBrain(); // CraftBukkit - decompile error -@@ -191,7 +277,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - brain.addActivity(Activity.PLAY, VillagerGoalPackages.getPlayPackage(0.5F)); - } else { - brain.setSchedule(Schedule.VILLAGER_DEFAULT); -- brain.addActivityWithConditions(Activity.WORK, VillagerGoalPackages.getWorkPackage(villagerprofession, 0.5F), ImmutableSet.of(Pair.of(MemoryModuleType.JOB_SITE, MemoryStatus.VALUE_PRESENT))); -+ brain.addActivityWithConditions(Activity.WORK, VillagerGoalPackages.getWorkPackage(villagerprofession, 0.5F, this.level.purpurConfig.villagerClericsFarmWarts), ImmutableSet.of(Pair.of(MemoryModuleType.JOB_SITE, MemoryStatus.VALUE_PRESENT))); // Purpur - } - - brain.addActivity(Activity.CORE, VillagerGoalPackages.getCorePackage(villagerprofession, 0.5F)); -@@ -251,13 +337,28 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - @Override - protected void customServerAiStep() { mobTick(false); } - protected void mobTick(boolean inactive) { -+ // Purpur start -+ if (this.level.purpurConfig.villagerLobotomizeEnabled) { -+ // treat as inactive if lobotomized -+ inactive = inactive || checkLobotomized(); -+ } else { -+ // clean up state for API -+ this.isLobotomized = false; -+ } -+ // Purpur end - if (!inactive) this.getBrain().tick((ServerLevel) this.level, this); // Paper - // Pufferfish start - if (!inactive) { -- 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); // Paper - } - // Pufferfish end -+ // Purpur start -+ else if (this.isLobotomized && shouldRestock()) { -+ // make sure we restock if needed when lobotomized -+ restock(); -+ } -+ // Purpur end - if (this.assignProfessionWhenSpawned) { - this.assignProfessionWhenSpawned = false; - } -@@ -313,7 +414,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - if (!itemstack.is(Items.VILLAGER_SPAWN_EGG) && this.isAlive() && !this.isTrading() && !this.isSleeping()) { - if (this.isBaby()) { - this.setUnhappy(); -- return InteractionResult.sidedSuccess(this.level.isClientSide); -+ return tryRide(player, hand, InteractionResult.sidedSuccess(this.level.isClientSide)); // Purpur - } else { - boolean flag = this.getOffers().isEmpty(); - -@@ -326,9 +427,10 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - } - - if (flag) { -- return InteractionResult.sidedSuccess(this.level.isClientSide); -+ return tryRide(player, hand, InteractionResult.sidedSuccess(this.level.isClientSide)); // Purpur - } else { -- if (!this.level.isClientSide && !this.offers.isEmpty()) { -+ if (level.purpurConfig.villagerRidable && itemstack.isEmpty()) return tryRide(player, hand); // Purpur -+ if (this.level.purpurConfig.villagerAllowTrading && !this.offers.isEmpty()) { // Purpur - this.startTrading(player); - } - -@@ -497,7 +599,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - while (iterator.hasNext()) { - MerchantOffer merchantrecipe = (MerchantOffer) iterator.next(); - -- merchantrecipe.updateDemand(); -+ merchantrecipe.updateDemand(this.level.purpurConfig.villagerMinimumDemand); // Purpur - } - - } -@@ -747,7 +849,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - - @Override - public boolean canBreed() { -- return this.foodLevel + this.countFoodPointsInInventory() >= 12 && !this.isSleeping() && this.getAge() == 0; -+ return this.level.purpurConfig.villagerCanBreed && this.foodLevel + this.countFoodPointsInInventory() >= 12 && !this.isSleeping() && this.getAge() == 0; // Purpur - } - - private boolean hungry() { -@@ -960,6 +1062,11 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - } - - public boolean hasFarmSeeds() { -+ // Purpur start -+ if (this.level.purpurConfig.villagerClericsFarmWarts && this.getVillagerData().getProfession() == VillagerProfession.CLERIC) { -+ return this.getInventory().hasAnyOf(ImmutableSet.of(Items.NETHER_WART)); -+ } -+ // Purpur end - return this.getInventory().hasAnyOf(ImmutableSet.of(Items.WHEAT_SEEDS, Items.POTATO, Items.CARROT, Items.BEETROOT_SEEDS)); - } - -@@ -1008,6 +1115,7 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - } - - public void spawnGolemIfNeeded(ServerLevel world, long time, int requiredCount) { -+ if (world.purpurConfig.villagerSpawnIronGolemRadius > 0 && world.getEntitiesOfClass(net.minecraft.world.entity.animal.IronGolem.class, getBoundingBox().inflate(world.purpurConfig.villagerSpawnIronGolemRadius)).size() > world.purpurConfig.villagerSpawnIronGolemLimit) return; // Purpur - if (this.wantsToSpawnGolem(time)) { - AABB axisalignedbb = this.getBoundingBox().inflate(10.0D, 10.0D, 10.0D); - List list = world.getEntitiesOfClass(Villager.class, axisalignedbb); -@@ -1081,6 +1189,12 @@ public class Villager extends AbstractVillager implements ReputationEventHandler - - @Override - public void startSleeping(BlockPos pos) { -+ // Purpur start -+ if (level.purpurConfig.bedExplodeOnVillagerSleep && this.level.getBlockState(pos).getBlock() instanceof net.minecraft.world.level.block.BedBlock) { -+ this.level.explode(null, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, (float) this.level.purpurConfig.bedExplosionPower, this.level.purpurConfig.bedExplosionFire, this.level.purpurConfig.bedExplosionEffect); -+ return; -+ } -+ // Purpur end - super.startSleeping(pos); - this.brain.setMemory(MemoryModuleType.LAST_SLEPT, this.level.getGameTime()); // CraftBukkit - decompile error - this.brain.eraseMemory(MemoryModuleType.WALK_TARGET); -diff --git a/src/main/java/net/minecraft/world/entity/npc/VillagerProfession.java b/src/main/java/net/minecraft/world/entity/npc/VillagerProfession.java -index 95197b601d93c30a7645d67c89c7608fc00a8de6..3ccfac9e8ae958f3f94fe774120716e5b6293ce7 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/VillagerProfession.java -+++ b/src/main/java/net/minecraft/world/entity/npc/VillagerProfession.java -@@ -1,6 +1,8 @@ - package net.minecraft.world.entity.npc; - - import java.util.function.Predicate; -+ -+import com.google.common.collect.ImmutableSet; - import net.minecraft.core.Holder; - import net.minecraft.core.Registry; - import net.minecraft.core.registries.BuiltInRegistries; -@@ -26,7 +28,7 @@ public record VillagerProfession(String name, Predicate> heldJob - public static final VillagerProfession ARMORER = register("armorer", PoiTypes.ARMORER, SoundEvents.VILLAGER_WORK_ARMORER); - public static final VillagerProfession BUTCHER = register("butcher", PoiTypes.BUTCHER, SoundEvents.VILLAGER_WORK_BUTCHER); - public static final VillagerProfession CARTOGRAPHER = register("cartographer", PoiTypes.CARTOGRAPHER, SoundEvents.VILLAGER_WORK_CARTOGRAPHER); -- public static final VillagerProfession CLERIC = register("cleric", PoiTypes.CLERIC, SoundEvents.VILLAGER_WORK_CLERIC); -+ public static final VillagerProfession CLERIC = register("cleric", PoiTypes.CLERIC, ImmutableSet.of(Items.NETHER_WART).toArray(new Item[0]), Blocks.SOUL_SAND, SoundEvents.VILLAGER_WORK_CLERIC); // Purpur - public static final VillagerProfession FARMER = register("farmer", PoiTypes.FARMER, new Item[] {Items.WHEAT, Items.WHEAT_SEEDS, Items.BEETROOT_SEEDS, Items.BONE_MEAL}, Blocks.FARMLAND, SoundEvents.VILLAGER_WORK_FARMER); // Gale - optimize villager data storage - public static final VillagerProfession FISHERMAN = register("fisherman", PoiTypes.FISHERMAN, SoundEvents.VILLAGER_WORK_FISHERMAN); - public static final VillagerProfession FLETCHER = register("fletcher", PoiTypes.FLETCHER, SoundEvents.VILLAGER_WORK_FLETCHER); -diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java -index c9fb50c33ac15fe72bc77167e4647f30942fdc5d..a6a4d5203cb5f35306f8225e56681bc25e06beed 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java -+++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java -@@ -69,6 +69,43 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill - this.setDespawnDelay(48000); // CraftBukkit - set default from MobSpawnerTrader - } - -+ // Purpur - start -+ @Override -+ public boolean isRidable() { -+ return level.purpurConfig.wanderingTraderRidable; -+ } -+ -+ @Override -+ public boolean dismountsUnderwater() { -+ return level.purpurConfig.useDismountsUnderwaterTag ? super.dismountsUnderwater() : !level.purpurConfig.wanderingTraderRidableInWater; -+ } -+ -+ @Override -+ public boolean isControllable() { -+ return level.purpurConfig.wanderingTraderControllable; -+ } -+ -+ @Override -+ public void initAttributes() { -+ this.getAttribute(net.minecraft.world.entity.ai.attributes.Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.wanderingTraderMaxHealth); -+ } -+ -+ @Override -+ public boolean canBeLeashed(Player player) { -+ return level.purpurConfig.wanderingTraderCanBeLeashed && !this.isLeashed(); -+ } -+ -+ @Override -+ public boolean isSensitiveToWater() { -+ return this.level.purpurConfig.wanderingTraderTakeDamageFromWater; -+ } -+ -+ @Override -+ protected boolean isAlwaysExperienceDropper() { -+ return this.level.purpurConfig.wanderingTraderAlwaysDropExp; -+ } -+ // Purpur end -+ - @Override - protected void registerGoals() { - this.goalSelector.addGoal(0, new FloatGoal(this)); -@@ -76,7 +113,7 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill - return this.canDrinkPotion && this.level.isNight() && !entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API - })); - this.goalSelector.addGoal(0, new UseItemGoal<>(this, new ItemStack(Items.MILK_BUCKET), SoundEvents.WANDERING_TRADER_REAPPEARED, (entityvillagertrader) -> { -- return canDrinkMilk && this.level.isDay() && entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API -+ return level.purpurConfig.milkClearsBeneficialEffects && canDrinkMilk && this.level.isDay() && entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API // Purpur - })); - this.goalSelector.addGoal(1, new TradeWithPlayerGoal(this)); - this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Zombie.class, 8.0F, 0.5D, 0.5D)); -@@ -89,6 +126,7 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill - this.goalSelector.addGoal(1, new PanicGoal(this, 0.5D)); - this.goalSelector.addGoal(1, new LookAtTradingPlayerGoal(this)); - this.goalSelector.addGoal(2, new WanderingTrader.WanderToPositionGoal(this, 2.0D, 0.35D)); -+ if (level.purpurConfig.wanderingTraderFollowEmeraldBlock) this.goalSelector.addGoal(3, new net.minecraft.world.entity.ai.goal.TemptGoal(this, 1.0D, TEMPT_ITEMS, false)); // Purpur - this.goalSelector.addGoal(4, new MoveTowardsRestrictionGoal(this, 0.35D)); - this.goalSelector.addGoal(8, new WaterAvoidingRandomStrollGoal(this, 0.35D)); - this.goalSelector.addGoal(9, new InteractGoal(this, Player.class, 3.0F, 1.0F)); -@@ -116,9 +154,10 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill - } - - if (this.getOffers().isEmpty()) { -- return InteractionResult.sidedSuccess(this.level.isClientSide); -+ return tryRide(player, hand, InteractionResult.sidedSuccess(this.level.isClientSide)); // Purpur - } else { -- if (!this.level.isClientSide) { -+ if (level.purpurConfig.wanderingTraderRidable && itemstack.isEmpty()) return tryRide(player, hand); // Purpur -+ if (this.level.purpurConfig.wanderingTraderAllowTrading) { // Purpur - this.setTradingPlayer(player); - this.openTradingScreen(player, this.getDisplayName(), 1); - } -diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java -index 5d199fe497bd852827d3d18fb7566a09e70331a3..6cd8a50289a6404441e9e5e08d82d2ebe14a09cc 100644 ---- a/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java -+++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTraderSpawner.java -@@ -159,7 +159,17 @@ public class WanderingTraderSpawner implements CustomSpawner { - int k = pos.getX() + this.random.nextInt(range * 2) - range; - int l = pos.getZ() + this.random.nextInt(range * 2) - range; - int i1 = world.getHeight(Heightmap.Types.WORLD_SURFACE, k, l); -- BlockPos blockposition2 = new BlockPos(k, i1, l); -+ // Purpur start - allow traders to spawn below nether roof -+ BlockPos.MutableBlockPos blockposition2 = new BlockPos.MutableBlockPos(k, i1, l); -+ if (world.dimensionType().hasCeiling()) { -+ do { -+ blockposition2.relative(net.minecraft.core.Direction.DOWN); -+ } while (!world.getBlockState(blockposition2).isAir()); -+ do { -+ blockposition2.relative(net.minecraft.core.Direction.DOWN); -+ } while (world.getBlockState(blockposition2).isAir() && blockposition2.getY() > 0); -+ } -+ // Purpur end - - if (NaturalSpawner.isSpawnPositionOk(SpawnPlacements.Type.ON_GROUND, world, blockposition2, EntityType.WANDERING_TRADER)) { - blockposition1 = blockposition2; -diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index 271833821eb1c5f6975a9742ffc2deba01fa25da..992e464a878588b80de8b1a21f4ae880694c15a6 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -188,6 +188,8 @@ public abstract class Player extends LivingEntity { - public boolean affectsSpawning = true; - public net.kyori.adventure.util.TriState flyingFallDamage = net.kyori.adventure.util.TriState.NOT_SET; - // Paper end -+ public int sixRowEnderchestSlotCount = -1; // Purpur -+ public boolean canPortalInstant = false; // Purpur - - // CraftBukkit start - public boolean fauxSleeping; -@@ -201,6 +203,28 @@ public abstract class Player extends LivingEntity { - - public final int sendAllPlayerInfoBucketIndex; // Gale - Purpur - spread out sending all player info - -+ // Purpur start -+ public int burpDelay = 0; -+ -+ public abstract void resetLastActionTime(); -+ -+ public void setAfk(boolean afk) { -+ } -+ -+ public boolean isAfk() { -+ return false; -+ } -+ -+ @Override -+ public boolean processClick(InteractionHand hand) { -+ Entity vehicle = getRootVehicle(); -+ if (vehicle != null && vehicle.getRider() == this) { -+ return vehicle.onClick(hand); -+ } -+ return false; -+ } -+ // Purpur end -+ - public Player(Level world, BlockPos pos, float yaw, GameProfile gameProfile) { - super(EntityType.PLAYER, world); - this.lastItemInMainHand = ItemStack.EMPTY; -@@ -246,6 +270,12 @@ public abstract class Player extends LivingEntity { - - @Override - public void tick() { -+ // Purpur start -+ if (this.burpDelay > 0 && --this.burpDelay == 0) { -+ this.level.playSound(null, getX(), getY(), getZ(), SoundEvents.PLAYER_BURP, SoundSource.PLAYERS, 1.0F, this.level.random.nextFloat() * 0.1F + 0.9F); -+ } -+ // Purpur end -+ - this.noPhysics = this.isSpectator(); - if (this.isSpectator()) { - this.onGround = false; -@@ -354,6 +384,16 @@ public abstract class Player extends LivingEntity { - this.addEffect(new MobEffectInstance(MobEffects.WATER_BREATHING, 200, 0, false, false, true), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.TURTLE_HELMET); // CraftBukkit - } - -+ // Purpur start -+ if (this.level.purpurConfig.playerNetheriteFireResistanceDuration > 0 && this.level.getGameTime() % 20 == 0) { -+ if (itemstack.is(Items.NETHERITE_HELMET) -+ && this.getItemBySlot(EquipmentSlot.CHEST).is(Items.NETHERITE_CHESTPLATE) -+ && this.getItemBySlot(EquipmentSlot.LEGS).is(Items.NETHERITE_LEGGINGS) -+ && this.getItemBySlot(EquipmentSlot.FEET).is(Items.NETHERITE_BOOTS)) { -+ this.addEffect(new MobEffectInstance(MobEffects.FIRE_RESISTANCE, this.level.purpurConfig.playerNetheriteFireResistanceDuration, this.level.purpurConfig.playerNetheriteFireResistanceAmplifier, this.level.purpurConfig.playerNetheriteFireResistanceAmbient, this.level.purpurConfig.playerNetheriteFireResistanceShowParticles, this.level.purpurConfig.playerNetheriteFireResistanceShowIcon), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.NETHERITE_ARMOR); -+ } -+ } -+ // Purpur end - } - - protected ItemCooldowns createItemCooldowns() { -@@ -440,7 +480,7 @@ public abstract class Player extends LivingEntity { - - @Override - public int getPortalWaitTime() { -- return this.abilities.invulnerable ? 1 : 80; -+ return canPortalInstant ? 1 : this.abilities.invulnerable ? this.level.purpurConfig.playerCreativePortalWaitTime : this.level.purpurConfig.playerPortalWaitTime; // Purpur - } - - @Override -@@ -600,7 +640,7 @@ public abstract class Player extends LivingEntity { - for (int i = 0; i < list.size(); ++i) { - Entity entity = (Entity) list.get(i); - -- if (entity.getType() == EntityType.EXPERIENCE_ORB) { -+ if (entity.getType() == EntityType.EXPERIENCE_ORB && entity.level.purpurConfig.playerExpPickupDelay >= 0) { // Purpur - list1.add(entity); - } else if (!entity.isRemoved()) { - this.touch(entity); -@@ -1291,7 +1331,7 @@ public abstract class Player extends LivingEntity { - flag2 = flag2 && !level.paperConfig().entities.behavior.disablePlayerCrits; // Paper - flag2 = flag2 && !this.isSprinting(); - if (flag2) { -- f *= 1.5F; -+ f *= this.level.purpurConfig.playerCriticalDamageMultiplier; // Purpur - } - - f += f1; -@@ -1965,9 +2005,19 @@ public abstract class Player extends LivingEntity { - @Override - public int getExperienceReward() { - if (!this.level.getGameRules().getBoolean(GameRules.RULE_KEEPINVENTORY) && !this.isSpectator()) { -- int i = this.experienceLevel * 7; -- -- return i > 100 ? 100 : i; -+ // Purpur start -+ int toDrop; -+ try { -+ toDrop = Math.round(((Number) scriptEngine.eval("let expLevel = " + experienceLevel + "; " + -+ "let expTotal = " + totalExperience + "; " + -+ "let exp = " + experienceProgress + "; " + -+ level.purpurConfig.playerDeathExpDropEquation)).floatValue()); -+ } catch (javax.script.ScriptException e) { -+ e.printStackTrace(); -+ toDrop = experienceLevel * 7; -+ } -+ return Math.min(toDrop, level.purpurConfig.playerDeathExpDropMax); -+ // Purpur end - } else { - return 0; - } -@@ -2043,6 +2093,11 @@ public abstract class Player extends LivingEntity { - return this.inventory.armor; - } - -+ @Override -+ public boolean dismountsUnderwater() { -+ return !level.purpurConfig.playerRidableInWater; -+ } -+ - public boolean setEntityOnShoulder(CompoundTag entityNbt) { - if (!this.isPassenger() && this.onGround && !this.isInWater() && !this.isInPowderSnow) { - if (this.getShoulderEntityLeft().isEmpty()) { -@@ -2326,7 +2381,7 @@ public abstract class Player extends LivingEntity { - public ItemStack eat(Level world, ItemStack stack) { - this.getFoodData().eat(stack.getItem(), stack); - this.awardStat(Stats.ITEM_USED.get(stack.getItem())); -- world.playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_BURP, SoundSource.PLAYERS, 0.5F, world.random.nextFloat() * 0.1F + 0.9F); -+ // world.playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.PLAYER_BURP, SoundSource.PLAYERS, 0.5F, world.random.nextFloat() * 0.1F + 0.9F); // Purpur - moved to tick() - if (this instanceof ServerPlayer) { - CriteriaTriggers.CONSUME_ITEM.trigger((ServerPlayer) this, stack); - } -diff --git a/src/main/java/net/minecraft/world/entity/player/StackedContents.java b/src/main/java/net/minecraft/world/entity/player/StackedContents.java -index 574ebb3a2fcd0e4e426a8a7ee88d722ed3b9c3f5..842b921799111789b37a34b76644c9217bc85794 100644 ---- a/src/main/java/net/minecraft/world/entity/player/StackedContents.java -+++ b/src/main/java/net/minecraft/world/entity/player/StackedContents.java -@@ -37,8 +37,62 @@ public class StackedContents { - int i = getStackingIndex(stack); - int j = Math.min(maxCount, stack.getCount()); - this.put(i, j); -+ // PaperPR start -+ if (stack.hasTag()) { -+ this.put(getExactStackingIndex(stack), j); -+ } -+ } -+ -+ } -+ private static final net.minecraft.core.IdMap EXACT_MATCHES_ID_MAP = new net.minecraft.core.IdMap<>() { -+ private final java.util.concurrent.atomic.AtomicInteger idCounter = new java.util.concurrent.atomic.AtomicInteger(BuiltInRegistries.ITEM.size()); -+ private final it.unimi.dsi.fastutil.objects.Object2IntMap itemstackToId = new it.unimi.dsi.fastutil.objects.Object2IntOpenCustomHashMap<>(new it.unimi.dsi.fastutil.Hash.Strategy<>() { -+ @Override -+ public int hashCode(ItemStack o) { -+ return java.util.Objects.hash(o.getItem(), o.getTag()); -+ } -+ -+ @Override -+ public boolean equals(@Nullable ItemStack a, @Nullable ItemStack b) { -+ if (a == null || b == null) { -+ return false; -+ } -+ return ItemStack.tagMatches(a, b); -+ } -+ }); -+ private final it.unimi.dsi.fastutil.ints.Int2ObjectMap idToItemstack = new it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap<>(); -+ -+ @Override -+ public int getId(ItemStack value) { -+ if (!this.itemstackToId.containsKey(value)) { -+ final int id = this.idCounter.incrementAndGet(); -+ final ItemStack copy = value.copy(); -+ this.itemstackToId.put(copy, id); -+ this.idToItemstack.put(id, copy); -+ return id; -+ } -+ return this.itemstackToId.getInt(value); -+ } -+ -+ @Override -+ public @Nullable ItemStack byId(int index) { -+ return this.idToItemstack.get(index); -+ } -+ -+ @Override -+ public int size() { -+ return this.itemstackToId.size(); -+ } -+ -+ @Override -+ public java.util.Iterator iterator() { -+ return this.idToItemstack.values().iterator(); - } -+ }; - -+ public static int getExactStackingIndex(ItemStack stack) { -+ return EXACT_MATCHES_ID_MAP.getId(stack); -+ // PaperPR end - } - - public static int getStackingIndex(ItemStack stack) { -@@ -80,6 +134,12 @@ public class StackedContents { - } - - public static ItemStack fromStackingIndex(int itemId) { -+ // PaperPR start -+ if (itemId > BuiltInRegistries.ITEM.size()) { -+ final ItemStack stack = EXACT_MATCHES_ID_MAP.byId(itemId); -+ return stack == null ? ItemStack.EMPTY : stack.copy(); -+ } -+ // PaperPR end - return itemId == 0 ? ItemStack.EMPTY : new ItemStack(Item.byId(itemId)); - } - -diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -index e190f414ab5af7326d92af3fddf3f7a2c3358fa0..e1a81ec7b925e4ae5f79a06ecde86e7f4f6f7052 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java -@@ -72,6 +72,7 @@ public abstract class AbstractArrow extends Projectile { - private IntOpenHashSet piercingIgnoreEntityIds; - @Nullable - private List piercedAndKilledEntities; -+ public int lootingLevel; // Purpur - - // Spigot Start - @Override -@@ -612,6 +613,12 @@ public abstract class AbstractArrow extends Projectile { - this.knockback = punch; - } - -+ // Purpur start -+ public void setLootingLevel(int looting) { -+ this.lootingLevel = looting; -+ } -+ // Purpur end -+ - public int getKnockback() { - return this.knockback; - } -diff --git a/src/main/java/net/minecraft/world/entity/projectile/LargeFireball.java b/src/main/java/net/minecraft/world/entity/projectile/LargeFireball.java -index 4daa368881e4fa59a9365d7b3810ae7dc1455fa3..a4041580061b2acd150836a1437df66ebb4a62cb 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/LargeFireball.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/LargeFireball.java -@@ -16,20 +16,20 @@ public class LargeFireball extends Fireball { - - public LargeFireball(EntityType type, Level world) { - super(type, world); -- isIncendiary = this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit -+ isIncendiary = this.level.purpurConfig.fireballsBypassMobGriefing || this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit // Purpur - } - - public LargeFireball(Level world, LivingEntity owner, double velocityX, double velocityY, double velocityZ, int explosionPower) { - super(EntityType.FIREBALL, owner, velocityX, velocityY, velocityZ, world); - this.explosionPower = explosionPower; -- isIncendiary = this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit -+ isIncendiary = this.level.purpurConfig.fireballsBypassMobGriefing || this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // CraftBukkit // Purpur - } - - @Override - protected void onHit(HitResult hitResult) { - super.onHit(hitResult); - if (!this.level.isClientSide) { -- boolean flag = this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); -+ boolean flag = this.level.purpurConfig.fireballsBypassMobGriefing || this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // Purpur - - // CraftBukkit start - fire ExplosionPrimeEvent - ExplosionPrimeEvent event = new ExplosionPrimeEvent((org.bukkit.entity.Explosive) this.getBukkitEntity()); -diff --git a/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java b/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java -index c4f4a26e016eea744f587461af80461074d48303..10b109de5abc015b61a896d363ad37a006dff554 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/LlamaSpit.java -@@ -26,6 +26,12 @@ public class LlamaSpit extends Projectile { - this.setPos(owner.getX() - (double) (owner.getBbWidth() + 1.0F) * 0.5D * (double) Mth.sin(owner.yBodyRot * 0.017453292F), owner.getEyeY() - 0.10000000149011612D, owner.getZ() + (double) (owner.getBbWidth() + 1.0F) * 0.5D * (double) Mth.cos(owner.yBodyRot * 0.017453292F)); - } - -+ // Purpur start -+ public void super_tick() { -+ super.tick(); -+ } -+ // Purpur end -+ - @Override - public void tick() { - super.tick(); -diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -index 87ea4fb3f0e5e3147a1e832b03972b7455adcf44..59fda194e515e2285529411f2884f47d58a74f46 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -@@ -326,6 +326,6 @@ public abstract class Projectile extends Entity implements TraceableEntity { - public boolean mayInteract(Level world, BlockPos pos) { - Entity entity = this.getOwner(); - -- return entity instanceof Player ? entity.mayInteract(world, pos) : entity == null || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); -+ return entity instanceof Player ? entity.mayInteract(world, pos) : entity == null || world.purpurConfig.projectilesBypassMobGriefing || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); - } - } -diff --git a/src/main/java/net/minecraft/world/entity/projectile/SmallFireball.java b/src/main/java/net/minecraft/world/entity/projectile/SmallFireball.java -index b9e4955fecabbad8d6762f3d933ea1402e932d9b..895c501f8579799139239701703078ef060cd481 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/SmallFireball.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/SmallFireball.java -@@ -24,7 +24,7 @@ public class SmallFireball extends Fireball { - super(EntityType.SMALL_FIREBALL, owner, velocityX, velocityY, velocityZ, world); - // CraftBukkit start - if (this.getOwner() != null && this.getOwner() instanceof Mob) { -- isIncendiary = this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); -+ isIncendiary = this.level.purpurConfig.fireballsBypassMobGriefing || this.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); // Purpur - } - // CraftBukkit end - } -diff --git a/src/main/java/net/minecraft/world/entity/projectile/Snowball.java b/src/main/java/net/minecraft/world/entity/projectile/Snowball.java -index 6cded52e4627c2b6073fa221fc6d6583f1b2a96d..9a84e8cc1d1b2803a061fe9ef6297c9c4aea34c5 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/Snowball.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/Snowball.java -@@ -53,10 +53,40 @@ public class Snowball extends ThrowableItemProjectile { - protected void onHitEntity(EntityHitResult entityHitResult) { - super.onHitEntity(entityHitResult); - Entity entity = entityHitResult.getEntity(); -- int i = entity instanceof Blaze ? 3 : 0; -+ int i = entity.level.purpurConfig.snowballDamage >= 0 ? entity.level.purpurConfig.snowballDamage : entity instanceof Blaze ? 3 : 0; // Purpur - entity.hurt(this.damageSources().thrown(this, this.getOwner()), (float)i); - } - -+ // Purpur start - borrowed and modified code from ThrownPotion#onHitBlock and ThrownPotion#dowseFire -+ @Override -+ protected void onHitBlock(net.minecraft.world.phys.BlockHitResult blockHitResult) { -+ super.onHitBlock(blockHitResult); -+ -+ if (!this.level.isClientSide) { -+ net.minecraft.core.BlockPos blockposition = blockHitResult.getBlockPos(); -+ net.minecraft.core.BlockPos blockposition1 = blockposition.relative(blockHitResult.getDirection()); -+ -+ net.minecraft.world.level.block.state.BlockState iblockdata = this.level.getBlockState(blockposition); -+ -+ if (this.level.purpurConfig.snowballExtinguishesFire && this.level.getBlockState(blockposition1).is(net.minecraft.world.level.block.Blocks.FIRE)) { -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, blockposition1, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState()).isCancelled()) { -+ this.level.removeBlock(blockposition1, false); -+ } -+ } else if (this.level.purpurConfig.snowballExtinguishesCandles && net.minecraft.world.level.block.AbstractCandleBlock.isLit(iblockdata)) { -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.setValue(net.minecraft.world.level.block.AbstractCandleBlock.LIT, false)).isCancelled()) { -+ net.minecraft.world.level.block.AbstractCandleBlock.extinguish(null, iblockdata, this.level, blockposition); -+ } -+ } else if (this.level.purpurConfig.snowballExtinguishesCampfires && net.minecraft.world.level.block.CampfireBlock.isLitCampfire(iblockdata)) { -+ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.setValue(net.minecraft.world.level.block.CampfireBlock.LIT, false)).isCancelled()) { -+ this.level.levelEvent(null, 1009, blockposition, 0); -+ net.minecraft.world.level.block.CampfireBlock.dowse(this.getOwner(), this.level, blockposition, iblockdata); -+ this.level.setBlockAndUpdate(blockposition, iblockdata.setValue(net.minecraft.world.level.block.CampfireBlock.LIT, false)); -+ } -+ } -+ } -+ } -+ // Purpur end -+ - @Override - protected void onHit(HitResult hitResult) { - super.onHit(hitResult); -diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java -index 39ab9a283d856ba8d578d1378285758e32a24cf0..f35547c6b5951bc6eb4df74b2a94496fd20d69b5 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownEnderpearl.java -@@ -68,10 +68,11 @@ public class ThrownEnderpearl extends ThrowableItemProjectile { - Bukkit.getPluginManager().callEvent(teleEvent); - - if (!teleEvent.isCancelled() && entityplayer.connection.isAcceptingMessages()) { -- if (this.random.nextFloat() < 0.05F && this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { -+ if (this.random.nextFloat() < this.level.purpurConfig.enderPearlEndermiteChance && this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) { // Purpur - Endermite entityendermite = (Endermite) EntityType.ENDERMITE.create(this.level); - - if (entityendermite != null) { -+ entityendermite.setPlayerSpawned(true); // Purpur - entityendermite.moveTo(entity.getX(), entity.getY(), entity.getZ(), entity.getYRot(), entity.getXRot()); - this.level.addFreshEntity(entityendermite, CreatureSpawnEvent.SpawnReason.ENDER_PEARL); - } -@@ -84,7 +85,7 @@ public class ThrownEnderpearl extends ThrowableItemProjectile { - entityplayer.connection.teleport(teleEvent.getTo()); - entity.resetFallDistance(); - CraftEventFactory.entityDamage = this; -- entity.hurt(this.damageSources().fall(), 5.0F); -+ entity.hurt(this.damageSources().fall(), this.level.purpurConfig.enderPearlDamage); // Purpur - CraftEventFactory.entityDamage = null; - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java -index db151bf624095014c99d78b4f6748d2c3792abea..fb10fc5a5aa3b6d6220694041778bfd39ffa1cb8 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java -@@ -60,7 +60,7 @@ public class ThrownTrident extends AbstractArrow { - Entity entity = this.getOwner(); - byte b0 = (Byte) this.entityData.get(ThrownTrident.ID_LOYALTY); - -- if (b0 > 0 && (this.dealtDamage || this.isNoPhysics()) && entity != null) { -+ if (b0 > 0 && (this.dealtDamage || this.isNoPhysics() || (level.purpurConfig.tridentLoyaltyVoidReturnHeight < 0.0D && getY() < level.purpurConfig.tridentLoyaltyVoidReturnHeight)) && entity != null) { // Purpur - if (!this.isAcceptibleReturnOwner()) { - if (!this.level.isClientSide && this.pickup == AbstractArrow.Pickup.ALLOWED) { - this.spawnAtLocation(this.getPickupItem(), 0.1F); -diff --git a/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java b/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java -index 093a00e52062868b4fbf358b307513d0f599f69d..ba753735f3cbb2cb3d0a491d1bd94a04f83b123d 100644 ---- a/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java -+++ b/src/main/java/net/minecraft/world/entity/projectile/WitherSkull.java -@@ -95,7 +95,7 @@ public class WitherSkull extends AbstractHurtingProjectile { - if (!this.level.isClientSide) { - // CraftBukkit start - // this.level.explode(this, this.getX(), this.getY(), this.getZ(), 1.0F, false, World.a.MOB); -- ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), 1.0F, false); -+ ExplosionPrimeEvent event = new ExplosionPrimeEvent(this.getBukkitEntity(), this.level.purpurConfig.witherExplosionRadius, false); // Purpur - this.level.getCraftServer().getPluginManager().callEvent(event); - - if (!event.isCancelled()) { -diff --git a/src/main/java/net/minecraft/world/entity/raid/Raider.java b/src/main/java/net/minecraft/world/entity/raid/Raider.java -index 088b25457f058404bdb535c5369a903768da6457..4b97171f14ae7b69391db2e844b4223a12134ea7 100644 ---- a/src/main/java/net/minecraft/world/entity/raid/Raider.java -+++ b/src/main/java/net/minecraft/world/entity/raid/Raider.java -@@ -319,7 +319,7 @@ public abstract class Raider extends PatrollingMonster { - - @Override - public boolean canUse() { -- if (!this.mob.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) || !this.mob.canPickUpLoot()) return false; // Paper - respect game and entity rules for picking up items -+ if ((!this.mob.level.purpurConfig.pillagerBypassMobGriefing && !this.mob.level.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) || !this.mob.canPickUpLoot()) return false; // Paper - respect game and entity rules for picking up items // Purpur - Raid raid = this.mob.getCurrentRaid(); - - if (this.mob.hasActiveRaid() && !this.mob.getCurrentRaid().isOver() && this.mob.canBeLeader() && !ItemStack.matches(this.mob.getItemBySlot(EquipmentSlot.HEAD), Raid.LEADER_BANNER)) { // Gale - Lithium - cache ominous banner item -diff --git a/src/main/java/net/minecraft/world/entity/raid/Raids.java b/src/main/java/net/minecraft/world/entity/raid/Raids.java -index fabce3bc592b1b172b227395a07febdbb66ec3c9..df48bcc8f329e3855bb7426bdfe0e3c72af53bea 100644 ---- a/src/main/java/net/minecraft/world/entity/raid/Raids.java -+++ b/src/main/java/net/minecraft/world/entity/raid/Raids.java -@@ -28,6 +28,7 @@ import net.minecraft.world.phys.Vec3; - public class Raids extends SavedData { - - private static final String RAID_FILE_ID = "raids"; -+ public final Map playerCooldowns = Maps.newHashMap(); - public final Map raidMap = Maps.newHashMap(); - private final ServerLevel level; - private int nextAvailableID; -@@ -45,6 +46,17 @@ public class Raids extends SavedData { - - public void tick() { - ++this.tick; -+ // Purpur start -+ if (level.purpurConfig.raidCooldownSeconds != 0 && this.tick % 20 == 0) { -+ com.google.common.collect.ImmutableMap.copyOf(playerCooldowns).forEach((uuid, i) -> { -+ if (i < 1) { -+ playerCooldowns.remove(uuid); -+ } else { -+ playerCooldowns.put(uuid, i - 1); -+ } -+ }); -+ } -+ // Purpur end - Iterator iterator = this.raidMap.values().iterator(); - - while (iterator.hasNext()) { -@@ -129,11 +141,13 @@ public class Raids extends SavedData { - } - - if (flag) { -+ if (level.purpurConfig.raidCooldownSeconds != 0 && playerCooldowns.containsKey(player.getUUID())) return null; // Purpur - // CraftBukkit start - if (!org.bukkit.craftbukkit.event.CraftEventFactory.callRaidTriggerEvent(raid, player)) { - player.removeEffect(MobEffects.BAD_OMEN); - return null; - } -+ if (level.purpurConfig.raidCooldownSeconds != 0) playerCooldowns.put(player.getUUID(), level.purpurConfig.raidCooldownSeconds); // Purpur - - if (!this.raidMap.containsKey(raid.getId())) { - this.raidMap.put(raid.getId(), raid); -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java -index ee4f924afe15c9a4d96af7a55b357076c7b28501..ddcd7ada9f4bf5653bd8bf7c6cdb35d7243367d9 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java -@@ -107,11 +107,13 @@ public abstract class AbstractMinecart extends Entity { - private double flyingY = 0.949999988079071D; // Paper - restore vanilla precision - private double flyingZ = 0.949999988079071D; // Paper - restore vanilla precision - public double maxSpeed = 0.4D; -+ public double storedMaxSpeed; // Purpur - // CraftBukkit end - - protected AbstractMinecart(EntityType type, Level world) { - super(type, world); - this.blocksBuilding = true; -+ if (world != null) maxSpeed = storedMaxSpeed = world.purpurConfig.minecartMaxSpeed; // Purpur - } - - protected AbstractMinecart(EntityType type, Level world, double x, double y, double z) { -@@ -334,6 +336,12 @@ public abstract class AbstractMinecart extends Entity { - - @Override - public void tick() { -+ // Purpur start -+ if (storedMaxSpeed != level.purpurConfig.minecartMaxSpeed) { -+ maxSpeed = storedMaxSpeed = level.purpurConfig.minecartMaxSpeed; -+ } -+ // Purpur end -+ - // CraftBukkit start - double prevX = this.getX(); - double prevY = this.getY(); -@@ -497,16 +505,63 @@ public abstract class AbstractMinecart extends Entity { - - public void activateMinecart(int x, int y, int z, boolean powered) {} - -+ // Purpur start -+ private Double lastSpeed; -+ -+ public double getControllableSpeed() { -+ BlockPos pos = new BlockPos(this); -+ Block block = level.getBlockState(pos).getBlock(); -+ if (!block.material.isSolid()) { -+ block = level.getBlockState(pos.relative(Direction.DOWN)).getBlock(); -+ } -+ Double speed = level.purpurConfig.minecartControllableBlockSpeeds.get(block); -+ if (!block.material.isSolid()) { -+ speed = lastSpeed; -+ } -+ if (speed == null) { -+ speed = level.purpurConfig.minecartControllableBaseSpeed; -+ } -+ return lastSpeed = speed; -+ } -+ // Purpur end -+ - protected void comeOffTrack() { - double d0 = this.getMaxSpeed(); - Vec3 vec3d = this.getDeltaMovement(); - - this.setDeltaMovement(Mth.clamp(vec3d.x, -d0, d0), vec3d.y, Mth.clamp(vec3d.z, -d0, d0)); -+ -+ // Purpur start -+ if (level.purpurConfig.minecartControllable && !isInWater() && !isInLava() && !passengers.isEmpty()) { -+ Entity passenger = passengers.get(0); -+ if (passenger instanceof Player) { -+ Player player = (Player) passenger; -+ if (player.jumping && this.onGround) { -+ setDeltaMovement(new Vec3(getDeltaMovement().x, level.purpurConfig.minecartControllableHopBoost, getDeltaMovement().z)); -+ } -+ if (player.zza != 0.0F) { -+ Vector velocity = player.getBukkitEntity().getEyeLocation().getDirection().normalize().multiply(getControllableSpeed()); -+ if (player.zza < 0.0) { -+ velocity.multiply(-0.5); -+ } -+ setDeltaMovement(new Vec3(velocity.getX(), getDeltaMovement().y, velocity.getZ())); -+ } -+ this.setYRot(passenger.getYRot() - 90); -+ maxUpStep = level.purpurConfig.minecartControllableStepHeight; -+ } else { -+ maxUpStep = 0.0F; -+ } -+ } else { -+ maxUpStep = 0.0F; -+ } -+ // Purpur end -+ - if (this.onGround) { - // CraftBukkit start - replace magic numbers with our variables - this.setDeltaMovement(new Vec3(this.getDeltaMovement().x * this.derailedX, this.getDeltaMovement().y * this.derailedY, this.getDeltaMovement().z * this.derailedZ)); - // CraftBukkit end - } -+ else if (level.purpurConfig.minecartControllable) setDeltaMovement(new Vec3(getDeltaMovement().x * derailedX, getDeltaMovement().y, getDeltaMovement().z * derailedZ)); // Purpur - - this.move(MoverType.SELF, this.getDeltaMovement()); - if (!this.onGround) { -@@ -668,7 +723,7 @@ public abstract class AbstractMinecart extends Entity { - if (d18 > 0.01D) { - double d20 = 0.06D; - -- this.setDeltaMovement(vec3d4.add(vec3d4.x / d18 * 0.06D, 0.0D, vec3d4.z / d18 * 0.06D)); -+ this.setDeltaMovement(vec3d4.add(vec3d4.x / d18 * this.level.purpurConfig.poweredRailBoostModifier, 0.0D, vec3d4.z / d18 * this.level.purpurConfig.poweredRailBoostModifier)); // Purpur - } else { - Vec3 vec3d5 = this.getDeltaMovement(); - double d21 = vec3d5.x; -diff --git a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java -index 12e3209c5246ede89daaf8455fe70b4a517e12f6..06421017e3a7a0511c253e2ad4a028b0c156c9a8 100644 ---- a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java -+++ b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java -@@ -223,7 +223,13 @@ public class Boat extends Entity implements VariantHolder { - } - - protected void destroy(DamageSource source) { -- this.spawnAtLocation((ItemLike) this.getDropItem()); -+ // Purpur start -+ final ItemStack boat = new ItemStack(this.getDropItem()); -+ if (this.level.purpurConfig.persistentDroppableEntityDisplayNames && this.hasCustomName()) { -+ boat.setHoverName(this.getCustomName()); -+ } -+ this.spawnAtLocation(boat); -+ // Purpur end - } - - @Override -@@ -541,6 +547,7 @@ public class Boat extends Entity implements VariantHolder { - - if (f > 0.0F) { - this.landFriction = f; -+ if (level.purpurConfig.boatEjectPlayersOnLand) ejectPassengers(); // Purpur - return Boat.Status.ON_LAND; - } else { - return Boat.Status.IN_AIR; -diff --git a/src/main/java/net/minecraft/world/food/FoodData.java b/src/main/java/net/minecraft/world/food/FoodData.java -index 4a2dcf9bd83dd3fdff43483f887f4f58dc4715cd..3d2e3c7dd3bf5c8f483a933e9f6868586c0d3911 100644 ---- a/src/main/java/net/minecraft/world/food/FoodData.java -+++ b/src/main/java/net/minecraft/world/food/FoodData.java -@@ -33,8 +33,10 @@ public class FoodData { - // CraftBukkit end - - public void eat(int food, float saturationModifier) { -+ int oldValue = this.foodLevel; // Purpur - this.foodLevel = Math.min(food + this.foodLevel, 20); - this.saturationLevel = Math.min(this.saturationLevel + (float) food * saturationModifier * 2.0F, (float) this.foodLevel); -+ if (this.entityhuman.level.purpurConfig.playerBurpWhenFull && this.foodLevel == 20 && oldValue < 20) this.entityhuman.burpDelay = this.entityhuman.level.purpurConfig.playerBurpDelay; // Purpur - } - - public void eat(Item item, ItemStack stack) { -@@ -100,7 +102,7 @@ public class FoodData { - ++this.tickTimer; - if (this.tickTimer >= this.starvationRate) { // CraftBukkit - add regen rate manipulation - if (player.getHealth() > 10.0F || enumdifficulty == Difficulty.HARD || player.getHealth() > 1.0F && enumdifficulty == Difficulty.NORMAL) { -- player.hurt(player.damageSources().starve(), 1.0F); -+ player.hurt(player.damageSources().starve(), player.level.purpurConfig.hungerStarvationDamage); // Purpur - } - - this.tickTimer = 0; -diff --git a/src/main/java/net/minecraft/world/food/FoodProperties.java b/src/main/java/net/minecraft/world/food/FoodProperties.java -index 9967ba762567631f2bdb1e4f8fe16a13ea927b46..6c945ae8fe8b1517e312c688f829fab41f12d9f4 100644 ---- a/src/main/java/net/minecraft/world/food/FoodProperties.java -+++ b/src/main/java/net/minecraft/world/food/FoodProperties.java -@@ -2,15 +2,22 @@ package net.minecraft.world.food; - - import com.google.common.collect.Lists; - import com.mojang.datafixers.util.Pair; -+ -+import java.util.ArrayList; - import java.util.List; - import net.minecraft.world.effect.MobEffectInstance; - - public class FoodProperties { -- private final int nutrition; -- private final float saturationModifier; -- private final boolean isMeat; -- private final boolean canAlwaysEat; -- private final boolean fastFood; -+ // Purpur start -+ private int nutrition; public void setNutrition(int nutrition) { this.nutrition = nutrition; } -+ private float saturationModifier; public void setSaturationModifier(float saturation) { this.saturationModifier = saturation; } -+ private boolean isMeat; public void setIsMeat(boolean isMeat) { this.isMeat = isMeat; } -+ private boolean canAlwaysEat; public void setCanAlwaysEat(boolean canAlwaysEat) { this.canAlwaysEat = canAlwaysEat; } -+ private boolean fastFood; public void setFastFood(boolean isFastFood) { this.fastFood = isFastFood; } -+ public FoodProperties copy() { -+ return new FoodProperties(this.nutrition, this.saturationModifier, this.isMeat, this.canAlwaysEat, this.fastFood, new ArrayList<>(this.effects)); -+ } -+ // Purpur end - private final List> effects; - - FoodProperties(int hunger, float saturationModifier, boolean meat, boolean alwaysEdible, boolean snack, List> statusEffects) { -diff --git a/src/main/java/net/minecraft/world/food/Foods.java b/src/main/java/net/minecraft/world/food/Foods.java -index b16d9e2eaa589f19c563ee70b1a56d67dbcdecb0..71beab673f04cd051c46ea37f8c847316885d38d 100644 ---- a/src/main/java/net/minecraft/world/food/Foods.java -+++ b/src/main/java/net/minecraft/world/food/Foods.java -@@ -4,6 +4,9 @@ import net.minecraft.world.effect.MobEffectInstance; - import net.minecraft.world.effect.MobEffects; - - public class Foods { -+ public static final java.util.Map ALL_PROPERTIES = new java.util.HashMap<>(); // Purpur -+ public static final java.util.Map DEFAULT_PROPERTIES = new java.util.HashMap<>(); // Purpur -+ - public static final FoodProperties APPLE = (new FoodProperties.Builder()).nutrition(4).saturationMod(0.3F).build(); - public static final FoodProperties BAKED_POTATO = (new FoodProperties.Builder()).nutrition(5).saturationMod(0.6F).build(); - public static final FoodProperties BEEF = (new FoodProperties.Builder()).nutrition(3).saturationMod(0.3F).meat().build(); -diff --git a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -index 6d2ef064400304fb96f9ec5174b926371d01ed7c..4697f4b126dc440d9c67b0a82c60789f90edd26d 100644 ---- a/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/AbstractContainerMenu.java -@@ -77,6 +77,7 @@ public abstract class AbstractContainerMenu { - @Nullable - private ContainerSynchronizer synchronizer; - private boolean suppressRemoteUpdates; -+ @javax.annotation.Nullable protected ItemStack activeQuickItem = null; // Purpur - - // CraftBukkit start - public boolean checkReachable = true; -diff --git a/src/main/java/net/minecraft/world/inventory/AbstractFurnaceMenu.java b/src/main/java/net/minecraft/world/inventory/AbstractFurnaceMenu.java -index 8ae78ae54d87ea7789df754311fa0e8aade0ce91..35479019fb846573f4e2eb5902f3ebe898c4d61f 100644 ---- a/src/main/java/net/minecraft/world/inventory/AbstractFurnaceMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/AbstractFurnaceMenu.java -@@ -145,7 +145,13 @@ public abstract class AbstractFurnaceMenu extends RecipeBookMenu { - } else if (slot != 1 && slot != 0) { - if (this.canSmelt(itemstack1)) { - if (!this.moveItemStackTo(itemstack1, 0, 1, false)) { -- return ItemStack.EMPTY; -+ // Purpur start - fix #625 -+ if (this.isFuel(itemstack1)) { -+ if (!this.moveItemStackTo(itemstack1, 1, 2, false)) { -+ return ItemStack.EMPTY; -+ } -+ } -+ // Purpur end - } - } else if (this.isFuel(itemstack1)) { - if (!this.moveItemStackTo(itemstack1, 1, 2, false)) { -diff --git a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java -index b7a2295290227045e6426ee0f71707185d95b943..7ade5cd93b3d7b7259bf3246a8b74b0b31407817 100644 ---- a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java -@@ -21,6 +21,13 @@ import org.slf4j.Logger; - import org.bukkit.craftbukkit.inventory.CraftInventoryView; - // CraftBukkit end - -+// Purpur start -+import net.minecraft.nbt.IntTag; -+import net.minecraft.network.protocol.game.ClientboundContainerSetDataPacket; -+import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket; -+import net.minecraft.server.level.ServerPlayer; -+// Purpur end -+ - public class AnvilMenu extends ItemCombinerMenu { - - public static final int INPUT_SLOT = 0; -@@ -48,6 +55,8 @@ public class AnvilMenu extends ItemCombinerMenu { - public int maximumRepairCost = 40; - private CraftInventoryView bukkitEntity; - // CraftBukkit end -+ public boolean bypassCost = false; // Purpur -+ public boolean canDoUnsafeEnchants = false; // Purpur - - public AnvilMenu(int syncId, Inventory inventory) { - this(syncId, inventory, ContainerLevelAccess.NULL); -@@ -75,12 +84,15 @@ public class AnvilMenu extends ItemCombinerMenu { - - @Override - protected boolean mayPickup(Player player, boolean present) { -- return (player.getAbilities().instabuild || player.experienceLevel >= this.cost.get()) && this.cost.get() > AnvilMenu.DEFAULT_DENIED_COST && present; // CraftBukkit - allow cost 0 like a free item -+ return (player.getAbilities().instabuild || player.experienceLevel >= this.cost.get()) && (bypassCost || this.cost.get() > AnvilMenu.DEFAULT_DENIED_COST) && present; // CraftBukkit - allow cost 0 like a free item // Purpur - } - - @Override - protected void onTake(Player player, ItemStack stack) { -+ ItemStack itemstack = activeQuickItem == null ? stack : activeQuickItem; // Purpur -+ if (org.purpurmc.purpur.event.inventory.AnvilTakeResultEvent.getHandlerList().getRegisteredListeners().length > 0) new org.purpurmc.purpur.event.inventory.AnvilTakeResultEvent(player.getBukkitEntity(), getBukkitView(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack)).callEvent(); // Purpur - if (!player.getAbilities().instabuild) { -+ if (bypassCost) ((ServerPlayer) player).lastSentExp = -1; else // Purpur - player.giveExperienceLevels(-this.cost.get()); - } - -@@ -131,6 +143,12 @@ public class AnvilMenu extends ItemCombinerMenu { - - @Override - public void createResult() { -+ // Purpur start -+ bypassCost = false; -+ canDoUnsafeEnchants = false; -+ if (org.purpurmc.purpur.event.inventory.AnvilUpdateResultEvent.getHandlerList().getRegisteredListeners().length > 0) new org.purpurmc.purpur.event.inventory.AnvilUpdateResultEvent(getBukkitView()).callEvent(); -+ // Purpur end -+ - ItemStack itemstack = this.inputSlots.getItem(0); - - this.cost.set(1); -@@ -207,7 +225,8 @@ public class AnvilMenu extends ItemCombinerMenu { - int i2 = (Integer) map1.get(enchantment); - - i2 = l1 == i2 ? i2 + 1 : Math.max(i2, l1); -- boolean flag3 = enchantment.canEnchant(itemstack); -+ boolean flag3 = canDoUnsafeEnchants || (org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchants && org.purpurmc.purpur.PurpurConfig.allowInapplicableEnchants) || enchantment.canEnchant(itemstack); // Purpur -+ boolean flag4 = true; // Purpur - - if (this.player.getAbilities().instabuild || itemstack.is(Items.ENCHANTED_BOOK)) { - flag3 = true; -@@ -219,16 +238,16 @@ public class AnvilMenu extends ItemCombinerMenu { - Enchantment enchantment1 = (Enchantment) iterator1.next(); - - if (enchantment1 != enchantment && !enchantment.isCompatibleWith(enchantment1)) { -- flag3 = false; -+ flag4 = canDoUnsafeEnchants || (org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchants && org.purpurmc.purpur.PurpurConfig.allowIncompatibleEnchants); // Purpur flag3 -> flag4 - ++i; - } - } - -- if (!flag3) { -+ if (!flag3 || !flag4) { // Purpur - flag2 = true; - } else { - flag1 = true; -- if (i2 > enchantment.getMaxLevel()) { -+ if ((!org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchants || !org.purpurmc.purpur.PurpurConfig.allowHigherEnchantsLevels) && i2 > enchantment.getMaxLevel()) { // Purpur - i2 = enchantment.getMaxLevel(); - } - -@@ -278,6 +297,54 @@ public class AnvilMenu extends ItemCombinerMenu { - } else if (!this.itemName.equals(itemstack.getHoverName().getString())) { - b1 = 1; - i += b1; -+ // Purpur start -+ if (this.player != null) { -+ org.bukkit.craftbukkit.entity.CraftHumanEntity player = this.player.getBukkitEntity(); -+ String name = this.itemName; -+ boolean removeItalics = false; -+ if (player.hasPermission("purpur.anvil.remove_italics")) { -+ if (name.startsWith("&r")) { -+ name = name.substring(2); -+ removeItalics = true; -+ } else if (name.startsWith("")) { -+ name = name.substring(3); -+ removeItalics = true; -+ } else if (name.startsWith("")) { -+ name = name.substring(7); -+ removeItalics = true; -+ } -+ } -+ if (this.player.level.purpurConfig.anvilAllowColors) { -+ if (player.hasPermission("purpur.anvil.color")) { -+ java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("(?i)&([0-9a-fr])").matcher(name); -+ while (matcher.find()) { -+ String match = matcher.group(1); -+ name = name.replace("&" + match, "\u00a7" + match.toLowerCase(java.util.Locale.ROOT)); -+ } -+ //name = name.replaceAll("(?i)&([0-9a-fr])", "\u00a7$1"); -+ } -+ if (player.hasPermission("purpur.anvil.format")) { -+ java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("(?i)&([k-or])").matcher(name); -+ while (matcher.find()) { -+ String match = matcher.group(1); -+ name = name.replace("&" + match, "\u00a7" + match.toLowerCase(java.util.Locale.ROOT)); -+ } -+ //name = name.replaceAll("(?i)&([l-or])", "\u00a7$1"); -+ } -+ } -+ net.kyori.adventure.text.Component component; -+ if (this.player.level.purpurConfig.anvilColorsUseMiniMessage && player.hasPermission("purpur.anvil.minimessage")) { -+ component = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(org.bukkit.ChatColor.stripColor(name)); -+ } else { -+ component = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(name); -+ } -+ if (removeItalics) { -+ component = component.decoration(net.kyori.adventure.text.format.TextDecoration.ITALIC, false); -+ } -+ itemstack1.setHoverName(io.papermc.paper.adventure.PaperAdventure.asVanilla(component)); -+ } -+ else -+ // Purpur end - itemstack1.setHoverName(Component.literal(this.itemName)); - } - -@@ -290,6 +357,13 @@ public class AnvilMenu extends ItemCombinerMenu { - this.cost.set(this.maximumRepairCost - 1); // CraftBukkit - } - -+ // Purpur start -+ if (bypassCost && cost.get() >= maximumRepairCost) { -+ itemstack.addTagElement("Purpur.realCost", IntTag.valueOf(cost.get())); -+ cost.set(maximumRepairCost - 1); -+ } -+ // Purpur end -+ - if (this.cost.get() >= this.maximumRepairCost && !this.player.getAbilities().instabuild) { // CraftBukkit - itemstack1 = ItemStack.EMPTY; - } -@@ -312,11 +386,17 @@ public class AnvilMenu extends ItemCombinerMenu { - org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(this.getBukkitView(), itemstack1); // CraftBukkit - sendAllDataToRemote(); // CraftBukkit - SPIGOT-6686: Always send completed inventory to stay in sync with client - this.broadcastChanges(); -+ // Purpur start -+ if ((canDoUnsafeEnchants || org.purpurmc.purpur.PurpurConfig.allowUnsafeEnchants) && itemstack1 != ItemStack.EMPTY) { -+ ((ServerPlayer) player).connection.send(new ClientboundContainerSetSlotPacket(containerId, incrementStateId(), 2, itemstack1)); -+ ((ServerPlayer) player).connection.send(new ClientboundContainerSetDataPacket(containerId, 0, cost.get())); -+ } -+ // Purpur end - } - } - - public static int calculateIncreasedRepairCost(int cost) { -- return cost * 2 + 1; -+ return org.purpurmc.purpur.PurpurConfig.anvilCumulativeCost ? cost * 2 + 1 : 0; - } - - public void setItemName(String newItemName) { -diff --git a/src/main/java/net/minecraft/world/inventory/ChestMenu.java b/src/main/java/net/minecraft/world/inventory/ChestMenu.java -index 0dbfd23bbfc6ad203f048142f8c90ef741849fe1..9a80427d2bb470b6b1638e59aba57216676dcbd2 100644 ---- a/src/main/java/net/minecraft/world/inventory/ChestMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/ChestMenu.java -@@ -67,10 +67,30 @@ public class ChestMenu extends AbstractContainerMenu { - return new ChestMenu(MenuType.GENERIC_9x6, syncId, playerInventory, 6); - } - -+ // Purpur start -+ public static ChestMenu oneRow(int syncId, Inventory playerInventory, Container inventory) { -+ return new ChestMenu(MenuType.GENERIC_9x1, syncId, playerInventory, inventory, 1); -+ } -+ -+ public static ChestMenu twoRows(int syncId, Inventory playerInventory, Container inventory) { -+ return new ChestMenu(MenuType.GENERIC_9x2, syncId, playerInventory, inventory, 2); -+ } -+ // Purpur end -+ - public static ChestMenu threeRows(int syncId, Inventory playerInventory, Container inventory) { - return new ChestMenu(MenuType.GENERIC_9x3, syncId, playerInventory, inventory, 3); - } - -+ // Purpur start -+ public static ChestMenu fourRows(int syncId, Inventory playerInventory, Container inventory) { -+ return new ChestMenu(MenuType.GENERIC_9x4, syncId, playerInventory, inventory, 4); -+ } -+ -+ public static ChestMenu fiveRows(int syncId, Inventory playerInventory, Container inventory) { -+ return new ChestMenu(MenuType.GENERIC_9x5, syncId, playerInventory, inventory, 5); -+ } -+ // Purpur end -+ - public static ChestMenu sixRows(int syncId, Inventory playerInventory, Container inventory) { - return new ChestMenu(MenuType.GENERIC_9x6, syncId, playerInventory, inventory, 6); - } -diff --git a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java -index c2fc00509bf3690d359928e8d352d4b3c2ca1491..69ae671be07b1928e778399551991777829e432a 100644 ---- a/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/EnchantmentMenu.java -@@ -38,6 +38,12 @@ import org.bukkit.event.enchantment.PrepareItemEnchantEvent; - import org.bukkit.entity.Player; - // CraftBukkit end - -+// Purpur start -+import net.minecraft.world.level.block.entity.BlockEntity; -+import net.minecraft.world.level.block.entity.EnchantmentTableBlockEntity; -+import org.bukkit.craftbukkit.entity.CraftHumanEntity; -+// Purpur end -+ - public class EnchantmentMenu extends AbstractContainerMenu { - - private final Container enchantSlots; -@@ -71,6 +77,22 @@ public class EnchantmentMenu extends AbstractContainerMenu { - return context.getLocation(); - } - // CraftBukkit end -+ -+ // Purpur start -+ @Override -+ public void onClose(CraftHumanEntity who) { -+ super.onClose(who); -+ -+ if (who.getHandle().getLevel().purpurConfig.enchantmentTableLapisPersists) { -+ access.execute((level, pos) -> { -+ BlockEntity blockEntity = level.getBlockEntity(pos); -+ if (blockEntity instanceof EnchantmentTableBlockEntity enchantmentTable) { -+ enchantmentTable.setLapis(this.getItem(1).getCount()); -+ } -+ }); -+ } -+ } -+ // Purpur end - }; - this.random = RandomSource.create(); - this.enchantmentSeed = DataSlot.standalone(); -@@ -96,6 +118,17 @@ public class EnchantmentMenu extends AbstractContainerMenu { - } - }); - -+ // Purpur start -+ access.execute((level, pos) -> { -+ if (level.purpurConfig.enchantmentTableLapisPersists) { -+ BlockEntity blockEntity = level.getBlockEntity(pos); -+ if (blockEntity instanceof EnchantmentTableBlockEntity enchantmentTable) { -+ this.getSlot(1).set(new ItemStack(Items.LAPIS_LAZULI, enchantmentTable.getLapis())); -+ } -+ } -+ }); -+ // Purpur end -+ - int j; - - for (j = 0; j < 3; ++j) { -@@ -338,6 +371,7 @@ public class EnchantmentMenu extends AbstractContainerMenu { - public void removed(net.minecraft.world.entity.player.Player player) { - super.removed(player); - this.access.execute((world, blockposition) -> { -+ if (world.purpurConfig.enchantmentTableLapisPersists) this.getSlot(1).set(ItemStack.EMPTY); // Purpur - this.clearContainer(player, this.enchantSlots); - }); - } -diff --git a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -index 89838076f3231ff4318ebb2718c9406399b4e4f5..d41987060c2261f1a345752ecc46af1ec23b83ea 100644 ---- a/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/GrindstoneMenu.java -@@ -95,9 +95,11 @@ public class GrindstoneMenu extends AbstractContainerMenu { - - @Override - public void onTake(net.minecraft.world.entity.player.Player player, ItemStack stack) { -+ ItemStack itemstack = activeQuickItem == null ? stack : activeQuickItem; // Purpur - context.execute((world, blockposition) -> { -+ org.purpurmc.purpur.event.inventory.GrindstoneTakeResultEvent grindstoneTakeResultEvent = new org.purpurmc.purpur.event.inventory.GrindstoneTakeResultEvent(player.getBukkitEntity(), getBukkitView(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), this.getExperienceAmount(world)); grindstoneTakeResultEvent.callEvent(); // Purpur - if (world instanceof ServerLevel) { -- ExperienceOrb.award((ServerLevel) world, Vec3.atCenterOf(blockposition), this.getExperienceAmount(world), org.bukkit.entity.ExperienceOrb.SpawnReason.GRINDSTONE, player); // Paper -+ ExperienceOrb.award((ServerLevel) world, Vec3.atCenterOf(blockposition), grindstoneTakeResultEvent.getExperienceAmount(), org.bukkit.entity.ExperienceOrb.SpawnReason.GRINDSTONE, player); // Paper // Purpur - } - - world.levelEvent(1042, blockposition, 0); -@@ -130,7 +132,7 @@ public class GrindstoneMenu extends AbstractContainerMenu { - Enchantment enchantment = (Enchantment) entry.getKey(); - Integer integer = (Integer) entry.getValue(); - -- if (!enchantment.isCurse()) { -+ if (!org.purpurmc.purpur.PurpurConfig.grindstoneIgnoredEnchants.contains(enchantment)) { // Purpur - j += enchantment.getMinCost(integer); - } - } -@@ -230,7 +232,7 @@ public class GrindstoneMenu extends AbstractContainerMenu { - Entry entry = (Entry) iterator.next(); - Enchantment enchantment = (Enchantment) entry.getKey(); - -- if (!enchantment.isCurse() || EnchantmentHelper.getItemEnchantmentLevel(enchantment, itemstack2) == 0) { -+ if (!org.purpurmc.purpur.PurpurConfig.grindstoneIgnoredEnchants.contains(enchantment) || EnchantmentHelper.getItemEnchantmentLevel(enchantment, itemstack2) == 0) { // Purpur - itemstack2.enchant(enchantment, (Integer) entry.getValue()); - } - } -@@ -251,7 +253,7 @@ public class GrindstoneMenu extends AbstractContainerMenu { - - itemstack1.setCount(amount); - Map map = (Map) EnchantmentHelper.getEnchantments(item).entrySet().stream().filter((entry) -> { -- return ((Enchantment) entry.getKey()).isCurse(); -+ return org.purpurmc.purpur.PurpurConfig.grindstoneIgnoredEnchants.contains((Enchantment) entry.getKey()); // Purpur - }).collect(Collectors.toMap(Entry::getKey, Entry::getValue)); - - EnchantmentHelper.setEnchantments(map, itemstack1); -@@ -267,6 +269,20 @@ public class GrindstoneMenu extends AbstractContainerMenu { - itemstack1.setRepairCost(AnvilMenu.calculateIncreasedRepairCost(itemstack1.getBaseRepairCost())); - } - -+ // Purpur start -+ if (org.purpurmc.purpur.PurpurConfig.grindstoneRemoveAttributes && itemstack1.getTag() != null) { -+ for (String key : itemstack1.getTag().getAllKeys()) { -+ if (!key.equals("display")) { -+ itemstack1.getTag().remove(key); -+ } -+ } -+ } -+ -+ if (org.purpurmc.purpur.PurpurConfig.grindstoneRemoveDisplay && itemstack1.getTag() != null) { -+ itemstack1.getTag().remove("display"); -+ } -+ // Purpur end -+ - return itemstack1; - } - -@@ -328,7 +344,9 @@ public class GrindstoneMenu extends AbstractContainerMenu { - return ItemStack.EMPTY; - } - -+ this.activeQuickItem = itemstack; // Purpur - slot1.onTake(player, itemstack1); -+ this.activeQuickItem = null; // Purpur - } - - return itemstack; -diff --git a/src/main/java/net/minecraft/world/inventory/InventoryMenu.java b/src/main/java/net/minecraft/world/inventory/InventoryMenu.java -index da0f5c5e6ca7ce7b38792e6da52c5cdcdbae3b78..4136bcd49fe05d916ab65de0e866145185db4204 100644 ---- a/src/main/java/net/minecraft/world/inventory/InventoryMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/InventoryMenu.java -@@ -4,6 +4,7 @@ import com.mojang.datafixers.util.Pair; - import net.minecraft.network.chat.Component; - import net.minecraft.resources.ResourceLocation; - import net.minecraft.world.Container; -+import net.minecraft.world.effect.MobEffects; - import net.minecraft.world.entity.EquipmentSlot; - import net.minecraft.world.entity.Mob; - import net.minecraft.world.entity.player.Inventory; -@@ -95,7 +96,7 @@ public class InventoryMenu extends RecipeBookMenu { - public boolean mayPickup(Player playerEntity) { - ItemStack itemstack = this.getItem(); - -- return !itemstack.isEmpty() && !playerEntity.isCreative() && EnchantmentHelper.hasBindingCurse(itemstack) ? false : super.mayPickup(playerEntity); -+ return !itemstack.isEmpty() && !playerEntity.isCreative() && EnchantmentHelper.hasBindingCurse(itemstack) ? playerEntity.level.purpurConfig.playerRemoveBindingWithWeakness && playerEntity.hasEffect(MobEffects.WEAKNESS) : super.mayPickup(playerEntity); // Purpur - } - - @Override -diff --git a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java -index ff770b9ce68a62418de0c7ed389650626fa1dcb2..102739c0089ff3f6b3432f954304d43a3dfebc35 100644 ---- a/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java -+++ b/src/main/java/net/minecraft/world/inventory/ItemCombinerMenu.java -@@ -177,7 +177,9 @@ public abstract class ItemCombinerMenu extends AbstractContainerMenu { - return ItemStack.EMPTY; - } - -+ this.activeQuickItem = itemstack; // Purpur - slot1.onTake(player, itemstack1); -+ this.activeQuickItem = null; // Purpur - } - - return itemstack; -diff --git a/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java b/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java -index 4703f23316f82a1a942907b46d2d6dcb7d70ec37..162798f57a05b78121fa6c4fadf5adee80fbe221 100644 ---- a/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java -+++ b/src/main/java/net/minecraft/world/inventory/PlayerEnderChestContainer.java -@@ -30,11 +30,18 @@ public class PlayerEnderChestContainer extends SimpleContainer { - } - - public PlayerEnderChestContainer(Player owner) { -- super(27); -+ super(org.purpurmc.purpur.PurpurConfig.enderChestSixRows ? 54 : 27); // Purpur - this.owner = owner; - // CraftBukkit end - } - -+ // Purpur start -+ @Override -+ public int getContainerSize() { -+ return owner.sixRowEnderchestSlotCount < 0 ? super.getContainerSize() : owner.sixRowEnderchestSlotCount; -+ } -+ // Purpur end -+ - public void setActiveChest(EnderChestBlockEntity blockEntity) { - this.activeChest = blockEntity; - } -diff --git a/src/main/java/net/minecraft/world/item/ArmorItem.java b/src/main/java/net/minecraft/world/item/ArmorItem.java -index d7a0cbde8f8c99276307502674c71463fbe7e89c..3500c56cb85d8c76b2acd77976d374eaf487b3b3 100644 ---- a/src/main/java/net/minecraft/world/item/ArmorItem.java -+++ b/src/main/java/net/minecraft/world/item/ArmorItem.java -@@ -60,7 +60,7 @@ public class ArmorItem extends Item implements Equipable { - return false; - } else { - LivingEntity entityliving = (LivingEntity) list.get(0); -- EquipmentSlot enumitemslot = Mob.getEquipmentSlotForItem(armor); -+ EquipmentSlot enumitemslot = pointer.getLevel().purpurConfig.dispenserApplyCursedArmor ? Mob.getEquipmentSlotForItem(armor) : Mob.getSlotForDispenser(armor); if (enumitemslot == null) return false; // Purpur - ItemStack itemstack1 = armor.copyWithCount(1); // Paper - shrink below and single item in event - // CraftBukkit start - Level world = pointer.getLevel(); -diff --git a/src/main/java/net/minecraft/world/item/ArmorStandItem.java b/src/main/java/net/minecraft/world/item/ArmorStandItem.java -index 7cffc64573008502bdd14ae4906fe51166b12fb3..1feafdbb48cf760cb6ebf95d5be2c32bdb1ad44f 100644 ---- a/src/main/java/net/minecraft/world/item/ArmorStandItem.java -+++ b/src/main/java/net/minecraft/world/item/ArmorStandItem.java -@@ -58,6 +58,14 @@ public class ArmorStandItem extends Item { - return InteractionResult.FAIL; - } - // CraftBukkit end -+ // Purpur start -+ if (world.purpurConfig.persistentDroppableEntityDisplayNames && itemstack.hasCustomHoverName()) { -+ entityarmorstand.setCustomName(itemstack.getHoverName()); -+ if (world.purpurConfig.armorstandSetNameVisible) { -+ entityarmorstand.setCustomNameVisible(true); -+ } -+ } -+ // Purpur end - worldserver.addFreshEntityWithPassengers(entityarmorstand); - world.playSound((Player) null, entityarmorstand.getX(), entityarmorstand.getY(), entityarmorstand.getZ(), SoundEvents.ARMOR_STAND_PLACE, SoundSource.BLOCKS, 0.75F, 0.8F); - entityarmorstand.gameEvent(GameEvent.ENTITY_PLACE, context.getPlayer()); -diff --git a/src/main/java/net/minecraft/world/item/AxeItem.java b/src/main/java/net/minecraft/world/item/AxeItem.java -index 9c7d0b9cc2fa98d5785c914c0183f7d4b5b1c1ea..89a4ab17ca8d2aa1f52b041c610d7de19bf55e66 100644 ---- a/src/main/java/net/minecraft/world/item/AxeItem.java -+++ b/src/main/java/net/minecraft/world/item/AxeItem.java -@@ -33,29 +33,32 @@ public class AxeItem extends DiggerItem { - BlockPos blockPos = context.getClickedPos(); - Player player = context.getPlayer(); - BlockState blockState = level.getBlockState(blockPos); -- Optional optional = this.getStripped(blockState); -- Optional optional2 = WeatheringCopper.getPrevious(blockState); -- Optional optional3 = Optional.ofNullable(HoneycombItem.WAX_OFF_BY_BLOCK.get().get(blockState.getBlock())).map((block) -> { -- return block.withPropertiesOf(blockState); -- }); -+ // Purpur start -+ Block clickedBlock = level.getBlockState(blockPos).getBlock(); -+ Optional optional = Optional.ofNullable(level.purpurConfig.axeStrippables.get(blockState.getBlock())); -+ Optional optional2 = Optional.ofNullable(level.purpurConfig.axeWeatherables.get(blockState.getBlock())); -+ Optional optional3 = Optional.ofNullable(level.purpurConfig.axeWaxables.get(blockState.getBlock())); -+ // Purpur end - ItemStack itemStack = context.getItemInHand(); -- Optional optional4 = Optional.empty(); -+ Optional optional4 = Optional.empty(); // Purpur - if (optional.isPresent()) { -- level.playSound(player, blockPos, SoundEvents.AXE_STRIP, SoundSource.BLOCKS, 1.0F, 1.0F); -+ if (!STRIPPABLES.containsKey(clickedBlock)) level.playSound(null, blockPos, SoundEvents.AXE_STRIP, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - force sound - optional4 = optional; - } else if (optional2.isPresent()) { -- level.playSound(player, blockPos, SoundEvents.AXE_SCRAPE, SoundSource.BLOCKS, 1.0F, 1.0F); -+ if (!HoneycombItem.WAXABLES.get().containsKey(clickedBlock)) level.playSound(null, blockPos, SoundEvents.AXE_SCRAPE, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - force sound - level.levelEvent(player, 3005, blockPos, 0); - optional4 = optional2; - } else if (optional3.isPresent()) { -- level.playSound(player, blockPos, SoundEvents.AXE_WAX_OFF, SoundSource.BLOCKS, 1.0F, 1.0F); -+ if (!HoneycombItem.WAX_OFF_BY_BLOCK.get().containsKey(clickedBlock)) level.playSound(null, blockPos, SoundEvents.AXE_WAX_OFF, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - force sound - level.levelEvent(player, 3004, blockPos, 0); - optional4 = optional3; - } - - if (optional4.isPresent()) { -+ org.purpurmc.purpur.tool.Actionable actionable = optional4.get(); -+ BlockState state = actionable.into().withPropertiesOf(blockState); // Purpur - // Paper start - EntityChangeBlockEvent -- if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, optional4.get()).isCancelled()) { -+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, blockPos, state).isCancelled()) { // Purpur - return InteractionResult.PASS; - } - // Paper end -@@ -63,15 +66,22 @@ public class AxeItem extends DiggerItem { - CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger((ServerPlayer)player, blockPos, itemStack); - } - -- level.setBlock(blockPos, optional4.get(), 11); -- level.gameEvent(GameEvent.BLOCK_CHANGE, blockPos, GameEvent.Context.of(player, optional4.get())); -+ // Purpur start -+ level.setBlock(blockPos, state, 11); -+ actionable.drops().forEach((drop, chance) -> { -+ if (level.random.nextDouble() < chance) { -+ Block.popResourceFromFace(level, blockPos, context.getClickedFace(), new ItemStack(drop)); -+ } -+ }); -+ level.gameEvent(GameEvent.BLOCK_CHANGE, blockPos, GameEvent.Context.of(player, state)); -+ // Purpur end - if (player != null) { - itemStack.hurtAndBreak(1, player, (p) -> { - p.broadcastBreakEvent(context.getHand()); - }); - } - -- return InteractionResult.sidedSuccess(level.isClientSide); -+ return InteractionResult.SUCCESS; // Purpur - force arm swing - } else { - return InteractionResult.PASS; - } -diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java -index ebee8de2ed831755b6fd154f6cc77ac993839bb9..9060a844cd3bb3b62171872d84516b9195b9b677 100644 ---- a/src/main/java/net/minecraft/world/item/BlockItem.java -+++ b/src/main/java/net/minecraft/world/item/BlockItem.java -@@ -153,7 +153,24 @@ public class BlockItem extends Item { - } - - protected boolean updateCustomBlockEntityTag(BlockPos pos, Level world, @Nullable Player player, ItemStack stack, BlockState state) { -- return BlockItem.updateCustomBlockEntityTag(world, player, pos, stack); -+ // Purpur start -+ boolean handled = updateCustomBlockEntityTag(world, player, pos, stack); -+ if (world.purpurConfig.persistentTileEntityDisplayNames && stack.hasTag()) { -+ CompoundTag display = stack.getTagElement("display"); -+ if (display != null) { -+ BlockEntity blockEntity = world.getBlockEntity(pos); -+ if (blockEntity != null) { -+ if (display.contains("Name", 8)) { -+ blockEntity.setPersistentDisplayName(display.getString("Name")); -+ } -+ if (display.contains("Lore", 9)) { -+ blockEntity.setPersistentLore(display.getList("Lore", 8)); -+ } -+ } -+ } -+ } -+ return handled; -+ // Purpur end - } - - @Nullable -@@ -288,7 +305,7 @@ public class BlockItem extends Item { - - @Override - public void onDestroyed(ItemEntity entity) { -- if (this.block instanceof ShulkerBoxBlock) { -+ if (this.block instanceof ShulkerBoxBlock && entity.level.purpurConfig.shulkerBoxItemDropContentsWhenDestroyed) { - ItemStack itemstack = entity.getItem(); - CompoundTag nbttagcompound = BlockItem.getBlockEntityData(itemstack); - -diff --git a/src/main/java/net/minecraft/world/item/BoatItem.java b/src/main/java/net/minecraft/world/item/BoatItem.java -index 1a95ac11a2fbc811c89afa3adf38e0fc9eaab09b..91280f8c39ea191b90da2a9ff5c49f43c255bd9a 100644 ---- a/src/main/java/net/minecraft/world/item/BoatItem.java -+++ b/src/main/java/net/minecraft/world/item/BoatItem.java -@@ -69,6 +69,11 @@ public class BoatItem extends Item { - - entityboat.setVariant(this.type); - entityboat.setYRot(user.getYRot()); -+ // Purpur start -+ if (world.purpurConfig.persistentDroppableEntityDisplayNames && itemstack.hasCustomHoverName()) { -+ entityboat.setCustomName(itemstack.getHoverName()); -+ } -+ // Purpur end - if (!world.noCollision(entityboat, entityboat.getBoundingBox())) { - return InteractionResultHolder.fail(itemstack); - } else { -diff --git a/src/main/java/net/minecraft/world/item/BowItem.java b/src/main/java/net/minecraft/world/item/BowItem.java -index 08d597db1a5345a343777a01427655e6bf2c926b..d45a2f49c82d00801578c34e5f5277fc5e82be87 100644 ---- a/src/main/java/net/minecraft/world/item/BowItem.java -+++ b/src/main/java/net/minecraft/world/item/BowItem.java -@@ -38,13 +38,13 @@ public class BowItem extends ProjectileWeaponItem implements Vanishable { - float f = BowItem.getPowerForTime(j); - - if ((double) f >= 0.1D) { -- boolean flag1 = flag && itemstack1.is(Items.ARROW); -+ boolean flag1 = flag && ((itemstack1.is(Items.ARROW) && world.purpurConfig.infinityWorksWithNormalArrows) || (itemstack1.is(Items.TIPPED_ARROW) && world.purpurConfig.infinityWorksWithTippedArrows) || (itemstack1.is(Items.SPECTRAL_ARROW) && world.purpurConfig.infinityWorksWithSpectralArrows)); // Purpur if (!world.isClientSide) { - - if (!world.isClientSide) { - ArrowItem itemarrow = (ArrowItem) (itemstack1.getItem() instanceof ArrowItem ? itemstack1.getItem() : Items.ARROW); - AbstractArrow entityarrow = itemarrow.createArrow(world, itemstack1, entityhuman); - -- entityarrow.shootFromRotation(entityhuman, entityhuman.getXRot(), entityhuman.getYRot(), 0.0F, f * 3.0F, 1.0F); -+ entityarrow.shootFromRotation(entityhuman, entityhuman.getXRot(), entityhuman.getYRot(), 0.0F, f * 3.0F, (float) world.purpurConfig.bowProjectileOffset); // Purpur - if (f == 1.0F) { - entityarrow.setCritArrow(true); - } -@@ -64,6 +64,13 @@ public class BowItem extends ProjectileWeaponItem implements Vanishable { - if (EnchantmentHelper.getItemEnchantmentLevel(Enchantments.FLAMING_ARROWS, stack) > 0) { - entityarrow.setSecondsOnFire(100); - } -+ // Purpur start -+ int lootingLevel = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.MOB_LOOTING, stack); -+ -+ if (lootingLevel > 0) { -+ entityarrow.setLootingLevel(lootingLevel); -+ } -+ // Purpur end - // CraftBukkit start - org.bukkit.event.entity.EntityShootBowEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callEntityShootBowEvent(entityhuman, stack, itemstack1, entityarrow, entityhuman.getUsedItemHand(), f, !flag1); - if (event.isCancelled()) { -@@ -132,7 +139,7 @@ public class BowItem extends ProjectileWeaponItem implements Vanishable { - ItemStack itemstack = user.getItemInHand(hand); - boolean flag = !user.getProjectile(itemstack).isEmpty(); - -- if (!user.getAbilities().instabuild && !flag) { -+ if (!(world.purpurConfig.infinityWorksWithoutArrows && EnchantmentHelper.getItemEnchantmentLevel(Enchantments.INFINITY_ARROWS, itemstack) > 0) && !user.getAbilities().instabuild && !flag) { // Purpur - return InteractionResultHolder.fail(itemstack); - } else { - user.startUsingItem(hand); -diff --git a/src/main/java/net/minecraft/world/item/BucketItem.java b/src/main/java/net/minecraft/world/item/BucketItem.java -index 5c6aa9c464784ad5ee366412d080c72d3d22a76f..c03abc9589bf5f37abc1b0d355ed9784bac31a93 100644 ---- a/src/main/java/net/minecraft/world/item/BucketItem.java -+++ b/src/main/java/net/minecraft/world/item/BucketItem.java -@@ -166,7 +166,7 @@ public class BucketItem extends Item implements DispensibleContainerItem { - // CraftBukkit end - if (!flag1) { - return movingobjectpositionblock != null && this.emptyContents(entityhuman, world, movingobjectpositionblock.getBlockPos().relative(movingobjectpositionblock.getDirection()), (BlockHitResult) null, enumdirection, clicked, itemstack, enumhand); // CraftBukkit -- } else if (world.dimensionType().ultraWarm() && this.content.is(FluidTags.WATER)) { -+ } else if ((world.dimensionType().ultraWarm() || (world.isTheEnd() && !org.purpurmc.purpur.PurpurConfig.allowWaterPlacementInTheEnd)) && this.content.is(FluidTags.WATER)) { // Purpur - int i = blockposition.getX(); - int j = blockposition.getY(); - int k = blockposition.getZ(); -@@ -174,7 +174,7 @@ public class BucketItem extends Item implements DispensibleContainerItem { - world.playSound(entityhuman, blockposition, SoundEvents.FIRE_EXTINGUISH, SoundSource.BLOCKS, 0.5F, 2.6F + (world.random.nextFloat() - world.random.nextFloat()) * 0.8F); - - for (int l = 0; l < 8; ++l) { -- world.addParticle(ParticleTypes.LARGE_SMOKE, (double) i + Math.random(), (double) j + Math.random(), (double) k + Math.random(), 0.0D, 0.0D, 0.0D); -+ ((ServerLevel) world).sendParticles(null, ParticleTypes.LARGE_SMOKE, (double) i + Math.random(), (double) j + Math.random(), (double) k + Math.random(), 1, 0.0D, 0.0D, 0.0D, 0.0D, true); // Purpur - } - - return true; -diff --git a/src/main/java/net/minecraft/world/item/CrossbowItem.java b/src/main/java/net/minecraft/world/item/CrossbowItem.java -index bc4f04c2512191da3c9e1c49f0716bb9128fc754..310e03d8cc07f95927d9806fc80a4215283d2ef5 100644 ---- a/src/main/java/net/minecraft/world/item/CrossbowItem.java -+++ b/src/main/java/net/minecraft/world/item/CrossbowItem.java -@@ -64,7 +64,7 @@ public class CrossbowItem extends ProjectileWeaponItem implements Vanishable { - ItemStack itemstack = user.getItemInHand(hand); - - if (CrossbowItem.isCharged(itemstack)) { -- CrossbowItem.performShooting(world, user, hand, itemstack, CrossbowItem.getShootingPower(itemstack), 1.0F); -+ CrossbowItem.performShooting(world, user, hand, itemstack, CrossbowItem.getShootingPower(itemstack), (float) world.purpurConfig.crossbowProjectileOffset); // Purpur - CrossbowItem.setCharged(itemstack, false); - return InteractionResultHolder.consume(itemstack); - } else if (!user.getProjectile(itemstack).isEmpty()) { -@@ -113,7 +113,7 @@ public class CrossbowItem extends ProjectileWeaponItem implements Vanishable { - // Paper end - int i = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.MULTISHOT, projectile); - int j = i == 0 ? 1 : 3; -- boolean flag = !consume || shooter instanceof Player && ((Player) shooter).getAbilities().instabuild; // Paper - add consume -+ boolean flag = !consume || shooter instanceof Player && ((Player) shooter).getAbilities().instabuild || (org.purpurmc.purpur.PurpurConfig.allowCrossbowInfinity && EnchantmentHelper.getItemEnchantmentLevel(Enchantments.INFINITY_ARROWS, projectile) > 0); // Paper - add consume // Purpur - ItemStack itemstack1 = shooter.getProjectile(projectile); - ItemStack itemstack2 = itemstack1.copy(); - -@@ -294,6 +294,14 @@ public class CrossbowItem extends ProjectileWeaponItem implements Vanishable { - entityarrow.setPierceLevel((byte) i); - } - -+ // Purpur start -+ int lootingLevel = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.MOB_LOOTING, crossbow); -+ -+ if (lootingLevel > 0) { -+ entityarrow.setLootingLevel(lootingLevel); -+ } -+ // Purpur end -+ - return entityarrow; - } - -@@ -303,7 +311,7 @@ public class CrossbowItem extends ProjectileWeaponItem implements Vanishable { - - for (int i = 0; i < list.size(); ++i) { - ItemStack itemstack1 = (ItemStack) list.get(i); -- boolean flag = entity instanceof Player && ((Player) entity).getAbilities().instabuild; -+ boolean flag = entity instanceof Player && ((Player) entity).getAbilities().instabuild || (org.purpurmc.purpur.PurpurConfig.allowCrossbowInfinity && EnchantmentHelper.getItemEnchantmentLevel(Enchantments.INFINITY_ARROWS, stack) > 0); // Purpur - - if (!itemstack1.isEmpty()) { - if (i == 0) { -diff --git a/src/main/java/net/minecraft/world/item/DyeColor.java b/src/main/java/net/minecraft/world/item/DyeColor.java -index 2170715ed0e81a3055e4ab546c8b294c5ef7f142..beae4e2b9f61df83215de860d64c4ce2d3482004 100644 ---- a/src/main/java/net/minecraft/world/item/DyeColor.java -+++ b/src/main/java/net/minecraft/world/item/DyeColor.java -@@ -103,4 +103,10 @@ public enum DyeColor implements StringRepresentable { - public String getSerializedName() { - return this.name; - } -+ -+ // Purpur start -+ public static DyeColor random(net.minecraft.util.RandomSource random) { -+ return values()[random.nextInt(values().length)]; -+ } -+ // Purpur end - } -diff --git a/src/main/java/net/minecraft/world/item/EggItem.java b/src/main/java/net/minecraft/world/item/EggItem.java -index 58cb992c5defec2f092755cbde661ff10f38bf9d..52f48681407d23f0925f4c9c072d5f0a2a6b1778 100644 ---- a/src/main/java/net/minecraft/world/item/EggItem.java -+++ b/src/main/java/net/minecraft/world/item/EggItem.java -@@ -24,7 +24,7 @@ public class EggItem extends Item { - ThrownEgg entityegg = new ThrownEgg(world, user); - - entityegg.setItem(itemstack); -- entityegg.shootFromRotation(user, user.getXRot(), user.getYRot(), 0.0F, 1.5F, 1.0F); -+ entityegg.shootFromRotation(user, user.getXRot(), user.getYRot(), 0.0F, 1.5F, (float) world.purpurConfig.eggProjectileOffset); // Purpur - // Paper start - com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) entityegg.getBukkitEntity()); - if (event.callEvent() && world.addFreshEntity(entityegg)) { -diff --git a/src/main/java/net/minecraft/world/item/EnderpearlItem.java b/src/main/java/net/minecraft/world/item/EnderpearlItem.java -index 749ab72edc0d2e9c6f1161415ab8d59d3d6ca976..6b27d98d06b163243bb0e1bb979aad03f48d7770 100644 ---- a/src/main/java/net/minecraft/world/item/EnderpearlItem.java -+++ b/src/main/java/net/minecraft/world/item/EnderpearlItem.java -@@ -24,7 +24,7 @@ public class EnderpearlItem extends Item { - ThrownEnderpearl entityenderpearl = new ThrownEnderpearl(world, user); - - entityenderpearl.setItem(itemstack); -- entityenderpearl.shootFromRotation(user, user.getXRot(), user.getYRot(), 0.0F, 1.5F, 1.0F); -+ entityenderpearl.shootFromRotation(user, user.getXRot(), user.getYRot(), 0.0F, 1.5F, (float) world.purpurConfig.enderPearlProjectileOffset); // Purpur - // Paper start - com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) entityenderpearl.getBukkitEntity()); - if (event.callEvent() && world.addFreshEntity(entityenderpearl)) { -@@ -36,7 +36,7 @@ public class EnderpearlItem extends Item { - - world.playSound((Player) null, user.getX(), user.getY(), user.getZ(), SoundEvents.ENDER_PEARL_THROW, SoundSource.NEUTRAL, 0.5F, 0.4F / (net.minecraft.world.entity.Entity.SHARED_RANDOM.nextFloat() * 0.4F + 0.8F)); - user.awardStat(Stats.ITEM_USED.get(this)); -- user.getCooldowns().addCooldown(this, 20); -+ user.getCooldowns().addCooldown(this, user.getAbilities().instabuild ? world.purpurConfig.enderPearlCooldownCreative : world.purpurConfig.enderPearlCooldown); // Purpur - } else { - // Paper end - if (user instanceof net.minecraft.server.level.ServerPlayer) { -diff --git a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java -index 82b0bda3e35ec2157a477e1a17b2b46baadc97d9..0fc45b1048a1c4e0dc2bd1ae0437eecbe113cf96 100644 ---- a/src/main/java/net/minecraft/world/item/FireworkRocketItem.java -+++ b/src/main/java/net/minecraft/world/item/FireworkRocketItem.java -@@ -15,6 +15,7 @@ import net.minecraft.util.ByIdMap; - import net.minecraft.world.InteractionHand; - import net.minecraft.world.InteractionResult; - import net.minecraft.world.InteractionResultHolder; -+import net.minecraft.world.entity.EquipmentSlot; - import net.minecraft.world.entity.player.Player; - import net.minecraft.world.entity.projectile.FireworkRocketEntity; - import net.minecraft.world.item.context.UseOnContext; -@@ -69,6 +70,14 @@ public class FireworkRocketItem extends Item { - com.destroystokyo.paper.event.player.PlayerElytraBoostEvent event = new com.destroystokyo.paper.event.player.PlayerElytraBoostEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Firework) fireworkRocketEntity.getBukkitEntity()); - if (event.callEvent() && world.addFreshEntity(fireworkRocketEntity)) { - user.awardStat(Stats.ITEM_USED.get(this)); -+ // Purpur start -+ if (world.purpurConfig.elytraDamagePerFireworkBoost > 0) { -+ ItemStack chestItem = user.getItemBySlot(EquipmentSlot.CHEST); -+ if (chestItem.getItem() == Items.ELYTRA) { -+ chestItem.hurtAndBreak(world.purpurConfig.elytraDamagePerFireworkBoost, user, (entityliving) -> entityliving.broadcastBreakEvent(EquipmentSlot.CHEST)); -+ } -+ } -+ // Purpur end - if (event.shouldConsume() && !user.getAbilities().instabuild) { - itemStack.shrink(1); - } else ((net.minecraft.server.level.ServerPlayer) user).getBukkitEntity().updateInventory(); -diff --git a/src/main/java/net/minecraft/world/item/HangingEntityItem.java b/src/main/java/net/minecraft/world/item/HangingEntityItem.java -index b2ad6d230de2c29f371178bccde1111c7532ee70..6667926519a0f1c151e53f59cce36e7417dfc1cd 100644 ---- a/src/main/java/net/minecraft/world/item/HangingEntityItem.java -+++ b/src/main/java/net/minecraft/world/item/HangingEntityItem.java -@@ -48,7 +48,7 @@ public class HangingEntityItem extends Item { - return InteractionResult.FAIL; - } else { - Level world = context.getLevel(); -- Object object; -+ Entity object; // Purpur - - if (this.type == EntityType.PAINTING) { - Optional optional = Painting.create(world, blockposition1, enumdirection); -@@ -72,6 +72,11 @@ public class HangingEntityItem extends Item { - - if (nbttagcompound != null) { - EntityType.updateCustomEntityTag(world, entityhuman, (Entity) object, nbttagcompound); -+ // Purpur start -+ if (world.purpurConfig.persistentDroppableEntityDisplayNames && itemstack.hasCustomHoverName()) { -+ object.setCustomName(itemstack.getHoverName()); -+ } -+ // Purpur end - } - - if (((HangingEntity) object).survives()) { -diff --git a/src/main/java/net/minecraft/world/item/HoeItem.java b/src/main/java/net/minecraft/world/item/HoeItem.java -index 180aec596110309aade13d2080f8824d152b07cb..c4aec1e5135a79837918b692e75a7b55d5cffeb0 100644 ---- a/src/main/java/net/minecraft/world/item/HoeItem.java -+++ b/src/main/java/net/minecraft/world/item/HoeItem.java -@@ -34,15 +34,23 @@ public class HoeItem extends DiggerItem { - public InteractionResult useOn(UseOnContext context) { - Level level = context.getLevel(); - BlockPos blockPos = context.getClickedPos(); -- Pair, Consumer> pair = TILLABLES.get(level.getBlockState(blockPos).getBlock()); -- if (pair == null) { -- return InteractionResult.PASS; -- } else { -- Predicate predicate = pair.getFirst(); -- Consumer consumer = pair.getSecond(); -+ // Purpur start -+ Block clickedBlock = level.getBlockState(blockPos).getBlock(); -+ var tillable = level.purpurConfig.hoeTillables.get(level.getBlockState(blockPos).getBlock()); -+ if (tillable == null) { return InteractionResult.PASS; } else { -+ Predicate predicate = tillable.condition().predicate(); -+ Consumer consumer = (ctx) -> { -+ level.setBlock(blockPos, tillable.into().defaultBlockState(), 11); -+ tillable.drops().forEach((drop, chance) -> { -+ if (level.random.nextDouble() < chance) { -+ Block.popResourceFromFace(level, blockPos, ctx.getClickedFace(), new ItemStack(drop)); -+ } -+ }); -+ }; -+ // Purpur end - if (predicate.test(context)) { - Player player = context.getPlayer(); -- level.playSound(player, blockPos, SoundEvents.HOE_TILL, SoundSource.BLOCKS, 1.0F, 1.0F); -+ if (!TILLABLES.containsKey(clickedBlock)) level.playSound(null, blockPos, SoundEvents.HOE_TILL, SoundSource.BLOCKS, 1.0F, 1.0F); // Purpur - force sound - if (!level.isClientSide) { - consumer.accept(context); - if (player != null) { -@@ -52,7 +60,7 @@ public class HoeItem extends DiggerItem { - } - } - -- return InteractionResult.sidedSuccess(level.isClientSide); -+ return InteractionResult.SUCCESS; // Purpur - force arm swing - } else { - return InteractionResult.PASS; - } -diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index f0b08787846f2d787826990b0684c4a4bf5fb8e6..2c2c6bd022c7f7f8ec28799a2843c1a83c18b3c4 100644 ---- a/src/main/java/net/minecraft/world/item/ItemStack.java -+++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -111,6 +111,7 @@ import org.bukkit.event.world.StructureGrowEvent; - - public final class ItemStack { - -+ public boolean isExactRecipeIngredient = false; // PaperPR - public static final Codec CODEC = RecordCodecBuilder.create((instance) -> { - return instance.group(BuiltInRegistries.ITEM.byNameCodec().fieldOf("id").forGetter((itemstack) -> { - return itemstack.item; -@@ -417,6 +418,7 @@ public final class ItemStack { - world.preventPoiUpdated = true; // CraftBukkit - SPIGOT-5710 - for (BlockState blockstate : blocks) { - blockstate.update(true, false); -+ ((CraftBlock) blockstate.getBlock()).getNMS().getBlock().forgetPlacer(); // Purpur - } - world.preventPoiUpdated = false; - -@@ -446,6 +448,7 @@ public final class ItemStack { - if (!(block.getBlock() instanceof BaseEntityBlock)) { // Containers get placed automatically - block.getBlock().onPlace(block, world, newblockposition, oldBlock, true, itemactioncontext); // Paper - pass itemactioncontext - } -+ block.getBlock().forgetPlacer(); // Purpur - - world.notifyAndUpdatePhysics(newblockposition, null, oldBlock, block, world.getBlockState(newblockposition), updateFlag, 512); // send null chunk as chunk.k() returns false by this point - } -@@ -566,6 +569,16 @@ public final class ItemStack { - return this.isDamageableItem() && this.getDamageValue() > 0; - } - -+ // Purpur start -+ public float getDamagePercent() { -+ if (isDamaged()) { -+ return (float) getDamageValue() / (float) getItem().getMaxDamage(); -+ } else { -+ return 0F; -+ } -+ } -+ // Purpur end -+ - public int getDamageValue() { - return this.tag == null ? 0 : this.tag.getInt("Damage"); - } -@@ -585,7 +598,7 @@ public final class ItemStack { - int j; - - if (amount > 0) { -- j = EnchantmentHelper.getItemEnchantmentLevel(Enchantments.UNBREAKING, this); -+ j = (getItem() == Items.ELYTRA && player != null && player.level.purpurConfig.elytraIgnoreUnbreaking) ? 0 : EnchantmentHelper.getItemEnchantmentLevel(Enchantments.UNBREAKING, this); - int k = 0; - - for (int l = 0; j > 0 && l < amount; ++l) { -@@ -640,6 +653,12 @@ public final class ItemStack { - if (this.hurt(amount, entity.getRandom(), entity /*instanceof ServerPlayer ? (ServerPlayer) entity : null*/)) { // Paper - pass LivingEntity for EntityItemDamageEvent - breakCallback.accept(entity); - Item item = this.getItem(); -+ // Purpur start -+ if (item == Items.ELYTRA) { -+ setDamageValue(item.getMaxDamage() - 1); -+ return; -+ } -+ // Purpur end - // CraftBukkit start - Check for item breaking - if (this.count == 1 && entity instanceof net.minecraft.world.entity.player.Player) { - org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemBreakEvent((net.minecraft.world.entity.player.Player) entity, this); -@@ -1199,7 +1218,7 @@ public final class ItemStack { - - ListTag nbttaglist = this.tag.getList("Enchantments", 10); - -- nbttaglist.add(EnchantmentHelper.storeEnchantment(EnchantmentHelper.getEnchantmentId(enchantment), (byte) level)); -+ nbttaglist.add(EnchantmentHelper.storeEnchantment(EnchantmentHelper.getEnchantmentId(enchantment), (org.purpurmc.purpur.PurpurConfig.clampEnchantLevels) ? (byte) level : (short) level)); // Purpur - processEnchantOrder(this.tag); // Paper - } - -@@ -1207,6 +1226,12 @@ public final class ItemStack { - return this.tag != null && this.tag.contains("Enchantments", 9) ? !this.tag.getList("Enchantments", 10).isEmpty() : false; - } - -+ // Purpur start -+ public boolean hasEnchantment(Enchantment enchantment) { -+ return isEnchanted() && EnchantmentHelper.deserializeEnchantments(getEnchantmentTags()).containsKey(enchantment); -+ } -+ // Purpur end -+ - public void addTagElement(String key, Tag element) { - this.getOrCreateTag().put(key, element); - } -diff --git a/src/main/java/net/minecraft/world/item/Items.java b/src/main/java/net/minecraft/world/item/Items.java -index 775823daa5187804d27e5ee696cd75f703bb067c..bc0915913d3d8fbe145ee7e19133c7de922e0c80 100644 ---- a/src/main/java/net/minecraft/world/item/Items.java -+++ b/src/main/java/net/minecraft/world/item/Items.java -@@ -292,7 +292,7 @@ public class Items { - public static final Item PURPUR_BLOCK = registerBlock(Blocks.PURPUR_BLOCK); - public static final Item PURPUR_PILLAR = registerBlock(Blocks.PURPUR_PILLAR); - public static final Item PURPUR_STAIRS = registerBlock(Blocks.PURPUR_STAIRS); -- public static final Item SPAWNER = registerBlock(Blocks.SPAWNER); -+ public static final Item SPAWNER = registerBlock(new org.purpurmc.purpur.item.SpawnerItem(Blocks.SPAWNER, new Item.Properties().rarity(Rarity.EPIC))); // Purpur - public static final Item CHEST = registerBlock(Blocks.CHEST); - public static final Item CRAFTING_TABLE = registerBlock(Blocks.CRAFTING_TABLE); - public static final Item FARMLAND = registerBlock(Blocks.FARMLAND); -@@ -1178,7 +1178,7 @@ public class Items { - public static final Item LANTERN = registerBlock(Blocks.LANTERN); - public static final Item SOUL_LANTERN = registerBlock(Blocks.SOUL_LANTERN); - public static final Item SWEET_BERRIES = registerItem("sweet_berries", new ItemNameBlockItem(Blocks.SWEET_BERRY_BUSH, (new Item.Properties()).food(Foods.SWEET_BERRIES))); -- public static final Item GLOW_BERRIES = registerItem("glow_berries", new ItemNameBlockItem(Blocks.CAVE_VINES, (new Item.Properties()).food(Foods.GLOW_BERRIES))); -+ public static final Item GLOW_BERRIES = registerItem("glow_berries", new org.purpurmc.purpur.item.GlowBerryItem(Blocks.CAVE_VINES, (new Item.Properties()).food(Foods.GLOW_BERRIES))); // Purpur - public static final Item CAMPFIRE = registerBlock(Blocks.CAMPFIRE); - public static final Item SOUL_CAMPFIRE = registerBlock(Blocks.SOUL_CAMPFIRE); - public static final Item SHROOMLIGHT = registerBlock(Blocks.SHROOMLIGHT); -@@ -1278,6 +1278,13 @@ public class Items { - ((BlockItem)item).registerBlocks(Item.BY_BLOCK, item); - } - -+ // Purpur start -+ if (item.getFoodProperties() != null) { -+ Foods.ALL_PROPERTIES.put(id.getPath(), item.getFoodProperties()); -+ Foods.DEFAULT_PROPERTIES.put(id.getPath(), item.getFoodProperties().copy()); -+ } -+ // Purpur end -+ - return Registry.register(BuiltInRegistries.ITEM, id, item); - } - } -diff --git a/src/main/java/net/minecraft/world/item/MilkBucketItem.java b/src/main/java/net/minecraft/world/item/MilkBucketItem.java -index f33977d95b6db473be4f95075ba99caf90ad0220..56dc04d8875971ee9a5d077a695509af74fe2473 100644 ---- a/src/main/java/net/minecraft/world/item/MilkBucketItem.java -+++ b/src/main/java/net/minecraft/world/item/MilkBucketItem.java -@@ -5,6 +5,8 @@ import net.minecraft.server.level.ServerPlayer; - import net.minecraft.stats.Stats; - import net.minecraft.world.InteractionHand; - import net.minecraft.world.InteractionResultHolder; -+import net.minecraft.world.effect.MobEffectInstance; -+import net.minecraft.world.effect.MobEffects; - import net.minecraft.world.entity.LivingEntity; - import net.minecraft.world.entity.player.Player; - import net.minecraft.world.level.Level; -@@ -31,7 +33,9 @@ public class MilkBucketItem extends Item { - } - - if (!world.isClientSide) { -+ MobEffectInstance badOmen = user.getEffect(MobEffects.BAD_OMEN); - user.removeAllEffects(org.bukkit.event.entity.EntityPotionEffectEvent.Cause.MILK); // CraftBukkit -+ if (!world.purpurConfig.milkCuresBadOmen && badOmen != null) user.addEffect(badOmen); // Purpur - } - - return stack.isEmpty() ? new ItemStack(Items.BUCKET) : stack; -diff --git a/src/main/java/net/minecraft/world/item/MinecartItem.java b/src/main/java/net/minecraft/world/item/MinecartItem.java -index c6d2f764efa9b8bec730bbe757d480e365b25ccc..33a30d26da2401535f0a72acb2bbffec1aef151e 100644 ---- a/src/main/java/net/minecraft/world/item/MinecartItem.java -+++ b/src/main/java/net/minecraft/world/item/MinecartItem.java -@@ -120,8 +120,9 @@ public class MinecartItem extends Item { - BlockState iblockdata = world.getBlockState(blockposition); - - if (!iblockdata.is(BlockTags.RAILS)) { -- return InteractionResult.FAIL; -- } else { -+ if (!world.purpurConfig.minecartPlaceAnywhere) return InteractionResult.FAIL; -+ if (iblockdata.getMaterial().isSolid()) blockposition = blockposition.relative(context.getClickedFace()); -+ } // else { // Purpur - place minecarts anywhere - ItemStack itemstack = context.getItemInHand(); - - if (!world.isClientSide) { -@@ -149,6 +150,6 @@ public class MinecartItem extends Item { - - itemstack.shrink(1); - return InteractionResult.sidedSuccess(world.isClientSide); -- } -+ // } // Purpur - place minecarts anywhere - } - } -diff --git a/src/main/java/net/minecraft/world/item/NameTagItem.java b/src/main/java/net/minecraft/world/item/NameTagItem.java -index 623f78c078fb3aa2665d7e8a37672438227bce6b..500c69e555c7247e20ef8cc59d83415578f44427 100644 ---- a/src/main/java/net/minecraft/world/item/NameTagItem.java -+++ b/src/main/java/net/minecraft/world/item/NameTagItem.java -@@ -24,6 +24,7 @@ public class NameTagItem extends Item { - if (!event.callEvent()) return InteractionResult.PASS; - LivingEntity newEntityLiving = ((org.bukkit.craftbukkit.entity.CraftLivingEntity) event.getEntity()).getHandle(); - newEntityLiving.setCustomName(event.getName() != null ? PaperAdventure.asVanilla(event.getName()) : null); -+ if (user.level.purpurConfig.armorstandFixNametags && entity instanceof net.minecraft.world.entity.decoration.ArmorStand) entity.setCustomNameVisible(true); // Purpur - if (event.isPersistent() && newEntityLiving instanceof Mob) { - ((Mob) newEntityLiving).setPersistenceRequired(); - // Paper end -diff --git a/src/main/java/net/minecraft/world/item/ShovelItem.java b/src/main/java/net/minecraft/world/item/ShovelItem.java -index c7195f2e12bbd6545f7bffcc2b4ba5cc3d48df20..5e730bc9c8ff94b16ac2bf8567dda8aea2ee4b2a 100644 ---- a/src/main/java/net/minecraft/world/item/ShovelItem.java -+++ b/src/main/java/net/minecraft/world/item/ShovelItem.java -@@ -34,7 +34,7 @@ public class ShovelItem extends DiggerItem { - return InteractionResult.PASS; - } else { - Player player = context.getPlayer(); -- BlockState blockState2 = FLATTENABLES.get(blockState.getBlock()); -+ BlockState blockState2 = level.purpurConfig.shovelTurnsBlockToGrassPath.contains(blockState.getBlock()) ? Blocks.DIRT_PATH.defaultBlockState() : null; // Purpur - BlockState blockState3 = null; - Runnable afterAction = null; // Paper - if (blockState2 != null && level.getBlockState(blockPos.above()).isAir()) { -diff --git a/src/main/java/net/minecraft/world/item/SnowballItem.java b/src/main/java/net/minecraft/world/item/SnowballItem.java -index ef3f90a5bcdd7b9815a4053cff166f9d2552f55d..e7e5e1cc92f56e3daba8fa09c59188febec5e8f2 100644 ---- a/src/main/java/net/minecraft/world/item/SnowballItem.java -+++ b/src/main/java/net/minecraft/world/item/SnowballItem.java -@@ -25,7 +25,7 @@ public class SnowballItem extends Item { - Snowball entitysnowball = new Snowball(world, user); - - entitysnowball.setItem(itemstack); -- entitysnowball.shootFromRotation(user, user.getXRot(), user.getYRot(), 0.0F, 1.5F, 1.0F); -+ entitysnowball.shootFromRotation(user, user.getXRot(), user.getYRot(), 0.0F, 1.5F, (float) world.purpurConfig.snowballProjectileOffset); // Purpur - // Paper start - com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), (org.bukkit.entity.Projectile) entitysnowball.getBukkitEntity()); - if (event.callEvent() && world.addFreshEntity(entitysnowball)) { -diff --git a/src/main/java/net/minecraft/world/item/SpawnEggItem.java b/src/main/java/net/minecraft/world/item/SpawnEggItem.java -index 31268e25056f980798ef7db72c4f955a074cc639..955822da142a2463af536dd1ce48037deda41402 100644 ---- a/src/main/java/net/minecraft/world/item/SpawnEggItem.java -+++ b/src/main/java/net/minecraft/world/item/SpawnEggItem.java -@@ -68,6 +68,15 @@ public class SpawnEggItem extends Item { - SpawnerBlockEntity tileentitymobspawner = (SpawnerBlockEntity) tileentity; - EntityType entitytypes = this.getType(itemstack.getTag()); - -+ // Purpur start -+ org.bukkit.block.Block bukkitBlock = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); -+ org.purpurmc.purpur.event.PlayerSetSpawnerTypeWithEggEvent event = new org.purpurmc.purpur.event.PlayerSetSpawnerTypeWithEggEvent((org.bukkit.entity.Player) context.getPlayer().getBukkitEntity(), bukkitBlock, (org.bukkit.block.CreatureSpawner) bukkitBlock.getState(), org.bukkit.entity.EntityType.fromName(entitytypes.getName())); -+ if (!event.callEvent()) { -+ return InteractionResult.FAIL; -+ } -+ entitytypes = EntityType.getFromBukkitType(event.getEntityType()); -+ // Purpur end -+ - tileentitymobspawner.setEntityId(entitytypes, world.getRandom()); - tileentity.setChanged(); - world.sendBlockUpdated(blockposition, iblockdata, iblockdata, 3); -diff --git a/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java b/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java -index de5bdceb4c8578fb972a2fd5ee0dfdae509e46dc..bcf63ccb6e679cb97d658780b2663aafa3568bcb 100644 ---- a/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java -+++ b/src/main/java/net/minecraft/world/item/ThrowablePotionItem.java -@@ -18,7 +18,7 @@ public class ThrowablePotionItem extends PotionItem { - if (!world.isClientSide) { - ThrownPotion thrownPotion = new ThrownPotion(world, user); - thrownPotion.setItem(itemStack); -- thrownPotion.shootFromRotation(user, user.getXRot(), user.getYRot(), -20.0F, 0.5F, 1.0F); -+ thrownPotion.shootFromRotation(user, user.getXRot(), user.getYRot(), -20.0F, 0.5F, (float) world.purpurConfig.throwablePotionProjectileOffset); // Purpur - // Paper start - com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) user.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemStack), (org.bukkit.entity.Projectile) thrownPotion.getBukkitEntity()); - if (event.callEvent() && world.addFreshEntity(thrownPotion)) { -diff --git a/src/main/java/net/minecraft/world/item/TridentItem.java b/src/main/java/net/minecraft/world/item/TridentItem.java -index 9365f886a23a71c41091b22d46896ff18a5a0635..d35432087c70ce66b74d1e27df19f462f22b1aa1 100644 ---- a/src/main/java/net/minecraft/world/item/TridentItem.java -+++ b/src/main/java/net/minecraft/world/item/TridentItem.java -@@ -77,11 +77,19 @@ public class TridentItem extends Item implements Vanishable { - if (k == 0) { - ThrownTrident entitythrowntrident = new ThrownTrident(world, entityhuman, stack); - -- entitythrowntrident.shootFromRotation(entityhuman, entityhuman.getXRot(), entityhuman.getYRot(), 0.0F, 2.5F + (float) k * 0.5F, 1.0F); -+ entitythrowntrident.shootFromRotation(entityhuman, entityhuman.getXRot(), entityhuman.getYRot(), 0.0F, 2.5F + (float) k * 0.5F, (float) world.purpurConfig.tridentProjectileOffset); // Purpur - if (entityhuman.getAbilities().instabuild) { - entitythrowntrident.pickup = AbstractArrow.Pickup.CREATIVE_ONLY; - } - -+ // Purpur start -+ int lootingLevel = EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.MOB_LOOTING, stack); -+ -+ if (lootingLevel > 0) { -+ entitythrowntrident.setLootingLevel(lootingLevel); -+ } -+ // Purpur end -+ - // CraftBukkit start - // Paper start - com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent event = new com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent((org.bukkit.entity.Player) entityhuman.getBukkitEntity(), org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(stack), (org.bukkit.entity.Projectile) entitythrowntrident.getBukkitEntity()); -@@ -130,6 +138,14 @@ public class TridentItem extends Item implements Vanishable { - f2 *= f6 / f5; - f3 *= f6 / f5; - f4 *= f6 / f5; -+ -+ // Purpur start -+ ItemStack chestItem = entityhuman.getItemBySlot(EquipmentSlot.CHEST); -+ if (chestItem.getItem() == Items.ELYTRA && world.purpurConfig.elytraDamagePerTridentBoost > 0) { -+ chestItem.hurtAndBreak(world.purpurConfig.elytraDamagePerTridentBoost, entityhuman, (entity) -> entity.broadcastBreakEvent(EquipmentSlot.CHEST)); -+ } -+ // Purpur end -+ - entityhuman.push((double) f2, (double) f3, (double) f4); - entityhuman.startAutoSpinAttack(20); - if (entityhuman.isOnGround()) { -diff --git a/src/main/java/net/minecraft/world/item/crafting/Ingredient.java b/src/main/java/net/minecraft/world/item/crafting/Ingredient.java -index 8d4aca59bd7518179520f4d4fb7137778e232d90..e24034d1ce4bb529de084aab69a531227e0c2f79 100644 ---- a/src/main/java/net/minecraft/world/item/crafting/Ingredient.java -+++ b/src/main/java/net/minecraft/world/item/crafting/Ingredient.java -@@ -39,6 +39,7 @@ public final class Ingredient implements Predicate { - @Nullable - private IntList stackingIds; - public boolean exact; // CraftBukkit -+ public Predicate predicate; - - public Ingredient(Stream entries) { - this.values = (Ingredient.Value[]) entries.toArray((i) -> { -@@ -50,7 +51,11 @@ public final class Ingredient implements Predicate { - if (this.itemStacks == null) { - this.itemStacks = (ItemStack[]) Arrays.stream(this.values).flatMap((recipeitemstack_provider) -> { - return recipeitemstack_provider.getItems().stream(); -- }).distinct().toArray((i) -> { -+ // PaperPR start -+ }).distinct().peek(stack -> { -+ stack.isExactRecipeIngredient = this.exact; -+ }).toArray((i) -> { -+ // PaperPR end - return new ItemStack[i]; - }); - } -@@ -64,6 +69,12 @@ public final class Ingredient implements Predicate { - } else if (this.isEmpty()) { - return itemstack.isEmpty(); - } else { -+ // Purpur start -+ if (predicate != null) { -+ return predicate.test(itemstack.asBukkitCopy()); -+ } -+ // Purpur end -+ - ItemStack[] aitemstack = this.getItems(); - int i = aitemstack.length; - -@@ -99,7 +110,13 @@ public final class Ingredient implements Predicate { - for (int j = 0; j < i; ++j) { - ItemStack itemstack = aitemstack1[j]; - -+ // PaperPR start -+ if (itemstack.isExactRecipeIngredient) { -+ this.stackingIds.add(StackedContents.getExactStackingIndex(itemstack)); -+ } else { -+ // PaperPR end - this.stackingIds.add(StackedContents.getStackingIndex(itemstack)); -+ } // PaperPR - } - - this.stackingIds.sort(IntComparators.NATURAL_COMPARATOR); -diff --git a/src/main/java/net/minecraft/world/item/enchantment/ArrowInfiniteEnchantment.java b/src/main/java/net/minecraft/world/item/enchantment/ArrowInfiniteEnchantment.java -index 518d85a13c37a2f7d32ca0718323181048559986..27512787b37381a5236b1b473e9ce3f06df8e2d0 100644 ---- a/src/main/java/net/minecraft/world/item/enchantment/ArrowInfiniteEnchantment.java -+++ b/src/main/java/net/minecraft/world/item/enchantment/ArrowInfiniteEnchantment.java -@@ -7,6 +7,14 @@ public class ArrowInfiniteEnchantment extends Enchantment { - super(weight, EnchantmentCategory.BOW, slotTypes); - } - -+ // Purpur start -+ @Override -+ public boolean canEnchant(net.minecraft.world.item.ItemStack stack) { -+ // we have to cheat the system because this class is loaded before purpur's config is loaded -+ return (org.purpurmc.purpur.PurpurConfig.allowCrossbowInfinity ? EnchantmentCategory.BOW_AND_CROSSBOW : EnchantmentCategory.BOW).canEnchant(stack.getItem()); -+ } -+ // Purpur end -+ - @Override - public int getMinCost(int level) { - return 20; -@@ -19,6 +27,6 @@ public class ArrowInfiniteEnchantment extends Enchantment { - - @Override - public boolean checkCompatibility(Enchantment other) { -- return other instanceof MendingEnchantment ? false : super.checkCompatibility(other); -+ return other instanceof MendingEnchantment ? org.purpurmc.purpur.PurpurConfig.allowInfinityMending : super.checkCompatibility(other); - } - } -diff --git a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentCategory.java b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentCategory.java -index 246516e67db0b8b197b287c067d5a0163d8bde22..fc2c35f57436371cb0111aedfd289ac95d506d07 100644 ---- a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentCategory.java -+++ b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentCategory.java -@@ -121,6 +121,20 @@ public enum EnchantmentCategory { - public boolean canEnchant(Item item) { - return item instanceof Vanishable || Block.byItem(item) instanceof Vanishable || BREAKABLE.canEnchant(item); - } -+ // Purpur start -+ }, -+ BOW_AND_CROSSBOW { -+ @Override -+ public boolean canEnchant(Item item) { -+ return item instanceof BowItem || item instanceof CrossbowItem; -+ } -+ }, -+ WEAPON_AND_SHEARS { -+ @Override -+ public boolean canEnchant(Item item) { -+ return WEAPON.canEnchant(item) || item instanceof net.minecraft.world.item.ShearsItem; -+ } -+ // Purpur end - }; - - public abstract boolean canEnchant(Item item); -diff --git a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java -index ecf640b00007a386290f8dfe9935a8aa610079fd..1eec84e217f6dc929091fa7451cd235ef3623822 100644 ---- a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java -+++ b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java -@@ -46,7 +46,7 @@ public class EnchantmentHelper { - } - - public static int getEnchantmentLevel(CompoundTag nbt) { -- return Mth.clamp(nbt.getInt("lvl"), 0, 255); -+ return Mth.clamp(nbt.getInt("lvl"), 0, (org.purpurmc.purpur.PurpurConfig.clampEnchantLevels) ? 255 : 32767); // Purpur - } - - @Nullable -@@ -278,6 +278,29 @@ public class EnchantmentHelper { - return getItemEnchantmentLevel(Enchantments.CHANNELING, stack) > 0; - } - -+ // Purpur start -+ @Nullable -+ public static Map.Entry getMostDamagedEquipment(Enchantment enchantment, LivingEntity entity) { -+ Map map = enchantment.getSlotItems(entity); -+ if (map.isEmpty()) { -+ return null; -+ } -+ Map.Entry item = null; -+ float maxPercent = 0F; -+ for (Map.Entry entry : map.entrySet()) { -+ ItemStack itemstack = entry.getValue(); -+ if (!itemstack.isEmpty() && itemstack.isDamaged() && getItemEnchantmentLevel(enchantment, itemstack) > 0) { -+ float percent = itemstack.getDamagePercent(); -+ if (item == null || percent > maxPercent) { -+ item = entry; -+ maxPercent = percent; -+ } -+ } -+ } -+ return item; -+ } -+ // Purpur end -+ - @Nullable - public static Map.Entry getRandomItemWith(Enchantment enchantment, LivingEntity entity) { - return getRandomItemWith(enchantment, entity, (stack) -> { -diff --git a/src/main/java/net/minecraft/world/item/enchantment/LootBonusEnchantment.java b/src/main/java/net/minecraft/world/item/enchantment/LootBonusEnchantment.java -index 4007c16550683e23b396dfdff29530a82523fe05..8fe09c13643d99639fb242da4367c42ef31b38b4 100644 ---- a/src/main/java/net/minecraft/world/item/enchantment/LootBonusEnchantment.java -+++ b/src/main/java/net/minecraft/world/item/enchantment/LootBonusEnchantment.java -@@ -7,6 +7,14 @@ public class LootBonusEnchantment extends Enchantment { - super(weight, target, slotTypes); - } - -+ // Purpur start -+ @Override -+ public boolean canEnchant(net.minecraft.world.item.ItemStack stack) { -+ // we have to cheat the system because this class is loaded before purpur's config is loaded -+ return (org.purpurmc.purpur.PurpurConfig.allowShearsLooting && this.category == EnchantmentCategory.WEAPON ? EnchantmentCategory.WEAPON_AND_SHEARS : this.category).canEnchant(stack.getItem()); -+ } -+ // Purpur end -+ - @Override - public int getMinCost(int level) { - return 15 + (level - 1) * 9; -diff --git a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java -index fd50d1c2435b82215bc5b3fdbe5044d426bc342e..68ffea572045634f1ad67a6954d480e6ae7833f5 100644 ---- a/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java -+++ b/src/main/java/net/minecraft/world/item/trading/MerchantOffer.java -@@ -132,7 +132,12 @@ public class MerchantOffer { - } - - public void updateDemand() { -- this.demand = Math.max(0, this.demand + this.uses - (this.maxUses - this.uses)); // Paper -+ // Purpur start -+ this.updateDemand(0); -+ } -+ public void updateDemand(int minimumDemand) { -+ this.demand = Math.max(minimumDemand, this.demand + this.uses - (this.maxUses - this.uses)); -+ // Purpur end - } - - public ItemStack assemble() { -diff --git a/src/main/java/net/minecraft/world/level/BaseSpawner.java b/src/main/java/net/minecraft/world/level/BaseSpawner.java -index 31ac0e5ca26c7bdfa9b710d0bb78d846ddf6863e..feb65fc9ee04141fe6f77400660442ed207547a1 100644 ---- a/src/main/java/net/minecraft/world/level/BaseSpawner.java -+++ b/src/main/java/net/minecraft/world/level/BaseSpawner.java -@@ -55,6 +55,7 @@ public abstract class BaseSpawner { - } - - public boolean isNearPlayer(Level world, BlockPos pos) { -+ if (world.purpurConfig.spawnerDeactivateByRedstone && world.hasNeighborSignal(pos)) return false; // Purpur - return world.hasNearbyAlivePlayerThatAffectsSpawning((double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, (double) this.requiredPlayerRange); // Paper - Affects Spawning API - } - -diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java -index 3b959f42d958bf0f426853aee56753d6c455fcdb..d17abb283ea818244df0379d6b57fc634071e0b9 100644 ---- a/src/main/java/net/minecraft/world/level/EntityGetter.java -+++ b/src/main/java/net/minecraft/world/level/EntityGetter.java -@@ -154,7 +154,7 @@ public interface EntityGetter { - - default boolean hasNearbyAlivePlayer(double x, double y, double z, double range) { - for(Player player : this.players()) { -- if (EntitySelector.NO_SPECTATORS.test(player) && EntitySelector.LIVING_ENTITY_STILL_ALIVE.test(player)) { -+ if (EntitySelector.NO_SPECTATORS.test(player) && EntitySelector.LIVING_ENTITY_STILL_ALIVE.test(player) && EntitySelector.notAfk.test(player)) { - double d = player.distanceToSqr(x, y, z); - if (range < 0.0D || d < range * range) { - return true; -diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java -index c321215a39cf9f69e90c2c6e139963f60877286b..65ff0b7a3866c39ffa6a7b9c9b8fe4b35092ed22 100644 ---- a/src/main/java/net/minecraft/world/level/Explosion.java -+++ b/src/main/java/net/minecraft/world/level/Explosion.java -@@ -86,7 +86,7 @@ public class Explosion { - this.hitPlayers = Maps.newHashMap(); - this.level = world; - this.source = entity; -- this.radius = (float) Math.max(power, 0.0); // CraftBukkit - clamp bad values -+ this.radius = (float) (world == null || world.purpurConfig.explosionClampRadius ? Math.max(power, 0.0) : power); // CraftBukkit - clamp bad values // Purpur - this.x = x; - this.y = y; - this.z = z; -@@ -137,10 +137,27 @@ public class Explosion { - - public void explode() { - // CraftBukkit start -- if (this.radius < 0.1F) { -+ if ((this.level == null || this.level.purpurConfig.explosionClampRadius) && this.radius < 0.1F) { // Purpur - return; - } - // CraftBukkit end -+ -+ // Purpur start - add PreExplodeEvents -+ if(this.source != null){ -+ Location location = new Location(this.level.getWorld(), this.x, this.y, this.z); -+ if(!new org.purpurmc.purpur.event.entity.PreEntityExplodeEvent(this.source.getBukkitEntity(), location, this.blockInteraction == Explosion.BlockInteraction.DESTROY_WITH_DECAY ? 1.0F / this.radius : 1.0F).callEvent()) { -+ this.wasCanceled = true; -+ return; -+ } -+ }else { -+ Location location = new Location(this.level.getWorld(), this.x, this.y, this.z); -+ if(!new org.purpurmc.purpur.event.PreBlockExplodeEvent(location.getBlock(), this.blockInteraction == Explosion.BlockInteraction.DESTROY_WITH_DECAY ? 1.0F / this.radius : 1.0F).callEvent()) { -+ this.wasCanceled = true; -+ return; -+ } -+ } -+ //Purpur end -+ - this.level.gameEvent(this.source, GameEvent.EXPLODE, new Vec3(this.x, this.y, this.z)); - Set set = Sets.newHashSet(); - boolean flag = true; -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index b19e842be160748a6969e498952eb02ffece2ecc..e3cf198f7fdcdf63eb686b5016998f2e0f0d7705 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -175,6 +175,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - // Gale end - Gale configuration - - public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray -+ public final org.purpurmc.purpur.PurpurWorldConfig purpurConfig; // Purpur - public final co.aikar.timings.WorldTimingsHandler timings; // Paper - public static BlockPos lastPhysicsProblem; // Spigot - private org.spigotmc.TickLimiter entityLimiter; -@@ -194,6 +195,49 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - } - // Paper end - fix and optimise world upgrading - -+ // Purpur start -+ private com.google.common.cache.Cache playerBreedingCooldowns; -+ -+ private com.google.common.cache.Cache getNewBreedingCooldownCache() { -+ return com.google.common.cache.CacheBuilder.newBuilder().expireAfterWrite(this.purpurConfig.animalBreedingCooldownSeconds, java.util.concurrent.TimeUnit.SECONDS).build(); -+ } -+ -+ public void resetBreedingCooldowns() { -+ this.playerBreedingCooldowns = this.getNewBreedingCooldownCache(); -+ } -+ -+ public boolean hasBreedingCooldown(java.util.UUID player, Class animalType) { // Purpur -+ return this.playerBreedingCooldowns.getIfPresent(new BreedingCooldownPair(player, animalType)) != null; -+ } -+ -+ public void addBreedingCooldown(java.util.UUID player, Class animalType) { -+ this.playerBreedingCooldowns.put(new BreedingCooldownPair(player, animalType), new Object()); -+ } -+ -+ private static final class BreedingCooldownPair { -+ private final java.util.UUID playerUUID; -+ private final Class animalType; -+ -+ public BreedingCooldownPair(java.util.UUID playerUUID, Class animalType) { -+ this.playerUUID = playerUUID; -+ this.animalType = animalType; -+ } -+ -+ @Override -+ public boolean equals(Object o) { -+ if (this == o) return true; -+ if (o == null || getClass() != o.getClass()) return false; -+ BreedingCooldownPair that = (BreedingCooldownPair) o; -+ return playerUUID.equals(that.playerUUID) && animalType.equals(that.animalType); -+ } -+ -+ @Override -+ public int hashCode() { -+ return java.util.Objects.hash(playerUUID, animalType); -+ } -+ } -+ // Purpur end -+ - public CraftWorld getWorld() { - return this.world; - } -@@ -287,6 +331,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot - this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - this.galeConfig = galeWorldConfigCreator.apply(this.spigotConfig); // Gale - Gale configuration -+ this.purpurConfig = new org.purpurmc.purpur.PurpurWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName(), env); // Purpur -+ this.playerBreedingCooldowns = this.getNewBreedingCooldownCache(); // Purpur - this.generator = gen; - this.world = new CraftWorld((ServerLevel) this, gen, biomeProvider, env); - -@@ -1629,4 +1675,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - return null; - } - // Paper end -+ -+ // Purpur start -+ public boolean isNether() { -+ return getWorld().getEnvironment() == org.bukkit.World.Environment.NETHER; -+ } -+ -+ public boolean isTheEnd() { -+ return getWorld().getEnvironment() == org.bukkit.World.Environment.THE_END; -+ } -+ // Purpur end - } -diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -index e0faab041aa2cca41f56fc065d04ca572a36a329..c37313e62fccdf1bb05b54b12e8ff6828de801ca 100644 ---- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java -+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -@@ -258,7 +258,7 @@ public final class NaturalSpawner { - blockposition_mutableblockposition.set(l, i, i1); - double d0 = (double) l + 0.5D; - double d1 = (double) i1 + 0.5D; -- Player entityhuman = (chunk instanceof LevelChunk) ? ((LevelChunk)chunk).findNearestPlayer(d0, i, d1, 576.0D, net.minecraft.world.entity.EntitySelector.NO_SPECTATORS) : world.getNearestPlayer(d0, (double) i, d1, -1.0D, false); // Paper - use chunk's player cache to optimize search in range -+ Player entityhuman = (chunk instanceof LevelChunk) ? ((LevelChunk)chunk).findNearestPlayer(d0, i, d1, 576.0D, world.purpurConfig.mobSpawningIgnoreCreativePlayers ? net.minecraft.world.entity.EntitySelector.NO_CREATIVE_OR_SPECTATOR : net.minecraft.world.entity.EntitySelector.NO_SPECTATORS) : world.getNearestPlayer(d0, (double) i, d1, -1.0D, world.purpurConfig.mobSpawningIgnoreCreativePlayers); // Paper - use chunk's player cache to optimize search in range // Purpur - - if (entityhuman != null) { - double d2 = entityhuman.distanceToSqr(d0, (double) i, d1); -diff --git a/src/main/java/net/minecraft/world/level/block/AnvilBlock.java b/src/main/java/net/minecraft/world/level/block/AnvilBlock.java -index 5c5a3b169795bf8a527b316c666cbc2105c66622..020afeca950d2c7fb6c7b179d424548fd90f8b0d 100644 ---- a/src/main/java/net/minecraft/world/level/block/AnvilBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/AnvilBlock.java -@@ -55,6 +55,54 @@ public class AnvilBlock extends FallingBlock { - - @Override - public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { -+ // Purpur start - repairable/damageable anvils -+ if (world.purpurConfig.anvilRepairIngotsAmount > 0) { -+ net.minecraft.world.item.ItemStack itemstack = player.getItemInHand(hand); -+ if (itemstack.is(net.minecraft.world.item.Items.IRON_INGOT)) { -+ if (itemstack.getCount() < world.purpurConfig.anvilRepairIngotsAmount) { -+ // not enough iron ingots, play "error" sound and consume -+ world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_HIT, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F); -+ return InteractionResult.CONSUME; -+ } -+ if (state.is(Blocks.DAMAGED_ANVIL)) { -+ world.setBlock(pos, Blocks.CHIPPED_ANVIL.defaultBlockState().setValue(FACING, state.getValue(FACING)), 3); -+ } else if (state.is(Blocks.CHIPPED_ANVIL)) { -+ world.setBlock(pos, Blocks.ANVIL.defaultBlockState().setValue(FACING, state.getValue(FACING)), 3); -+ } else if (state.is(Blocks.ANVIL)) { -+ // anvil is already fully repaired, play "error" sound and consume -+ world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_HIT, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F); -+ return InteractionResult.CONSUME; -+ } -+ if (!player.getAbilities().instabuild) { -+ itemstack.shrink(world.purpurConfig.anvilRepairIngotsAmount); -+ } -+ world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_PLACE, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F); -+ return InteractionResult.CONSUME; -+ } -+ } -+ if (world.purpurConfig.anvilDamageObsidianAmount > 0) { -+ net.minecraft.world.item.ItemStack itemstack = player.getItemInHand(hand); -+ if (itemstack.is(net.minecraft.world.item.Items.OBSIDIAN)) { -+ if (itemstack.getCount() < world.purpurConfig.anvilDamageObsidianAmount) { -+ // not enough obsidian, play "error" sound and consume -+ world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_HIT, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F); -+ return InteractionResult.CONSUME; -+ } -+ if (state.is(Blocks.DAMAGED_ANVIL)) { -+ world.destroyBlock(pos, false); -+ } else if (state.is(Blocks.CHIPPED_ANVIL)) { -+ world.setBlock(pos, Blocks.DAMAGED_ANVIL.defaultBlockState().setValue(FACING, state.getValue(FACING)), 3); -+ } else if (state.is(Blocks.ANVIL)) { -+ world.setBlock(pos, Blocks.CHIPPED_ANVIL.defaultBlockState().setValue(FACING, state.getValue(FACING)), 3); -+ } -+ if (!player.getAbilities().instabuild) { -+ itemstack.shrink(world.purpurConfig.anvilDamageObsidianAmount); -+ } -+ world.playSound(null, pos, net.minecraft.sounds.SoundEvents.ANVIL_LAND, net.minecraft.sounds.SoundSource.BLOCKS, 1.0F, 1.0F); -+ return InteractionResult.CONSUME; -+ } -+ } -+ // Purpur end - if (world.isClientSide) { - return InteractionResult.SUCCESS; - } else { -diff --git a/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java b/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java -index 95b53450a807fccfa55b59852da52785b8cf3e3d..c69f1d23979a0759472d22760a18d986b2d979b6 100644 ---- a/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/AzaleaBlock.java -@@ -43,6 +43,20 @@ public class AzaleaBlock extends BushBlock implements BonemealableBlock { - - @Override - public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { -+ // Purpur start -+ growTree(world, random, pos, state); -+ } -+ -+ @Override -+ public void randomTick(net.minecraft.world.level.block.state.BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { -+ double chance = state.getBlock() == Blocks.FLOWERING_AZALEA ? world.purpurConfig.floweringAzaleaGrowthChance : world.purpurConfig.azaleaGrowthChance; -+ if (chance > 0.0D && world.getMaxLocalRawBrightness(pos.above()) > 9 && random.nextDouble() < chance) { -+ growTree(world, random, pos, state); -+ } -+ } -+ -+ private void growTree(ServerLevel world, RandomSource random, BlockPos pos, net.minecraft.world.level.block.state.BlockState state) { -+ // Purpur end - TREE_GROWER.growTree(world, world.getChunkSource().getGenerator(), pos, state, random); - } - -diff --git a/src/main/java/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java b/src/main/java/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java -index 3d2b34c5a7c9b00c1164b4f89c2cbff81fc460eb..b5505e926e5cdb447de68e8eb8e46c97eb988e27 100644 ---- a/src/main/java/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BaseCoralPlantTypeBlock.java -@@ -35,6 +35,7 @@ public class BaseCoralPlantTypeBlock extends Block implements SimpleWaterloggedB - } - - protected static boolean scanForWater(BlockState state, BlockGetter world, BlockPos pos) { -+ if (!((net.minecraft.world.level.LevelAccessor) world).getMinecraftWorld().purpurConfig.coralDieOutsideWater) return true; // Purpur - if (state.getValue(WATERLOGGED)) { - return true; - } else { -diff --git a/src/main/java/net/minecraft/world/level/block/BedBlock.java b/src/main/java/net/minecraft/world/level/block/BedBlock.java -index d1d5363ab1742add8ff45507a303106f4d65f52f..19d31064eb271ee02115a75cde383796c899e7f7 100644 ---- a/src/main/java/net/minecraft/world/level/block/BedBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BedBlock.java -@@ -97,7 +97,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock - - Vec3 vec3d = pos.getCenter(); - -- world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, explodedBlockState), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); -+ if (world.purpurConfig.bedExplode) world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, explodedBlockState), (ExplosionDamageCalculator) null, vec3d, (float) world.purpurConfig.bedExplosionPower, world.purpurConfig.bedExplosionFire, world.purpurConfig.bedExplosionEffect); // Purpur - return InteractionResult.SUCCESS; - } else if ((Boolean) state.getValue(BedBlock.OCCUPIED)) { - if (!BedBlock.canSetSpawn(world)) return this.explodeBed(state, world, pos); // Paper - check explode first -@@ -150,7 +150,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock - - Vec3 vec3d = blockposition.getCenter(); - -- world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, explodedBlockState), (ExplosionDamageCalculator) null, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); -+ if (world.purpurConfig.bedExplode) world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, explodedBlockState), (ExplosionDamageCalculator) null, vec3d, (float) world.purpurConfig.bedExplosionPower, world.purpurConfig.bedExplosionFire, world.purpurConfig.bedExplosionEffect); // Purpur - return InteractionResult.SUCCESS; - } - } -@@ -174,7 +174,7 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock - - @Override - public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) { -- super.fallOn(world, state, pos, entity, fallDistance * 0.5F); -+ super.fallOn(world, state, pos, entity, fallDistance); // Purpur - } - - @Override -diff --git a/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java b/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java -index 8537581e7ca1f4efb492a2e734f46f947f36cffa..5f89229ff68d923c5cdee40e72e379ba7024f961 100644 ---- a/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java -@@ -236,7 +236,7 @@ public class BigDripleafBlock extends HorizontalDirectionalBlock implements Bone - BigDripleafBlock.playTiltSound(world, blockposition, soundeffect); - } - -- int i = BigDripleafBlock.DELAY_UNTIL_NEXT_TILT_STATE.getInt(tilt); -+ int i = world.purpurConfig.bigDripleafTiltDelay.getOrDefault(tilt, -1); // Purpur - - if (i != -1) { - world.scheduleTick(blockposition, (Block) this, i); -diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java -index 52743a5865997506b5a53fdfd99cdbab67ae3d3f..11b9217ef2022b5a1e616c30a92786f909a66a6e 100644 ---- a/src/main/java/net/minecraft/world/level/block/Block.java -+++ b/src/main/java/net/minecraft/world/level/block/Block.java -@@ -65,6 +65,13 @@ import net.minecraft.world.phys.shapes.Shapes; - import net.minecraft.world.phys.shapes.VoxelShape; - import org.slf4j.Logger; - -+// Purpur start -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.nbt.ListTag; -+import net.minecraft.nbt.StringTag; -+import net.minecraft.world.Nameable; -+// Purpur end -+ - public class Block extends BlockBehaviour implements ItemLike { - - private static final Logger LOGGER = LogUtils.getLogger(); -@@ -92,6 +99,10 @@ public class Block extends BlockBehaviour implements ItemLike { - public static final int UPDATE_LIMIT = 512; - protected final StateDefinition stateDefinition; - private BlockState defaultBlockState; -+ // Purpur start -+ public float fallDamageMultiplier = 1.0F; -+ public float fallDistanceMultiplier = 1.0F; -+ // Purpur end - // Paper start - public final boolean isDestroyable() { - return io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits || -@@ -328,7 +339,7 @@ public class Block extends BlockBehaviour implements ItemLike { - public static void dropResources(BlockState state, LevelAccessor world, BlockPos pos, @Nullable BlockEntity blockEntity) { - if (world instanceof ServerLevel) { - Block.getDrops(state, (ServerLevel) world, pos, blockEntity).forEach((itemstack) -> { -- Block.popResource((ServerLevel) world, pos, itemstack); -+ Block.popResource((ServerLevel) world, pos, applyDisplayNameAndLoreFromTile(itemstack, blockEntity)); // Purpur - }); - state.spawnAfterBreak((ServerLevel) world, pos, ItemStack.EMPTY, true); - } -@@ -344,7 +355,7 @@ public class Block extends BlockBehaviour implements ItemLike { - io.papermc.paper.event.block.BlockBreakBlockEvent event = new io.papermc.paper.event.block.BlockBreakBlockEvent(org.bukkit.craftbukkit.block.CraftBlock.at(world, pos), org.bukkit.craftbukkit.block.CraftBlock.at(world, source), items); - event.callEvent(); - for (var drop : event.getDrops()) { -- popResource(world.getMinecraftWorld(), pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop)); -+ popResource(world.getMinecraftWorld(), pos, applyDisplayNameAndLoreFromTile(org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop), blockEntity)); // Purpur - } - state.spawnAfterBreak(world.getMinecraftWorld(), pos, ItemStack.EMPTY, true); - } -@@ -355,13 +366,53 @@ public class Block extends BlockBehaviour implements ItemLike { - public static void dropResources(BlockState state, Level world, BlockPos pos, @Nullable BlockEntity blockEntity, Entity entity, ItemStack tool) { - if (world instanceof ServerLevel) { - Block.getDrops(state, (ServerLevel) world, pos, blockEntity, entity, tool).forEach((itemstack1) -> { -- Block.popResource(world, pos, itemstack1); -+ Block.popResource(world, pos, applyDisplayNameAndLoreFromTile(itemstack1, blockEntity)); // Purpur - }); - state.spawnAfterBreak((ServerLevel) world, pos, tool, true); - } - - } - -+ // Purpur start -+ private static ItemStack applyDisplayNameAndLoreFromTile(ItemStack stack, BlockEntity blockEntity) { -+ if (stack.getItem() instanceof BlockItem) { -+ if (blockEntity != null && blockEntity.getLevel() instanceof ServerLevel && blockEntity.getLevel().purpurConfig.persistentTileEntityDisplayNames) { -+ String name = blockEntity.getPersistentDisplayName(); -+ ListTag lore = blockEntity.getPersistentLore(); -+ if (blockEntity instanceof Nameable) { -+ Nameable namedTile = (Nameable) blockEntity; -+ if (namedTile.hasCustomName()) { -+ name = Component.Serializer.toJson(namedTile.getCustomName()); -+ } -+ } -+ -+ if (name != null || lore != null) { -+ CompoundTag display = stack.getTagElement("display"); -+ if (display == null) { -+ display = new CompoundTag(); -+ } -+ -+ if (name != null) { -+ display.put("Name", StringTag.valueOf(name)); -+ } -+ if (lore != null) { -+ display.put("Lore", lore); -+ } -+ -+ CompoundTag tag = stack.getTag(); -+ if (tag == null) { -+ tag = new CompoundTag(); -+ } -+ tag.put("display", display); -+ -+ stack.setTag(tag); -+ } -+ } -+ } -+ return stack; -+ } -+ // Purpur end -+ - public static void popResource(Level world, BlockPos pos, ItemStack stack) { - double d0 = (double) EntityType.ITEM.getHeight() / 2.0D; - double d1 = (double) pos.getX() + 0.5D + Mth.nextDouble(world.random, -0.25D, 0.25D); -@@ -437,7 +488,17 @@ public class Block extends BlockBehaviour implements ItemLike { - Block.dropResources(state, world, pos, blockEntity, player, tool); - } - -- public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack itemStack) {} -+ // Purpur start -+ @Nullable protected LivingEntity placer = null; -+ -+ public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack itemStack) { -+ this.placer = placer; -+ } -+ -+ public void forgetPlacer() { -+ this.placer = null; -+ } -+ // Purpur end - - public boolean isPossibleToRespawnInThis() { - return !this.material.isSolid() && !this.material.isLiquid(); -@@ -456,7 +517,7 @@ public class Block extends BlockBehaviour implements ItemLike { - } - - public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) { -- entity.causeFallDamage(fallDistance, 1.0F, entity.damageSources().fall()); -+ entity.causeFallDamage(fallDistance * fallDistanceMultiplier, fallDamageMultiplier, entity.damageSources().fall()); // Purpur - } - - public void updateEntityAfterFallOn(BlockGetter world, Entity entity) { -diff --git a/src/main/java/net/minecraft/world/level/block/Blocks.java b/src/main/java/net/minecraft/world/level/block/Blocks.java -index f148c7d2954cc17377d0da4af03ea2c1c9397a52..4afc4670f9b00a4087410ec366fe45fe2f2734dc 100644 ---- a/src/main/java/net/minecraft/world/level/block/Blocks.java -+++ b/src/main/java/net/minecraft/world/level/block/Blocks.java -@@ -1087,8 +1087,8 @@ public class Blocks { - public static final Block CAVE_VINES = register("cave_vines", new CaveVinesBlock(BlockBehaviour.Properties.of(Material.PLANT).randomTicks().noCollission().lightLevel(CaveVines.emission(14)).instabreak().sound(SoundType.CAVE_VINES))); - public static final Block CAVE_VINES_PLANT = register("cave_vines_plant", new CaveVinesPlantBlock(BlockBehaviour.Properties.of(Material.PLANT).noCollission().lightLevel(CaveVines.emission(14)).instabreak().sound(SoundType.CAVE_VINES))); - public static final Block SPORE_BLOSSOM = register("spore_blossom", new SporeBlossomBlock(BlockBehaviour.Properties.of(Material.PLANT).instabreak().noCollission().sound(SoundType.SPORE_BLOSSOM))); -- public static final Block AZALEA = register("azalea", new AzaleaBlock(BlockBehaviour.Properties.of(Material.PLANT).instabreak().sound(SoundType.AZALEA).noOcclusion())); -- public static final Block FLOWERING_AZALEA = register("flowering_azalea", new AzaleaBlock(BlockBehaviour.Properties.of(Material.PLANT).instabreak().sound(SoundType.FLOWERING_AZALEA).noOcclusion())); -+ public static final Block AZALEA = register("azalea", new AzaleaBlock(BlockBehaviour.Properties.of(Material.PLANT).randomTicks().instabreak().sound(SoundType.AZALEA).noOcclusion())); // Purpur -+ public static final Block FLOWERING_AZALEA = register("flowering_azalea", new AzaleaBlock(BlockBehaviour.Properties.of(Material.PLANT).randomTicks().instabreak().sound(SoundType.FLOWERING_AZALEA).noOcclusion())); // Purpur - public static final Block MOSS_CARPET = register("moss_carpet", new CarpetBlock(BlockBehaviour.Properties.of(Material.PLANT, MaterialColor.COLOR_GREEN).strength(0.1F).sound(SoundType.MOSS_CARPET))); - public static final Block PINK_PETALS = register("pink_petals", new PinkPetalsBlock(BlockBehaviour.Properties.of(Material.PLANT).noCollission().sound(SoundType.PINK_PETALS).requiredFeatures(FeatureFlags.UPDATE_1_20))); - public static final Block MOSS_BLOCK = register("moss_block", new MossBlock(BlockBehaviour.Properties.of(Material.MOSS, MaterialColor.COLOR_GREEN).strength(0.1F).sound(SoundType.MOSS))); -@@ -1153,7 +1153,7 @@ public class Blocks { - } - - private static Boolean ocelotOrParrot(BlockState state, BlockGetter world, BlockPos pos, EntityType type) { -- return (boolean)type == EntityType.OCELOT || type == EntityType.PARROT; -+ return type == EntityType.OCELOT || type == EntityType.PARROT; // Purpur - decompile error - } - - private static BedBlock bed(DyeColor color) { -diff --git a/src/main/java/net/minecraft/world/level/block/BuddingAmethystBlock.java b/src/main/java/net/minecraft/world/level/block/BuddingAmethystBlock.java -index bedccb8717d08d5a60058445b04ddff149e7d36c..5293ffca3da94c9c485a87d1232b6a902fcafd6a 100644 ---- a/src/main/java/net/minecraft/world/level/block/BuddingAmethystBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BuddingAmethystBlock.java -@@ -53,4 +53,14 @@ public class BuddingAmethystBlock extends AmethystBlock { - public static boolean canClusterGrowAtState(BlockState state) { - return state.isAir() || state.is(Blocks.WATER) && state.getFluidState().getAmount() == 8; - } -+ -+ // Purpur start -+ @Override -+ public void playerDestroy(net.minecraft.world.level.Level level, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, net.minecraft.world.level.block.entity.BlockEntity blockEntity, net.minecraft.world.item.ItemStack stack) { -+ if (level.purpurConfig.buddingAmethystSilkTouch && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.SILK_TOUCH, stack) > 0) { -+ popResource(level, pos, net.minecraft.world.item.Items.BUDDING_AMETHYST.getDefaultInstance()); -+ } -+ super.playerDestroy(level, player, pos, state, blockEntity, stack); -+ } -+ // Purpur end - } -diff --git a/src/main/java/net/minecraft/world/level/block/BushBlock.java b/src/main/java/net/minecraft/world/level/block/BushBlock.java -index 03fde6e47c4a347c62fe9b4a3351769aedf874f6..ca906b0250e5332f7ececf1419ca6d2c1d385adc 100644 ---- a/src/main/java/net/minecraft/world/level/block/BushBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/BushBlock.java -@@ -48,4 +48,24 @@ public class BushBlock extends Block { - public boolean isPathfindable(BlockState state, BlockGetter world, BlockPos pos, PathComputationType type) { - return type == PathComputationType.AIR && !this.hasCollision ? true : super.isPathfindable(state, world, pos, type); - } -+ -+ // Purpur start -+ public void playerDestroyAndReplant(net.minecraft.world.level.Level world, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @javax.annotation.Nullable net.minecraft.world.level.block.entity.BlockEntity blockEntity, net.minecraft.world.item.ItemStack itemInHand, net.minecraft.world.level.ItemLike itemToReplant) { -+ player.awardStat(net.minecraft.stats.Stats.BLOCK_MINED.get(this)); -+ player.causeFoodExhaustion(0.005F, org.bukkit.event.entity.EntityExhaustionEvent.ExhaustionReason.BLOCK_MINED); -+ java.util.List dropList = Block.getDrops(state, (net.minecraft.server.level.ServerLevel) world, pos, blockEntity, player, itemInHand); -+ -+ boolean planted = false; -+ for (net.minecraft.world.item.ItemStack itemToDrop : dropList) { -+ if (!planted && itemToDrop.getItem() == itemToReplant) { -+ world.setBlock(pos, defaultBlockState(), 3); -+ itemToDrop.setCount(itemToDrop.getCount() - 1); -+ planted = true; -+ } -+ Block.popResource(world, pos, itemToDrop); -+ } -+ -+ state.spawnAfterBreak((net.minecraft.server.level.ServerLevel) world, pos, itemInHand, true); -+ } -+ // Purpur end - } -diff --git a/src/main/java/net/minecraft/world/level/block/CactusBlock.java b/src/main/java/net/minecraft/world/level/block/CactusBlock.java -index 7579946ce222b6ab3685a7fd9821bcd5a4babe33..ae2ac1c24c1e502a1968a3008273096281d5f1ca 100644 ---- a/src/main/java/net/minecraft/world/level/block/CactusBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CactusBlock.java -@@ -22,7 +22,7 @@ import net.minecraft.world.phys.shapes.CollisionContext; - import net.minecraft.world.phys.shapes.VoxelShape; - import org.bukkit.craftbukkit.event.CraftEventFactory; // CraftBukkit - --public class CactusBlock extends Block { -+public class CactusBlock extends Block implements BonemealableBlock { // Purpur - - public static final IntegerProperty AGE = BlockStateProperties.AGE_15; - public static final int MAX_AGE = 15; -@@ -109,7 +109,7 @@ public class CactusBlock extends Block { - BlockState iblockdata2 = world.getBlockState(pos.relative(enumdirection)); - - material = iblockdata2.getMaterial(); -- } while (!material.isSolid() && !world.getFluidState(pos.relative(enumdirection)).is(FluidTags.LAVA)); -+ } while ((!world.getWorldBorder().world.purpurConfig.cactusBreaksFromSolidNeighbors || !material.isSolid()) && !world.getFluidState(pos.relative(enumdirection)).is(FluidTags.LAVA)); // Purpur - - return false; - } -@@ -131,4 +131,34 @@ public class CactusBlock extends Block { - public boolean isPathfindable(BlockState state, BlockGetter world, BlockPos pos, PathComputationType type) { - return false; - } -+ -+ // Purpur start -+ @Override -+ public boolean isValidBonemealTarget(LevelReader world, BlockPos pos, BlockState state, boolean isClient) { -+ if (!((Level) world).purpurConfig.cactusAffectedByBonemeal || !world.isEmptyBlock(pos.above())) return false; -+ -+ int cactusHeight = 0; -+ while (world.getBlockState(pos.below(cactusHeight)).is(this)) { -+ cactusHeight++; -+ } -+ -+ return cactusHeight < ((Level) world).paperConfig().maxGrowthHeight.cactus; -+ } -+ -+ @Override -+ public boolean isBonemealSuccess(Level world, RandomSource random, BlockPos pos, BlockState state) { -+ return true; -+ } -+ -+ @Override -+ public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { -+ int cactusHeight = 0; -+ while (world.getBlockState(pos.below(cactusHeight)).is(this)) { -+ cactusHeight++; -+ } -+ for (int i = 0; i <= world.paperConfig().maxGrowthHeight.cactus - cactusHeight; i++) { -+ world.setBlockAndUpdate(pos.above(i), state.setValue(CactusBlock.AGE, 0)); -+ } -+ } -+ // Purpur end - } -diff --git a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java -index 219c87dcf065e86512f330fbeec59e55f4675083..f8fd3b320494d1c1e8ee3d170f2feebd152230fa 100644 ---- a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java -@@ -122,7 +122,7 @@ public class CampfireBlock extends BaseEntityBlock implements SimpleWaterloggedB - BlockPos blockposition = ctx.getClickedPos(); - boolean flag = world.getFluidState(blockposition).getType() == Fluids.WATER; - -- return (BlockState) ((BlockState) ((BlockState) ((BlockState) this.defaultBlockState().setValue(CampfireBlock.WATERLOGGED, flag)).setValue(CampfireBlock.SIGNAL_FIRE, this.isSmokeSource(world.getBlockState(blockposition.below())))).setValue(CampfireBlock.LIT, !flag)).setValue(CampfireBlock.FACING, ctx.getHorizontalDirection()); -+ return (BlockState) ((BlockState) ((BlockState) ((BlockState) this.defaultBlockState().setValue(CampfireBlock.WATERLOGGED, flag)).setValue(CampfireBlock.SIGNAL_FIRE, this.isSmokeSource(world.getBlockState(blockposition.below())))).setValue(CampfireBlock.LIT, world.purpurConfig.campFireLitWhenPlaced ? !flag : world.purpurConfig.campFireLitWhenPlaced)).setValue(CampfireBlock.FACING, ctx.getHorizontalDirection()); // Purpur - } - - @Override -diff --git a/src/main/java/net/minecraft/world/level/block/CarvedPumpkinBlock.java b/src/main/java/net/minecraft/world/level/block/CarvedPumpkinBlock.java -index 05112bc416019daba885a3de1b7f96177665135f..32d7ae44dd4e4987b1085f08cb30a92937e57226 100644 ---- a/src/main/java/net/minecraft/world/level/block/CarvedPumpkinBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CarvedPumpkinBlock.java -@@ -69,7 +69,7 @@ public class CarvedPumpkinBlock extends HorizontalDirectionalBlock implements Eq - SnowGolem entitysnowman = (SnowGolem) EntityType.SNOW_GOLEM.create(world); - - if (entitysnowman != null) { -- CarvedPumpkinBlock.spawnGolemInWorld(world, shapedetector_shapedetectorcollection, entitysnowman, shapedetector_shapedetectorcollection.getBlock(0, 2, 0).getPos()); -+ CarvedPumpkinBlock.spawnGolemInWorld(world, shapedetector_shapedetectorcollection, entitysnowman, shapedetector_shapedetectorcollection.getBlock(0, 2, 0).getPos(), this.placer); // Purpur - } - } else { - BlockPattern.BlockPatternMatch shapedetector_shapedetectorcollection1 = this.getOrCreateIronGolemFull().find(world, pos); -@@ -79,7 +79,7 @@ public class CarvedPumpkinBlock extends HorizontalDirectionalBlock implements Eq - - if (entityirongolem != null) { - entityirongolem.setPlayerCreated(true); -- CarvedPumpkinBlock.spawnGolemInWorld(world, shapedetector_shapedetectorcollection1, entityirongolem, shapedetector_shapedetectorcollection1.getBlock(1, 2, 0).getPos()); -+ CarvedPumpkinBlock.spawnGolemInWorld(world, shapedetector_shapedetectorcollection1, entityirongolem, shapedetector_shapedetectorcollection1.getBlock(1, 2, 0).getPos(), this.placer); // Purpur - } - } - } -@@ -87,6 +87,16 @@ public class CarvedPumpkinBlock extends HorizontalDirectionalBlock implements Eq - } - - private static void spawnGolemInWorld(Level world, BlockPattern.BlockPatternMatch patternResult, Entity entity, BlockPos pos) { -+ // Purpur start -+ spawnGolemInWorld(world, patternResult, entity, pos, null); -+ } -+ private static void spawnGolemInWorld(Level world, BlockPattern.BlockPatternMatch patternResult, Entity entity, BlockPos pos, net.minecraft.world.entity.LivingEntity placer) { -+ if (entity instanceof SnowGolem snowGolem) { -+ snowGolem.setSummoner(placer == null ? null : placer.getUUID()); -+ } else if (entity instanceof IronGolem ironGolem) { -+ ironGolem.setSummoner(placer == null ? null : placer.getUUID()); -+ } -+ // Purpur end - // clearPatternBlocks(world, shapedetector_shapedetectorcollection); // CraftBukkit - moved down - entity.moveTo((double) pos.getX() + 0.5D, (double) pos.getY() + 0.05D, (double) pos.getZ() + 0.5D, 0.0F, 0.0F); - // CraftBukkit start -diff --git a/src/main/java/net/minecraft/world/level/block/CauldronBlock.java b/src/main/java/net/minecraft/world/level/block/CauldronBlock.java -index 2f85b893dd0abc39fcedec65acc89e1567faf6f0..3ee012a9ef8cada0b2203e53b2f731f60f697cb1 100644 ---- a/src/main/java/net/minecraft/world/level/block/CauldronBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CauldronBlock.java -@@ -29,7 +29,7 @@ public class CauldronBlock extends AbstractCauldronBlock { - } - - protected static boolean shouldHandlePrecipitation(Level world, Biome.Precipitation precipitation) { -- return precipitation == Biome.Precipitation.RAIN ? world.getRandom().nextFloat() < 0.05F : (precipitation == Biome.Precipitation.SNOW ? world.getRandom().nextFloat() < 0.1F : false); -+ return precipitation == Biome.Precipitation.RAIN ? world.getRandom().nextFloat() < world.purpurConfig.cauldronRainChance : (precipitation == Biome.Precipitation.SNOW ? world.getRandom().nextFloat() < world.purpurConfig.cauldronPowderSnowChance : false); // Purpur - } - - @Override -diff --git a/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java b/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java -index 18b5bce1138d50be32e5da013221be69dc47e21f..58b4a0d97af37f7164db86ef821f04102c6c5ddd 100644 ---- a/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java -@@ -88,4 +88,11 @@ public class CaveVinesBlock extends GrowingPlantHeadBlock implements Bonemealabl - public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { - world.setBlock(pos, state.setValue(BERRIES, Boolean.valueOf(true)), 2); - } -+ -+ // Purpur start -+ @Override -+ public int getMaxGrowthAge() { -+ return org.purpurmc.purpur.PurpurConfig.caveVinesMaxGrowthAge; -+ } -+ // Purpur end - } -diff --git a/src/main/java/net/minecraft/world/level/block/ChestBlock.java b/src/main/java/net/minecraft/world/level/block/ChestBlock.java -index 5e22d175b1048a58802cdf64ac70a8b56329e915..d81946b400f208c39941128ce823ff7709741c10 100644 ---- a/src/main/java/net/minecraft/world/level/block/ChestBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/ChestBlock.java -@@ -355,6 +355,7 @@ public class ChestBlock extends AbstractChestBlock implements - } - - private static boolean isBlockedChestByBlock(BlockGetter world, BlockPos pos) { -+ if (world instanceof Level && ((Level) world).purpurConfig.chestOpenWithBlockOnTop) return false; // Purpur - BlockPos blockposition1 = pos.above(); - - return world.getBlockState(blockposition1).isRedstoneConductor(world, blockposition1); -diff --git a/src/main/java/net/minecraft/world/level/block/ChorusPlantBlock.java b/src/main/java/net/minecraft/world/level/block/ChorusPlantBlock.java -index a6c25647fb37f59307de0d390f8e8cf55504d7d3..52aae8bd4023b2bb48f12983f54b20fa3c95d403 100644 ---- a/src/main/java/net/minecraft/world/level/block/ChorusPlantBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/ChorusPlantBlock.java -@@ -21,6 +21,7 @@ public class ChorusPlantBlock extends PipeBlock { - - @Override - public BlockState getStateForPlacement(BlockPlaceContext ctx) { -+ if (org.purpurmc.purpur.PurpurConfig.disableChorusPlantUpdates) return this.defaultBlockState(); // Purpur - return this.getStateForPlacement(ctx.getLevel(), ctx.getClickedPos()); - } - -@@ -36,6 +37,7 @@ public class ChorusPlantBlock extends PipeBlock { - - @Override - public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { -+ if (org.purpurmc.purpur.PurpurConfig.disableChorusPlantUpdates) return state; // Purpur - if (!state.canSurvive(world, pos)) { - world.scheduleTick(pos, this, 1); - return super.updateShape(state, direction, neighborState, world, pos, neighborPos); -diff --git a/src/main/java/net/minecraft/world/level/block/ComposterBlock.java b/src/main/java/net/minecraft/world/level/block/ComposterBlock.java -index 29aa7303ce5854a7f2b5ed5f9ff02ca7e00bf6c0..13fe64e6c29488ed95366588e7cf05a6482ba780 100644 ---- a/src/main/java/net/minecraft/world/level/block/ComposterBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/ComposterBlock.java -@@ -230,21 +230,28 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { - ItemStack itemstack = player.getItemInHand(hand); - - if (i < 8 && ComposterBlock.COMPOSTABLES.containsKey(itemstack.getItem())) { -- if (i < 7 && !world.isClientSide) { -- BlockState iblockdata1 = ComposterBlock.addItem(player, state, world, pos, itemstack); -- // Paper start - handle cancelled events -- if (iblockdata1 == null) { -- return InteractionResult.PASS; -- } -- // Paper end -- -- world.levelEvent(1500, pos, state != iblockdata1 ? 1 : 0); -- player.awardStat(Stats.ITEM_USED.get(itemstack.getItem())); -- if (!player.getAbilities().instabuild) { -- itemstack.shrink(1); -- } -+ // Purpur start -+ BlockState newState = process(i, state, world, itemstack, pos, player); -+ if (newState == null) { -+ return InteractionResult.PASS; - } - -+ if (world.purpurConfig.composterBulkProcess && player.isShiftKeyDown() && newState != state) { -+ BlockState oldState; -+ int oldCount, newCount, oldLevel, newLevel; -+ do { -+ oldState = newState; -+ oldCount = itemstack.getCount(); -+ oldLevel = oldState.getValue(ComposterBlock.LEVEL); -+ newState = process(oldLevel, oldState, world, itemstack, pos, player); -+ if (newState == null) { -+ return InteractionResult.PASS; -+ } -+ newCount = itemstack.getCount(); -+ newLevel = newState.getValue(ComposterBlock.LEVEL); -+ } while (newCount > 0 && (newCount != oldCount || newLevel != oldLevel || newState != oldState)); -+ } -+ // Purpur end - return InteractionResult.sidedSuccess(world.isClientSide); - } else if (i == 8) { - ComposterBlock.extractProduce(player, state, world, pos); -@@ -254,6 +261,32 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { - } - } - -+ // Purpur start -+ private static BlockState process(int level, BlockState state, Level world, ItemStack itemstack, BlockPos pos, Player player) { -+ if (level < 7 && !world.isClientSide) { -+ // Paper start - EntityChangeBlockEvent -+ double rand = world.getRandom().nextDouble(); -+ BlockState dummyBlockState = ComposterBlock.addItem(player, state, org.bukkit.craftbukkit.util.DummyGeneratorAccess.INSTANCE, pos, itemstack, rand); -+ if (dummyBlockState == null) { -+ return dummyBlockState; -+ } -+ if (state != dummyBlockState && org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(player, pos, dummyBlockState).isCancelled()) { // if block state will change and event cancelled -+ return state; -+ } -+ BlockState iblockdata1 = ComposterBlock.addItem(player, state, world, pos, itemstack, rand); -+ // Paper end -+ -+ world.levelEvent(1500, pos, state != iblockdata1 ? 1 : 0); -+ player.awardStat(Stats.ITEM_USED.get(itemstack.getItem())); -+ if (!player.getAbilities().instabuild) { -+ itemstack.shrink(1); -+ } -+ return dummyBlockState; -+ } -+ return state; -+ } -+ // Purpur end -+ - public static BlockState insertItem(Entity user, BlockState state, ServerLevel world, ItemStack stack, BlockPos pos) { - int i = (Integer) state.getValue(ComposterBlock.LEVEL); - -diff --git a/src/main/java/net/minecraft/world/level/block/CoralBlock.java b/src/main/java/net/minecraft/world/level/block/CoralBlock.java -index 88faea00be60a519f56f975a5311df5e1eb3e6b8..cbb726ac367be81e27d3a86643baf7c4f0746edf 100644 ---- a/src/main/java/net/minecraft/world/level/block/CoralBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CoralBlock.java -@@ -45,6 +45,7 @@ public class CoralBlock extends Block { - } - - protected boolean scanForWater(BlockGetter world, BlockPos pos) { -+ if (!((net.minecraft.world.level.LevelAccessor) world).getMinecraftWorld().purpurConfig.coralDieOutsideWater) return true; // Purpur - Direction[] aenumdirection = Direction.values(); - int i = aenumdirection.length; - -diff --git a/src/main/java/net/minecraft/world/level/block/CropBlock.java b/src/main/java/net/minecraft/world/level/block/CropBlock.java -index a140fed067e7e6c1c42e111f47d3678863ef95ce..3415cbb1def0700b5998a8a1db2e48146f4c2c1e 100644 ---- a/src/main/java/net/minecraft/world/level/block/CropBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/CropBlock.java -@@ -168,7 +168,7 @@ public class CropBlock extends BushBlock implements BonemealableBlock { - @Override - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { - if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper -- if (entity instanceof Ravager && !CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)).isCancelled()) { // CraftBukkit -+ if (entity instanceof Ravager && world.purpurConfig.ravagerGriefableBlocks.contains(world.getBlockState(pos).getBlock()) && !CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), (!world.purpurConfig.ravagerBypassMobGriefing && !world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))).isCancelled()) { // CraftBukkit // Purpur - world.destroyBlock(pos, true, entity); - } - -@@ -203,4 +203,15 @@ public class CropBlock extends BushBlock implements BonemealableBlock { - protected void createBlockStateDefinition(StateDefinition.Builder builder) { - builder.add(CropBlock.AGE); - } -+ -+ // Purpur start -+ @Override -+ public void playerDestroy(Level world, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @javax.annotation.Nullable net.minecraft.world.level.block.entity.BlockEntity blockEntity, ItemStack itemInHand) { -+ if (world.purpurConfig.hoeReplantsCrops && itemInHand.getItem() instanceof net.minecraft.world.item.HoeItem) { -+ super.playerDestroyAndReplant(world, player, pos, state, blockEntity, itemInHand, getBaseSeedId()); -+ } else { -+ super.playerDestroy(world, player, pos, state, blockEntity, itemInHand); -+ } -+ } -+ // Purpur end - } -diff --git a/src/main/java/net/minecraft/world/level/block/DoorBlock.java b/src/main/java/net/minecraft/world/level/block/DoorBlock.java -index 5ba56ee7d5dd210770e6703be559055d218028d5..b5e90dc00240bccf1a6eca342729a4f4165e22bf 100644 ---- a/src/main/java/net/minecraft/world/level/block/DoorBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/DoorBlock.java -@@ -165,6 +165,7 @@ public class DoorBlock extends Block { - public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { - if (this.material == Material.METAL) { - return InteractionResult.PASS; -+ } else if (requiresRedstone(world, state, pos)) { return InteractionResult.CONSUME; // Purpur - } else { - state = (BlockState) state.cycle(DoorBlock.OPEN); - world.setBlock(pos, state, 10); -@@ -260,4 +261,18 @@ public class DoorBlock extends Block { - public static boolean isWoodenDoor(BlockState state) { - return state.getBlock() instanceof DoorBlock && (state.getMaterial() == Material.WOOD || state.getMaterial() == Material.NETHER_WOOD); - } -+ -+ // Purpur start -+ public static boolean requiresRedstone(Level level, BlockState state, BlockPos pos) { -+ if (level.purpurConfig.doorRequiresRedstone.contains(state.getBlock())) { -+ // force update client -+ BlockPos otherPos = pos.relative(state.getValue(DoorBlock.HALF) == DoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN); -+ BlockState otherState = level.getBlockState(otherPos); -+ level.sendBlockUpdated(pos, state, state, 3); -+ level.sendBlockUpdated(otherPos, otherState, otherState, 3); -+ return true; -+ } -+ return false; -+ } -+ // Purpur end - } -diff --git a/src/main/java/net/minecraft/world/level/block/DragonEggBlock.java b/src/main/java/net/minecraft/world/level/block/DragonEggBlock.java -index 7e1edcc7b9f170b7c649437c2f0dd78c0bab9be4..5f8ac1fdac2c334951261f2b9702f5e711743c88 100644 ---- a/src/main/java/net/minecraft/world/level/block/DragonEggBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/DragonEggBlock.java -@@ -42,8 +42,8 @@ public class DragonEggBlock extends FallingBlock { - } - - private void teleport(BlockState state, Level world, BlockPos pos) { -+ if (!world.purpurConfig.dragonEggTeleport) return; // Purpur - WorldBorder worldborder = world.getWorldBorder(); -- - for (int i = 0; i < 1000; ++i) { - BlockPos blockposition1 = pos.offset(world.random.nextInt(16) - world.random.nextInt(16), world.random.nextInt(8) - world.random.nextInt(8), world.random.nextInt(16) - world.random.nextInt(16)); - -diff --git a/src/main/java/net/minecraft/world/level/block/EnchantmentTableBlock.java b/src/main/java/net/minecraft/world/level/block/EnchantmentTableBlock.java -index f4ee3ce287528337a0f9a3b612c157254f895a58..c4a91d7f1320027ee6a2b364303c01ebbacde584 100644 ---- a/src/main/java/net/minecraft/world/level/block/EnchantmentTableBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/EnchantmentTableBlock.java -@@ -28,6 +28,8 @@ import net.minecraft.world.level.pathfinder.PathComputationType; - import net.minecraft.world.phys.BlockHitResult; - import net.minecraft.world.phys.shapes.CollisionContext; - import net.minecraft.world.phys.shapes.VoxelShape; -+import net.minecraft.world.Containers; // Purpur -+import net.minecraft.world.item.Items; // Purpur - - public class EnchantmentTableBlock extends BaseEntityBlock { - protected static final VoxelShape SHAPE = Block.box(0.0D, 0.0D, 0.0D, 16.0D, 12.0D, 16.0D); -@@ -40,6 +42,10 @@ public class EnchantmentTableBlock extends BaseEntityBlock { - } - - public static boolean isValidBookShelf(Level world, BlockPos tablePos, BlockPos bookshelfOffset) { -+ // Purpur Start -+ if(org.purpurmc.purpur.PurpurConfig.allowTransparentBlocksInEnchantmentBox){ -+ return world.getBlockState(tablePos.offset(bookshelfOffset)).is(Blocks.BOOKSHELF) && !world.getBlockState(tablePos.offset(bookshelfOffset.getX() / 2, bookshelfOffset.getY(), bookshelfOffset.getZ() / 2)).isSuffocating(world, bookshelfOffset); -+ } // Purpur end - return world.getBlockState(tablePos.offset(bookshelfOffset)).is(Blocks.BOOKSHELF) && world.isEmptyBlock(tablePos.offset(bookshelfOffset.getX() / 2, bookshelfOffset.getY(), bookshelfOffset.getZ() / 2)); - } - -@@ -120,4 +126,18 @@ public class EnchantmentTableBlock extends BaseEntityBlock { - public boolean isPathfindable(BlockState state, BlockGetter world, BlockPos pos, PathComputationType type) { - return false; - } -+ -+ // Purpur start -+ @Override -+ public void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean moved) { -+ BlockEntity blockEntity = level.getBlockEntity(pos); -+ -+ if (level.purpurConfig.enchantmentTableLapisPersists && blockEntity instanceof EnchantmentTableBlockEntity enchantmentTable) { -+ Containers.dropItemStack(level, pos.getX(), pos.getY(), pos.getZ(), new ItemStack(Items.LAPIS_LAZULI, enchantmentTable.getLapis())); -+ level.updateNeighbourForOutputSignal(pos, this); -+ } -+ -+ super.onRemove(state, level, pos, newState, moved); -+ } -+ // Purpur end - } -diff --git a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -index 41d7cff39fc37955877668337689b4b26cd8c7cf..2deddc746e43896584bd65ba8e7971a80acb4a4d 100644 ---- a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java -@@ -46,6 +46,14 @@ public class EndPortalBlock extends BaseEntityBlock { - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { - if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - if (world instanceof ServerLevel && entity.canChangeDimensions() && Shapes.joinIsNotEmpty(Shapes.create(entity.getBoundingBox().move((double) (-pos.getX()), (double) (-pos.getY()), (double) (-pos.getZ()))), state.getShape(world, pos), BooleanOp.AND)) { -+ // Purpur start -+ if (entity.isPassenger() || entity.isVehicle()) { -+ if (new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), entity.isPassenger() ? org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_PASSENGER : org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, PlayerTeleportEvent.TeleportCause.END_PORTAL).callEvent()) { -+ this.entityInside(state, world, pos, entity); -+ } -+ return; -+ } -+ // Purpur end - ResourceKey resourcekey = world.getTypeKey() == LevelStem.END ? Level.OVERWORLD : Level.END; // CraftBukkit - SPIGOT-6152: send back to main overworld in custom ends - ServerLevel worldserver = ((ServerLevel) world).getServer().getLevel(resourcekey); - -@@ -53,6 +61,22 @@ public class EndPortalBlock extends BaseEntityBlock { - // return; // CraftBukkit - always fire event in case plugins wish to change it - } - -+ // Purpur start -+ if (!world.purpurConfig.endPortalSafeTeleporting) { -+ // CraftBukkit start - Entity in portal -+ EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); -+ world.getCraftServer().getPluginManager().callEvent(event); -+ -+ if (entity instanceof ServerPlayer) { -+ ((ServerPlayer) entity).changeDimension(worldserver, PlayerTeleportEvent.TeleportCause.END_PORTAL); -+ return; -+ } -+ // CraftBukkit end -+ entity.changeDimension(worldserver); -+ return; -+ } -+ // Purpur end -+ - // Paper start - move all of this logic into portal tick - entity.portalWorld = ((ServerLevel)world); - entity.portalBlock = pos.immutable(); -diff --git a/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java b/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java -index 7385e91f32f070e86a4e0fd3d214f55d832c7979..c3b78dd2d06be7d64920c6bcffcd16c82caa52b4 100644 ---- a/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/EnderChestBlock.java -@@ -85,6 +85,27 @@ public class EnderChestBlock extends AbstractChestBlock i - EnderChestBlockEntity enderChestBlockEntity = (EnderChestBlockEntity)blockEntity; - playerEnderChestContainer.setActiveChest(enderChestBlockEntity); - player.openMenu(new SimpleMenuProvider((syncId, inventory, playerx) -> { -+ // Purpur start -+ if (org.purpurmc.purpur.PurpurConfig.enderChestSixRows) { -+ if (org.purpurmc.purpur.PurpurConfig.enderChestPermissionRows) { -+ org.bukkit.craftbukkit.entity.CraftHumanEntity bukkitPlayer = player.getBukkitEntity(); -+ if (bukkitPlayer.hasPermission("purpur.enderchest.rows.six")) { -+ return ChestMenu.sixRows(syncId, inventory, playerEnderChestContainer); -+ } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.five")) { -+ return ChestMenu.fiveRows(syncId, inventory, playerEnderChestContainer); -+ } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.four")) { -+ return ChestMenu.fourRows(syncId, inventory, playerEnderChestContainer); -+ } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.three")) { -+ return ChestMenu.threeRows(syncId, inventory, playerEnderChestContainer); -+ } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.two")) { -+ return ChestMenu.twoRows(syncId, inventory, playerEnderChestContainer); -+ } else if (bukkitPlayer.hasPermission("purpur.enderchest.rows.one")) { -+ return ChestMenu.oneRow(syncId, inventory, playerEnderChestContainer); -+ } -+ } -+ return ChestMenu.sixRows(syncId, inventory, playerEnderChestContainer); -+ } -+ // Purpur end - return ChestMenu.threeRows(syncId, inventory, playerEnderChestContainer); - }, CONTAINER_TITLE)); - player.awardStat(Stats.OPEN_ENDERCHEST); -diff --git a/src/main/java/net/minecraft/world/level/block/FarmBlock.java b/src/main/java/net/minecraft/world/level/block/FarmBlock.java -index 34d744837e599633a3c2c0b72f253bb0e157f226..69cc276fecd4cac51d38bd3cc7de490ad0ae8ace 100644 ---- a/src/main/java/net/minecraft/world/level/block/FarmBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/FarmBlock.java -@@ -100,7 +100,7 @@ public class FarmBlock extends Block { - @Override - public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) { - super.fallOn(world, state, pos, entity, fallDistance); // CraftBukkit - moved here as game rules / events shouldn't affect fall damage. -- if (!world.isClientSide && world.random.nextFloat() < fallDistance - 0.5F && entity instanceof LivingEntity && (entity instanceof Player || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && entity.getBbWidth() * entity.getBbWidth() * entity.getBbHeight() > 0.512F) { -+ if (!world.isClientSide && (world.purpurConfig.farmlandTrampleHeight >= 0D ? fallDistance >= world.purpurConfig.farmlandTrampleHeight : world.random.nextFloat() < fallDistance - 0.5F) && entity instanceof LivingEntity && (entity instanceof Player || world.purpurConfig.farmlandBypassMobGriefing || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && entity.getBbWidth() * entity.getBbWidth() * entity.getBbHeight() > 0.512F) { // Purpur - // CraftBukkit start - Interact soil - org.bukkit.event.Cancellable cancellable; - if (entity instanceof Player) { -@@ -114,6 +114,22 @@ public class FarmBlock extends Block { - return; - } - -+ // Purpur start -+ if (world.purpurConfig.farmlandTramplingDisabled) return; -+ if (world.purpurConfig.farmlandTramplingOnlyPlayers && !(entity instanceof Player)) return; -+ if (world.purpurConfig.farmlandAlpha) { -+ Block block = world.getBlockState(pos.below()).getBlock(); -+ if (block instanceof FenceBlock || block instanceof WallBlock) { -+ return; -+ } -+ } -+ if (world.purpurConfig.farmlandTramplingFeatherFalling) { -+ Iterator armor = entity.getArmorSlots().iterator(); -+ if (armor.hasNext() && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.FALL_PROTECTION, armor.next()) >= (int) entity.fallDistance) { -+ return; -+ } -+ } -+ // Purpur end - if (CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.DIRT.defaultBlockState()).isCancelled()) { - return; - } -@@ -163,7 +179,7 @@ public class FarmBlock extends Block { - } - } - -- return false; -+ return ((ServerLevel) world).purpurConfig.farmlandGetsMoistFromBelow && world.getFluidState(pos.relative(Direction.DOWN)).is(FluidTags.WATER); // Purpur; - } - - @Override -diff --git a/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java b/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java -index 3a1aa4e2405090ccebefb7f5944f36462929e221..f3cf9f06de40054720d1847c1869a9d82592134d 100644 ---- a/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java -@@ -30,12 +30,12 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements - - @Override - public BlockState getStateForPlacement(LevelAccessor world) { -- return (BlockState) this.defaultBlockState().setValue(GrowingPlantHeadBlock.AGE, world.getRandom().nextInt(25)); -+ return (BlockState) this.defaultBlockState().setValue(GrowingPlantHeadBlock.AGE, world.getRandom().nextInt(getMaxGrowthAge())); // Purpur - } - - @Override - public boolean isRandomlyTicking(BlockState state) { -- return (Integer) state.getValue(GrowingPlantHeadBlock.AGE) < 25; -+ return (Integer) state.getValue(GrowingPlantHeadBlock.AGE) < getMaxGrowthAge(); // Purpur - } - - @Override -@@ -51,7 +51,7 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements - } else { - modifier = world.spigotConfig.caveVinesModifier; - } -- if ((Integer) state.getValue(GrowingPlantHeadBlock.AGE) < 25 && random.nextDouble() < ((modifier / 100.0D) * this.growPerTickProbability)) { // Spigot - SPIGOT-7159: Better modifier resolution -+ if ((Integer) state.getValue(GrowingPlantHeadBlock.AGE) < getMaxGrowthAge() && random.nextDouble() < ((modifier / 100.0D) * this.growPerTickProbability)) { // Spigot - SPIGOT-7159: Better modifier resolution // Purpur - // Spigot end - BlockPos blockposition1 = pos.relative(this.growthDirection); - -@@ -73,11 +73,11 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements - } - - public BlockState getMaxAgeState(BlockState state) { -- return (BlockState) state.setValue(GrowingPlantHeadBlock.AGE, 25); -+ return (BlockState) state.setValue(GrowingPlantHeadBlock.AGE, getMaxGrowthAge()); // Purpur - } - - public boolean isMaxAge(BlockState state) { -- return (Integer) state.getValue(GrowingPlantHeadBlock.AGE) == 25; -+ return (Integer) state.getValue(GrowingPlantHeadBlock.AGE) >= getMaxGrowthAge(); // Purpur - } - - protected BlockState updateBodyAfterConvertedFromHead(BlockState from, BlockState to) { -@@ -119,13 +119,13 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements - @Override - public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { - BlockPos blockposition1 = pos.relative(this.growthDirection); -- int i = Math.min((Integer) state.getValue(GrowingPlantHeadBlock.AGE) + 1, 25); -+ int i = Math.min((Integer) state.getValue(GrowingPlantHeadBlock.AGE) + 1, getMaxGrowthAge()); // Purpur - int j = this.getBlocksToGrowWhenBonemealed(random); - - for (int k = 0; k < j && this.canGrowInto(world.getBlockState(blockposition1)); ++k) { - world.setBlockAndUpdate(blockposition1, (BlockState) state.setValue(GrowingPlantHeadBlock.AGE, i)); - blockposition1 = blockposition1.relative(this.growthDirection); -- i = Math.min(i + 1, 25); -+ i = Math.min(i + 1, getMaxGrowthAge()); // Purpur - } - - } -@@ -138,4 +138,6 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements - protected GrowingPlantHeadBlock getHeadBlock() { - return this; - } -+ -+ public abstract int getMaxGrowthAge(); // Purpur - } -diff --git a/src/main/java/net/minecraft/world/level/block/HayBlock.java b/src/main/java/net/minecraft/world/level/block/HayBlock.java -index cfbe1dae76db76cf54a4f5d72aca72d5e893859e..74cb10230d459ac9f300a9d59af504d233ac663e 100644 ---- a/src/main/java/net/minecraft/world/level/block/HayBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/HayBlock.java -@@ -15,6 +15,6 @@ public class HayBlock extends RotatedPillarBlock { - - @Override - public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) { -- entity.causeFallDamage(fallDistance, 0.2F, world.damageSources().fall()); -+ super.fallOn(world, state, pos, entity, fallDistance); // Purpur - } - } -diff --git a/src/main/java/net/minecraft/world/level/block/HugeMushroomBlock.java b/src/main/java/net/minecraft/world/level/block/HugeMushroomBlock.java -index 3c6d97b51c6fec130b80e5965afa2c49d48843c9..b456cb8efd8f0be8a6860c82462ce9bdde3a8383 100644 ---- a/src/main/java/net/minecraft/world/level/block/HugeMushroomBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/HugeMushroomBlock.java -@@ -22,29 +22,65 @@ public class HugeMushroomBlock extends Block { - - public HugeMushroomBlock(BlockBehaviour.Properties settings) { - super(settings); -- this.registerDefaultState(this.stateDefinition.any().setValue(NORTH, Boolean.valueOf(true)).setValue(EAST, Boolean.valueOf(true)).setValue(SOUTH, Boolean.valueOf(true)).setValue(WEST, Boolean.valueOf(true)).setValue(UP, Boolean.valueOf(true)).setValue(DOWN, Boolean.valueOf(true))); -+ // Purpur start -+ this.registerDefaultState(this.stateDefinition.any() -+ .setValue(NORTH, true) -+ .setValue(EAST, true) -+ .setValue(SOUTH, true) -+ .setValue(WEST, true) -+ .setValue(UP, true) -+ .setValue(DOWN, true)); -+ // Purpur end - } - - @Override - public BlockState getStateForPlacement(BlockPlaceContext ctx) { -+ if (org.purpurmc.purpur.PurpurConfig.disableMushroomBlockUpdates) return this.defaultBlockState(); // Purpur - BlockGetter blockGetter = ctx.getLevel(); - BlockPos blockPos = ctx.getClickedPos(); -- return this.defaultBlockState().setValue(DOWN, Boolean.valueOf(!blockGetter.getBlockState(blockPos.below()).is(this))).setValue(UP, Boolean.valueOf(!blockGetter.getBlockState(blockPos.above()).is(this))).setValue(NORTH, Boolean.valueOf(!blockGetter.getBlockState(blockPos.north()).is(this))).setValue(EAST, Boolean.valueOf(!blockGetter.getBlockState(blockPos.east()).is(this))).setValue(SOUTH, Boolean.valueOf(!blockGetter.getBlockState(blockPos.south()).is(this))).setValue(WEST, Boolean.valueOf(!blockGetter.getBlockState(blockPos.west()).is(this))); -+ // Purpur start -+ return this.defaultBlockState() -+ .setValue(DOWN, this != blockGetter.getBlockStateIfLoaded(blockPos.below()).getBlock()) -+ .setValue(UP, this != blockGetter.getBlockStateIfLoaded(blockPos.above()).getBlock()) -+ .setValue(NORTH, this != blockGetter.getBlockStateIfLoaded(blockPos.north()).getBlock()) -+ .setValue(EAST, this != blockGetter.getBlockStateIfLoaded(blockPos.east()).getBlock()) -+ .setValue(SOUTH, this != blockGetter.getBlockStateIfLoaded(blockPos.south()).getBlock()) -+ .setValue(WEST, this != blockGetter.getBlockStateIfLoaded(blockPos.west()).getBlock()); -+ // Purpur end - } - - @Override - public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { -+ if (org.purpurmc.purpur.PurpurConfig.disableMushroomBlockUpdates) return state; // Purpur - return neighborState.is(this) ? state.setValue(PROPERTY_BY_DIRECTION.get(direction), Boolean.valueOf(false)) : super.updateShape(state, direction, neighborState, world, pos, neighborPos); - } - - @Override - public BlockState rotate(BlockState state, Rotation rotation) { -- return state.setValue(PROPERTY_BY_DIRECTION.get(rotation.rotate(Direction.NORTH)), state.getValue(NORTH)).setValue(PROPERTY_BY_DIRECTION.get(rotation.rotate(Direction.SOUTH)), state.getValue(SOUTH)).setValue(PROPERTY_BY_DIRECTION.get(rotation.rotate(Direction.EAST)), state.getValue(EAST)).setValue(PROPERTY_BY_DIRECTION.get(rotation.rotate(Direction.WEST)), state.getValue(WEST)).setValue(PROPERTY_BY_DIRECTION.get(rotation.rotate(Direction.UP)), state.getValue(UP)).setValue(PROPERTY_BY_DIRECTION.get(rotation.rotate(Direction.DOWN)), state.getValue(DOWN)); -+ // Purpur start -+ if (org.purpurmc.purpur.PurpurConfig.disableMushroomBlockUpdates) return state; -+ return state -+ .setValue(PROPERTY_BY_DIRECTION.get(rotation.rotate(Direction.NORTH)), state.getValue(NORTH)) -+ .setValue(PROPERTY_BY_DIRECTION.get(rotation.rotate(Direction.SOUTH)), state.getValue(SOUTH)) -+ .setValue(PROPERTY_BY_DIRECTION.get(rotation.rotate(Direction.EAST)), state.getValue(EAST)) -+ .setValue(PROPERTY_BY_DIRECTION.get(rotation.rotate(Direction.WEST)), state.getValue(NORTH)) -+ .setValue(PROPERTY_BY_DIRECTION.get(rotation.rotate(Direction.UP)), state.getValue(UP)) -+ .setValue(PROPERTY_BY_DIRECTION.get(rotation.rotate(Direction.DOWN)), state.getValue(DOWN)); -+ // Purpur end - } - - @Override - public BlockState mirror(BlockState state, Mirror mirror) { -- return state.setValue(PROPERTY_BY_DIRECTION.get(mirror.mirror(Direction.NORTH)), state.getValue(NORTH)).setValue(PROPERTY_BY_DIRECTION.get(mirror.mirror(Direction.SOUTH)), state.getValue(SOUTH)).setValue(PROPERTY_BY_DIRECTION.get(mirror.mirror(Direction.EAST)), state.getValue(EAST)).setValue(PROPERTY_BY_DIRECTION.get(mirror.mirror(Direction.WEST)), state.getValue(WEST)).setValue(PROPERTY_BY_DIRECTION.get(mirror.mirror(Direction.UP)), state.getValue(UP)).setValue(PROPERTY_BY_DIRECTION.get(mirror.mirror(Direction.DOWN)), state.getValue(DOWN)); -+ // Purpur start -+ if (org.purpurmc.purpur.PurpurConfig.disableMushroomBlockUpdates) return state; -+ return state -+ .setValue(PROPERTY_BY_DIRECTION.get(mirror.mirror(Direction.NORTH)), state.getValue(NORTH)) -+ .setValue(PROPERTY_BY_DIRECTION.get(mirror.mirror(Direction.SOUTH)), state.getValue(SOUTH)) -+ .setValue(PROPERTY_BY_DIRECTION.get(mirror.mirror(Direction.EAST)), state.getValue(EAST)) -+ .setValue(PROPERTY_BY_DIRECTION.get(mirror.mirror(Direction.WEST)), state.getValue(NORTH)) -+ .setValue(PROPERTY_BY_DIRECTION.get(mirror.mirror(Direction.UP)), state.getValue(UP)) -+ .setValue(PROPERTY_BY_DIRECTION.get(mirror.mirror(Direction.DOWN)), state.getValue(DOWN)); -+ // Purpur end - } - - @Override -diff --git a/src/main/java/net/minecraft/world/level/block/IceBlock.java b/src/main/java/net/minecraft/world/level/block/IceBlock.java -index 5ecf02ce83b7496c977adfeb203b8eadb05f9da5..bf7f1ac5c691c0c4c30c124970f4b08a8108ad34 100644 ---- a/src/main/java/net/minecraft/world/level/block/IceBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/IceBlock.java -@@ -31,7 +31,7 @@ public class IceBlock extends HalfTransparentBlock { - public void afterDestroy(Level world, BlockPos pos, ItemStack tool) { - // Paper end - if (EnchantmentHelper.getItemEnchantmentLevel(Enchantments.SILK_TOUCH, tool) == 0) { -- if (world.dimensionType().ultraWarm()) { -+ if (world.isNether() || (world.isTheEnd() && !org.purpurmc.purpur.PurpurConfig.allowWaterPlacementInTheEnd)) { // Purpur - world.removeBlock(pos, false); - return; - } -@@ -59,7 +59,7 @@ public class IceBlock extends HalfTransparentBlock { - return; - } - // CraftBukkit end -- if (world.dimensionType().ultraWarm()) { -+ if (world.isNether() || (world.isTheEnd() && !org.purpurmc.purpur.PurpurConfig.allowWaterPlacementInTheEnd)) { // Purpur - world.removeBlock(pos, false); - } else { - world.setBlockAndUpdate(pos, Blocks.WATER.defaultBlockState()); -diff --git a/src/main/java/net/minecraft/world/level/block/KelpBlock.java b/src/main/java/net/minecraft/world/level/block/KelpBlock.java -index bc66fa91ec3e13431d5d9b6e17935cab73066be7..0f16b5ed2e249f3d8f583dc941e32066d354cf95 100644 ---- a/src/main/java/net/minecraft/world/level/block/KelpBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/KelpBlock.java -@@ -64,4 +64,11 @@ public class KelpBlock extends GrowingPlantHeadBlock implements LiquidBlockConta - public FluidState getFluidState(BlockState state) { - return Fluids.WATER.getSource(false); - } -+ -+ // Purpur start -+ @Override -+ public int getMaxGrowthAge() { -+ return org.purpurmc.purpur.PurpurConfig.kelpMaxGrowthAge; -+ } -+ // Purpur end - } -diff --git a/src/main/java/net/minecraft/world/level/block/LiquidBlock.java b/src/main/java/net/minecraft/world/level/block/LiquidBlock.java -index 43e8ef1d6a65d4fd3fe53a587639ffb814368217..9c22a730772f71b34c63d1e43d48943f71e9990b 100644 ---- a/src/main/java/net/minecraft/world/level/block/LiquidBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/LiquidBlock.java -@@ -105,7 +105,7 @@ public class LiquidBlock extends Block implements BucketPickup { - - @Override - public void onPlace(BlockState state, Level world, BlockPos pos, BlockState oldState, boolean notify) { -- if (this.shouldSpreadLiquid(world, pos, state)) { -+ if (world.purpurConfig.tickFluids && this.shouldSpreadLiquid(world, pos, state)) { // Purpur - world.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(world, pos)); // Paper - } - -@@ -129,7 +129,7 @@ public class LiquidBlock extends Block implements BucketPickup { - - @Override - public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { -- if (state.getFluidState().isSource() || neighborState.getFluidState().isSource()) { -+ if (world.getMinecraftWorld().purpurConfig.tickFluids && state.getFluidState().isSource() || neighborState.getFluidState().isSource()) { // Purpur - world.scheduleTick(pos, state.getFluidState().getType(), this.fluid.getTickDelay(world)); - } - -@@ -138,7 +138,7 @@ public class LiquidBlock extends Block implements BucketPickup { - - @Override - public void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, BlockPos sourcePos, boolean notify) { -- if (this.shouldSpreadLiquid(world, pos, state)) { -+ if (world.purpurConfig.tickFluids && this.shouldSpreadLiquid(world, pos, state)) { // Purpur - world.scheduleTick(pos, state.getFluidState().getType(), this.getFlowSpeed(world, pos)); // Paper - } - -diff --git a/src/main/java/net/minecraft/world/level/block/MagmaBlock.java b/src/main/java/net/minecraft/world/level/block/MagmaBlock.java -index 12ffb5714f088f4aeafa1ad6a36f5b64a86c4c96..293aa5c8f91a997045f8d9f2951fe3a7f01f0642 100644 ---- a/src/main/java/net/minecraft/world/level/block/MagmaBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/MagmaBlock.java -@@ -27,7 +27,7 @@ public class MagmaBlock extends Block { - - @Override - public void stepOn(Level world, BlockPos pos, BlockState state, Entity entity) { -- if (!entity.isSteppingCarefully() && entity instanceof LivingEntity && !EnchantmentHelper.hasFrostWalker((LivingEntity) entity)) { -+ if ((!entity.isSteppingCarefully() || world.purpurConfig.magmaBlockDamageWhenSneaking) && entity instanceof LivingEntity && (world.purpurConfig.magmaBlockDamageWithFrostWalker || !EnchantmentHelper.hasFrostWalker((LivingEntity) entity))) { // Purpur - org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); // CraftBukkit - entity.hurt(world.damageSources().hotFloor(), 1.0F); - org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = null; // CraftBukkit -diff --git a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -index a6ab0d0defc05e56a91084c49897059670a1324b..589b437e7c97c846410f293e2f014bdcd7cb333e 100644 ---- a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java -@@ -52,7 +52,7 @@ public class NetherPortalBlock extends Block { - - @Override - public void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) { -- if (world.spigotConfig.enableZombiePigmenPortalSpawns && world.dimensionType().natural() && world.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && random.nextInt(2000) < world.getDifficulty().getId()) { // Spigot -+ if (world.spigotConfig.enableZombiePigmenPortalSpawns && world.dimensionType().natural() && world.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && random.nextInt(world.purpurConfig.piglinPortalSpawnModifier) < world.getDifficulty().getId()) { // Spigot // Purpur - while (world.getBlockState(pos).is((Block) this)) { - pos = pos.below(); - } -@@ -84,6 +84,14 @@ public class NetherPortalBlock extends Block { - public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) { - if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - if (entity.canChangeDimensions()) { -+ // Purpur start -+ if (entity.isPassenger() || entity.isVehicle()) { -+ if (new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), entity.isPassenger() ? org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_PASSENGER : org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.NETHER_PORTAL).callEvent()) { -+ this.entityInside(state, world, pos, entity); -+ } -+ return; -+ } -+ // Purpur end - // CraftBukkit start - Entity in portal - EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ())); - world.getCraftServer().getPluginManager().callEvent(event); -diff --git a/src/main/java/net/minecraft/world/level/block/NetherWartBlock.java b/src/main/java/net/minecraft/world/level/block/NetherWartBlock.java -index e55720c4d2fbdf6aae526910e87a67c29cf906fd..bf4485b4cad324d5aace657ebf284c4d97197f53 100644 ---- a/src/main/java/net/minecraft/world/level/block/NetherWartBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/NetherWartBlock.java -@@ -14,7 +14,7 @@ import net.minecraft.world.level.block.state.properties.IntegerProperty; - import net.minecraft.world.phys.shapes.CollisionContext; - import net.minecraft.world.phys.shapes.VoxelShape; - --public class NetherWartBlock extends BushBlock { -+public class NetherWartBlock extends BushBlock implements BonemealableBlock { // Purpur - - public static final int MAX_AGE = 3; - public static final IntegerProperty AGE = BlockStateProperties.AGE_3; -@@ -60,4 +60,32 @@ public class NetherWartBlock extends BushBlock { - protected void createBlockStateDefinition(StateDefinition.Builder builder) { - builder.add(NetherWartBlock.AGE); - } -+ -+ // Purpur start -+ @Override -+ public void playerDestroy(net.minecraft.world.level.Level world, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, @javax.annotation.Nullable net.minecraft.world.level.block.entity.BlockEntity blockEntity, ItemStack itemInHand) { -+ if (world.purpurConfig.hoeReplantsNetherWarts && itemInHand.getItem() instanceof net.minecraft.world.item.HoeItem) { -+ super.playerDestroyAndReplant(world, player, pos, state, blockEntity, itemInHand, Items.NETHER_WART); -+ } else { -+ super.playerDestroy(world, player, pos, state, blockEntity, itemInHand); -+ } -+ } -+ -+ @Override -+ public boolean isValidBonemealTarget(net.minecraft.world.level.LevelReader world, BlockPos pos, BlockState state, boolean isClient) { -+ return ((net.minecraft.world.level.Level) world).purpurConfig.netherWartAffectedByBonemeal && state.getValue(NetherWartBlock.AGE) < 3; -+ } -+ -+ @Override -+ public boolean isBonemealSuccess(net.minecraft.world.level.Level world, RandomSource random, BlockPos pos, BlockState state) { -+ return true; -+ } -+ -+ @Override -+ public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { -+ int i = Math.min(3, state.getValue(NetherWartBlock.AGE) + 1); -+ state = state.setValue(NetherWartBlock.AGE, i); -+ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockGrowEvent(world, pos, state, 2); // CraftBukkit -+ } -+ // Purpur end - } -diff --git a/src/main/java/net/minecraft/world/level/block/NoteBlock.java b/src/main/java/net/minecraft/world/level/block/NoteBlock.java -index a9c2d254bda5686a35ad2393534b85030dd8b136..c11752564ea48960232844ee735779aa95d82c12 100644 ---- a/src/main/java/net/minecraft/world/level/block/NoteBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/NoteBlock.java -@@ -62,11 +62,13 @@ public class NoteBlock extends Block { - - @Override - public BlockState getStateForPlacement(BlockPlaceContext ctx) { -+ if (org.purpurmc.purpur.PurpurConfig.disableNoteBlockUpdates) return this.defaultBlockState(); // Purpur - return this.setInstrument(ctx.getLevel(), ctx.getClickedPos(), this.defaultBlockState()); - } - - @Override - public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { -+ if (org.purpurmc.purpur.PurpurConfig.disableNoteBlockUpdates) return state; // Purpur - boolean flag = NoteBlock.isFeatureFlagEnabled(world) ? direction.getAxis() == Direction.Axis.Y : direction == Direction.DOWN; - - return flag ? this.setInstrument(world, pos, state) : super.updateShape(state, direction, neighborState, world, pos, neighborPos); -@@ -82,13 +84,14 @@ public class NoteBlock extends Block { - state = world.getBlockState(pos); // CraftBukkit - SPIGOT-5617: update in case changed in event - } - -+ if (!org.purpurmc.purpur.PurpurConfig.disableNoteBlockUpdates) // Purpur - world.setBlock(pos, (BlockState) state.setValue(NoteBlock.POWERED, flag1), 3); - } - - } - - private void playNote(@Nullable Entity entity, BlockState state, Level world, BlockPos pos) { -- if (!((NoteBlockInstrument) state.getValue(NoteBlock.INSTRUMENT)).requiresAirAbove() || world.getBlockState(pos.above()).isAir()) { -+ if (world.purpurConfig.noteBlockIgnoreAbove || !((NoteBlockInstrument) state.getValue(NoteBlock.INSTRUMENT)).requiresAirAbove() || world.getBlockState(pos.above()).isAir()) { // Purpur - // CraftBukkit start - // org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(world, pos, state.getValue(NoteBlock.INSTRUMENT), state.getValue(NoteBlock.NOTE)); - // if (event.isCancelled()) { -diff --git a/src/main/java/net/minecraft/world/level/block/ObserverBlock.java b/src/main/java/net/minecraft/world/level/block/ObserverBlock.java -index 7b45d6b9a005036ca5051d089a7be792eb87012f..8806c97ecc6bdd8a64c2d82bb2f58f46ac37c468 100644 ---- a/src/main/java/net/minecraft/world/level/block/ObserverBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/ObserverBlock.java -@@ -64,6 +64,7 @@ public class ObserverBlock extends DirectionalBlock { - @Override - public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { - if (state.getValue(ObserverBlock.FACING) == direction && !(Boolean) state.getValue(ObserverBlock.POWERED)) { -+ if (!world.getMinecraftWorld().purpurConfig.disableObserverClocks || !(neighborState.getBlock() instanceof ObserverBlock) || neighborState.getValue(ObserverBlock.FACING).getOpposite() != direction) // Purpur - this.startSignal(world, pos); - } - -diff --git a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java -index 6b909d41ccdf6c1ac3ac0c4e673ff52f0d14a238..b8f69063cec4d31c9d9525a04c46ed8904ceff76 100644 ---- a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java -@@ -188,7 +188,7 @@ public class PointedDripstoneBlock extends Block implements Fallable, SimpleWate - - @VisibleForTesting - public static void maybeTransferFluid(BlockState state, ServerLevel world, BlockPos pos, float dripChance) { -- if (dripChance <= 0.17578125F || dripChance <= 0.05859375F) { -+ if (dripChance <= world.purpurConfig.cauldronDripstoneWaterFillChance || dripChance <= world.purpurConfig.cauldronDripstoneLavaFillChance) { // Purpur - if (PointedDripstoneBlock.isStalactiteStartPos(state, world, pos)) { - Optional optional = PointedDripstoneBlock.getFluidAboveStalactite(world, pos, state); - -@@ -197,13 +197,13 @@ public class PointedDripstoneBlock extends Block implements Fallable, SimpleWate - float f1; - - if (fluidtype == Fluids.WATER) { -- f1 = 0.17578125F; -+ f1 = world.purpurConfig.cauldronDripstoneWaterFillChance; // Purpur - } else { - if (fluidtype != Fluids.LAVA) { - return; - } - -- f1 = 0.05859375F; -+ f1 = world.purpurConfig.cauldronDripstoneLavaFillChance; // Purpur - } - - if (dripChance < f1) { -diff --git a/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java b/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java -index 518d3832c36c9ecf1ed9267ffc1f926dc84b7989..af5933b886abf3fd17bfdb8c1cb1ea63f6f2a757 100644 ---- a/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java -@@ -72,7 +72,7 @@ public class PowderSnowBlock extends Block implements BucketPickup { - if (!world.isClientSide) { - // CraftBukkit start - if (entity.isOnFire() && entity.mayInteract(world, pos)) { -- if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !(world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) || entity instanceof Player)).isCancelled()) { -+ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !((world.purpurConfig.powderSnowBypassMobGriefing || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) || entity instanceof Player)).isCancelled()) { - return; - } - // CraftBukkit end -diff --git a/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java b/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java -index 7fddb6fa8fd30ef88346a59f7867aae792f13772..40893e71fe8447b695350273bef9623bd5accdcd 100644 ---- a/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/PoweredRailBlock.java -@@ -23,7 +23,7 @@ public class PoweredRailBlock extends BaseRailBlock { - } - - protected boolean findPoweredRailSignal(Level world, BlockPos pos, BlockState state, boolean flag, int distance) { -- if (distance >= 8) { -+ if (distance >= world.purpurConfig.railActivationRange) { // Purpur - return false; - } else { - int j = pos.getX(); -diff --git a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java -index 2ed78cf83c0ae66a6ddba1ff307da89a24b0d0a8..ae17d6a54fad0bd2d71d306f418b5ced2f11b863 100644 ---- a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java -@@ -141,7 +141,7 @@ public class RespawnAnchorBlock extends Block { - }; - Vec3 vec3d = explodedPos.getCenter(); - -- world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, explodedBlockState), explosiondamagecalculator, vec3d, 5.0F, true, Level.ExplosionInteraction.BLOCK); // Paper -+ if (world.purpurConfig.respawnAnchorExplode)world.explode((Entity) null, world.damageSources().badRespawnPointExplosion(vec3d, explodedBlockState), explosiondamagecalculator, vec3d, (float) world.purpurConfig.respawnAnchorExplosionPower, world.purpurConfig.respawnAnchorExplosionFire, world.purpurConfig.respawnAnchorExplosionEffect); // Paper // Purpur - } - - public static boolean canSetSpawn(Level world) { -diff --git a/src/main/java/net/minecraft/world/level/block/SculkShriekerBlock.java b/src/main/java/net/minecraft/world/level/block/SculkShriekerBlock.java -index 437b44fb68bcbe81d1c431689431225b6a17a1a6..06d091b7c4df949c4abda16c4f73c194a71a4669 100644 ---- a/src/main/java/net/minecraft/world/level/block/SculkShriekerBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SculkShriekerBlock.java -@@ -130,7 +130,7 @@ public class SculkShriekerBlock extends BaseEntityBlock implements SimpleWaterlo - @Nullable - @Override - public BlockState getStateForPlacement(BlockPlaceContext ctx) { -- return (BlockState) this.defaultBlockState().setValue(SculkShriekerBlock.WATERLOGGED, ctx.getLevel().getFluidState(ctx.getClickedPos()).getType() == Fluids.WATER); -+ return (BlockState) this.defaultBlockState().setValue(SculkShriekerBlock.WATERLOGGED, ctx.getLevel().getFluidState(ctx.getClickedPos()).getType() == Fluids.WATER).setValue(SculkShriekerBlock.CAN_SUMMON, ctx.getLevel().purpurConfig.sculkShriekerCanSummonDefault); // Purpur - } - - @Override -diff --git a/src/main/java/net/minecraft/world/level/block/ShulkerBoxBlock.java b/src/main/java/net/minecraft/world/level/block/ShulkerBoxBlock.java -index c89978ecbc5a13dda6f76ea6d1cc3056efc9a174..39868ad3ee4bb573a4dd562894d93f64be4ee5ac 100644 ---- a/src/main/java/net/minecraft/world/level/block/ShulkerBoxBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/ShulkerBoxBlock.java -@@ -138,7 +138,7 @@ public class ShulkerBoxBlock extends BaseEntityBlock { - public void playerWillDestroy(Level world, BlockPos pos, BlockState state, Player player) { - BlockEntity blockEntity = world.getBlockEntity(pos); - if (blockEntity instanceof ShulkerBoxBlockEntity shulkerBoxBlockEntity) { -- if (!world.isClientSide && player.isCreative() && !shulkerBoxBlockEntity.isEmpty()) { -+ if (world.purpurConfig.shulkerBoxAllowOversizedStacks || (!world.isClientSide && player.isCreative() && !shulkerBoxBlockEntity.isEmpty())) { // Purpur - ItemStack itemStack = getColoredItemStack(this.getColor()); - blockEntity.saveToItem(itemStack); - if (shulkerBoxBlockEntity.hasCustomName()) { -diff --git a/src/main/java/net/minecraft/world/level/block/SignBlock.java b/src/main/java/net/minecraft/world/level/block/SignBlock.java -index aface9a9697095a29edaf73c9cdabc2c1414b9d7..1a04d0a601b8e481dd6e2592b849b907a5b9f63f 100644 ---- a/src/main/java/net/minecraft/world/level/block/SignBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SignBlock.java -@@ -14,6 +14,7 @@ import net.minecraft.world.item.DyeItem; - import net.minecraft.world.item.Item; - import net.minecraft.world.item.ItemStack; - import net.minecraft.world.item.Items; -+import net.minecraft.world.item.SignItem; - import net.minecraft.world.level.BlockGetter; - import net.minecraft.world.level.Level; - import net.minecraft.world.level.LevelAccessor; -@@ -76,11 +77,11 @@ public abstract class SignBlock extends BaseEntityBlock implements SimpleWaterlo - if (world.isClientSide) { - return bl4 ? InteractionResult.SUCCESS : InteractionResult.CONSUME; - } else { -- BlockEntity bl5 = world.getBlockEntity(pos); -- if (!(bl5 instanceof SignBlockEntity)) { -+ BlockEntity blockEntity = world.getBlockEntity(pos); // Purpur - decompile fix -+ if (!(blockEntity instanceof SignBlockEntity)) { // Purpur - decompile fix - return InteractionResult.PASS; - } else { -- SignBlockEntity signBlockEntity = (SignBlockEntity)bl5; -+ SignBlockEntity signBlockEntity = (SignBlockEntity)blockEntity; // Purpur - decompile fix - boolean bl5 = signBlockEntity.hasGlowingText(); - if ((!bl2 || !bl5) && (!bl3 || bl5)) { - if (bl4) { -@@ -108,6 +109,17 @@ public abstract class SignBlock extends BaseEntityBlock implements SimpleWaterlo - } - } - -+ // Purpur start - right click to open sign editor -+ if (world.purpurConfig.signRightClickEdit && itemStack.getItem() instanceof SignItem && -+ !player.isCrouching() && player.getAbilities().mayBuild && -+ player.getBukkitEntity().hasPermission("purpur.sign.edit")) { -+ signBlockEntity.setEditable(true); -+ signBlockEntity.setAllowedPlayerEditor(player.getUUID()); -+ player.openTextEdit(signBlockEntity); -+ return InteractionResult.SUCCESS; -+ } -+ // Purpur end -+ - return signBlockEntity.executeClickCommands((ServerPlayer)player) ? InteractionResult.SUCCESS : InteractionResult.PASS; - } else { - return InteractionResult.PASS; -diff --git a/src/main/java/net/minecraft/world/level/block/SlabBlock.java b/src/main/java/net/minecraft/world/level/block/SlabBlock.java -index 18b603d646081926343dea108b55d641df1c2c34..03ad3e45fc6d48091ac0c0ba5dc3d014b1d4ddfa 100644 ---- a/src/main/java/net/minecraft/world/level/block/SlabBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SlabBlock.java -@@ -130,4 +130,25 @@ public class SlabBlock extends Block implements SimpleWaterloggedBlock { - return false; - } - } -+ -+ // Purpur start -+ public boolean halfBreak(BlockState state, BlockPos pos, net.minecraft.server.level.ServerPlayer player) { -+ if (state.getValue(SlabBlock.TYPE) != SlabType.DOUBLE) { -+ return false; -+ } -+ net.minecraft.world.phys.HitResult result = player.getRayTrace(16, net.minecraft.world.level.ClipContext.Fluid.NONE); -+ if (result.getType() != net.minecraft.world.phys.HitResult.Type.BLOCK) { -+ return false; -+ } -+ double hitY = result.getLocation().y(); -+ int blockY = org.bukkit.util.NumberConversions.floor(hitY); -+ player.level.setBlock(pos, state.setValue(SlabBlock.TYPE, (hitY - blockY > 0.5 || blockY - pos.getY() == 1) ? SlabType.BOTTOM : SlabType.TOP), 3); -+ if (!player.getAbilities().instabuild) { -+ net.minecraft.world.entity.item.ItemEntity item = new net.minecraft.world.entity.item.ItemEntity(player.level, pos.getX(), pos.getY(), pos.getZ(), new ItemStack(asItem())); -+ item.setDefaultPickUpDelay(); -+ player.level.addFreshEntity(item); -+ } -+ return true; -+ } -+ // Purpur end - } -diff --git a/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java b/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java -index 14e00c7feb1c051d56a3d27cd00dcef072dd771a..4952fb1aaaafb55baa0fddb389f966a120a4786c 100644 ---- a/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SnowLayerBlock.java -@@ -81,6 +81,12 @@ public class SnowLayerBlock extends Block { - public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) { - BlockState iblockdata1 = world.getBlockState(pos.below()); - -+ // Purpur start -+ if (iblockdata1.is(Blocks.BLUE_ICE) && !world.getWorldBorder().world.purpurConfig.snowOnBlueIce) { -+ return false; -+ } -+ // Purpur end -+ - return iblockdata1.is(BlockTags.SNOW_LAYER_CANNOT_SURVIVE_ON) ? false : (iblockdata1.is(BlockTags.SNOW_LAYER_CAN_SURVIVE_ON) ? true : Block.isFaceFull(iblockdata1.getCollisionShape(world, pos.below()), Direction.UP) || iblockdata1.is((Block) this) && (Integer) iblockdata1.getValue(SnowLayerBlock.LAYERS) == 8); - } - -diff --git a/src/main/java/net/minecraft/world/level/block/SpawnerBlock.java b/src/main/java/net/minecraft/world/level/block/SpawnerBlock.java -index 936d844a5a246138c9f9ae4ae6e318242b8f1420..d58dc4aa02fe371deaf879df8692dbe93c648f9b 100644 ---- a/src/main/java/net/minecraft/world/level/block/SpawnerBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SpawnerBlock.java -@@ -40,6 +40,58 @@ public class SpawnerBlock extends BaseEntityBlock { - return createTickerHelper(type, BlockEntityType.MOB_SPAWNER, world.isClientSide ? SpawnerBlockEntity::clientTick : SpawnerBlockEntity::serverTick); - } - -+ // Purpur start -+ @Override -+ public void playerDestroy(Level level, net.minecraft.world.entity.player.Player player, BlockPos pos, BlockState state, BlockEntity blockEntity, ItemStack stack) { -+ if (level.purpurConfig.silkTouchEnabled && player.getBukkitEntity().hasPermission("purpur.drop.spawners") && isSilkTouch(level, stack)) { -+ Optional> type = net.minecraft.world.entity.EntityType.by(((SpawnerBlockEntity) blockEntity).getSpawner().nextSpawnData.getEntityToSpawn()); -+ -+ net.minecraft.world.entity.EntityType entityType = type.orElse(null); -+ final net.kyori.adventure.text.Component mobName = io.papermc.paper.adventure.PaperAdventure.asAdventure(entityType == null ? Component.empty() : entityType.getDescription()); -+ CompoundTag display = new CompoundTag(); -+ CompoundTag tag = new CompoundTag(); -+ -+ String name = level.purpurConfig.silkTouchSpawnerName; -+ if (name != null && !name.isEmpty() && !name.equals("Monster Spawner")) { -+ net.kyori.adventure.text.Component displayName = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(name, net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.component("mob", mobName)); -+ if (name.startsWith("")) { -+ displayName = displayName.decoration(net.kyori.adventure.text.format.TextDecoration.ITALIC, false); -+ } -+ display.put("Name", net.minecraft.nbt.StringTag.valueOf(io.papermc.paper.adventure.PaperAdventure.asJsonString(displayName, java.util.Locale.ROOT))); -+ tag.put("display", display); -+ } -+ -+ List lore = level.purpurConfig.silkTouchSpawnerLore; -+ if (lore != null && !lore.isEmpty()) { -+ net.minecraft.nbt.ListTag list = new net.minecraft.nbt.ListTag(); -+ for (String line : lore) { -+ net.kyori.adventure.text.Component lineComponent = net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(line, net.kyori.adventure.text.minimessage.tag.resolver.Placeholder.component("mob", mobName)); -+ if (line.startsWith("")) { -+ lineComponent = lineComponent.decoration(net.kyori.adventure.text.format.TextDecoration.ITALIC, false); -+ } -+ list.add(net.minecraft.nbt.StringTag.valueOf(io.papermc.paper.adventure.PaperAdventure.asJsonString(lineComponent, java.util.Locale.ROOT))); -+ } -+ display.put("Lore", list); -+ tag.put("display", display); -+ } -+ -+ ItemStack item = new ItemStack(Blocks.SPAWNER.asItem()); -+ if (entityType != null) { -+ tag.putString("Purpur.mob_type", entityType.getName()); -+ tag.putDouble("HideFlags", 32); // hides the "Interact with Spawn Egg" tooltip -+ item.setTag(tag); -+ } -+ -+ popResource(level, pos, item); -+ } -+ super.playerDestroy(level, player, pos, state, blockEntity, stack); -+ } -+ -+ private boolean isSilkTouch(Level level, ItemStack stack) { -+ return stack != null && level.purpurConfig.silkTouchTools.contains(stack.getItem()) && net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.SILK_TOUCH, stack) >= level.purpurConfig.minimumSilkTouchSpawnerRequire; -+ } -+ // Purpur end -+ - @Override - public void spawnAfterBreak(BlockState state, ServerLevel world, BlockPos pos, ItemStack tool, boolean dropExperience) { - super.spawnAfterBreak(state, world, pos, tool, dropExperience); -@@ -48,6 +100,7 @@ public class SpawnerBlock extends BaseEntityBlock { - - @Override - public int getExpDrop(BlockState iblockdata, ServerLevel worldserver, BlockPos blockposition, ItemStack itemstack, boolean flag) { -+ if (isSilkTouch(worldserver, itemstack)) return 0; // Purpur - if (flag) { - int i = 15 + worldserver.random.nextInt(15) + worldserver.random.nextInt(15); - -diff --git a/src/main/java/net/minecraft/world/level/block/SpongeBlock.java b/src/main/java/net/minecraft/world/level/block/SpongeBlock.java -index 7304b2659eb45bc4bc9fa7c43e6ca07221d0fc73..df04a571ebd3c04bc7b58c1ee5661a1f03c69d2f 100644 ---- a/src/main/java/net/minecraft/world/level/block/SpongeBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SpongeBlock.java -@@ -73,16 +73,16 @@ public class SpongeBlock extends Block { - // CraftBukkit end - Material material = iblockdata.getMaterial(); - -- if (fluid.is(FluidTags.WATER)) { -+ if (fluid.is(FluidTags.WATER) || (world.purpurConfig.spongeAbsorbsLava && fluid.is(FluidTags.LAVA))) { // Purpur - if (iblockdata.getBlock() instanceof BucketPickup && !((BucketPickup) iblockdata.getBlock()).pickupBlock(blockList, blockposition2, iblockdata).isEmpty()) { // CraftBukkit - ++i; -- if (j < 6) { -+ if (j < world.purpurConfig.spongeAbsorptionRadius) { // Purpur - queue.add(new Tuple<>(blockposition2, j + 1)); - } - } else if (iblockdata.getBlock() instanceof LiquidBlock) { - blockList.setBlock(blockposition2, Blocks.AIR.defaultBlockState(), 3); // CraftBukkit - ++i; -- if (j < 6) { -+ if (j < world.purpurConfig.spongeAbsorptionRadius) { // Purpur - queue.add(new Tuple<>(blockposition2, j + 1)); - } - } else if (material == Material.WATER_PLANT || material == Material.REPLACEABLE_WATER_PLANT) { -@@ -93,14 +93,14 @@ public class SpongeBlock extends Block { - blockList.setBlock(blockposition2, Blocks.AIR.defaultBlockState(), 3); - // CraftBukkit end - ++i; -- if (j < 6) { -+ if (j < world.purpurConfig.spongeAbsorptionRadius) { // Purpur - queue.add(new Tuple<>(blockposition2, j + 1)); - } - } - } - } - -- if (i > 64) { -+ if (i > world.purpurConfig.spongeAbsorptionArea) { // Purpur - break; - } - } -diff --git a/src/main/java/net/minecraft/world/level/block/StonecutterBlock.java b/src/main/java/net/minecraft/world/level/block/StonecutterBlock.java -index 0a95842c53a9d0286c57bcb42db97e468e30fb7d..e2d42e7947a237dd060ec1b9b63ac6ca4f37241a 100644 ---- a/src/main/java/net/minecraft/world/level/block/StonecutterBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/StonecutterBlock.java -@@ -92,4 +92,16 @@ public class StonecutterBlock extends Block { - public boolean isPathfindable(BlockState state, BlockGetter world, BlockPos pos, PathComputationType type) { - return false; - } -+ -+ // Purpur start -+ @Override -+ public void stepOn(Level level, BlockPos pos, BlockState state, net.minecraft.world.entity.Entity entity) { -+ if (level.purpurConfig.stonecutterDamage > 0.0F && entity instanceof net.minecraft.world.entity.LivingEntity) { -+ org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = level.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); -+ entity.hurt(entity.damageSources().magic(), level.purpurConfig.stonecutterDamage); -+ org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = null; -+ } -+ super.stepOn(level, pos, state, entity); -+ } -+ // Purpur end - } -diff --git a/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java b/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java -index 6b400a4759c8c8612a3b5c96ca0d87ef9dc71435..992de1ab2c00a2545a857f1b5533926bc895f996 100644 ---- a/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java -@@ -19,7 +19,7 @@ import net.minecraft.world.level.material.FluidState; - import net.minecraft.world.phys.shapes.CollisionContext; - import net.minecraft.world.phys.shapes.VoxelShape; - --public class SugarCaneBlock extends Block { -+public class SugarCaneBlock extends Block implements BonemealableBlock { // Purpur - - public static final IntegerProperty AGE = BlockStateProperties.AGE_15; - protected static final float AABB_OFFSET = 6.0F; -@@ -106,4 +106,34 @@ public class SugarCaneBlock extends Block { - protected void createBlockStateDefinition(StateDefinition.Builder builder) { - builder.add(SugarCaneBlock.AGE); - } -+ -+ // Purpur start -+ @Override -+ public boolean isValidBonemealTarget(LevelReader world, BlockPos pos, BlockState state, boolean isClient) { -+ if (!((net.minecraft.world.level.Level) world).purpurConfig.sugarCanAffectedByBonemeal || !world.isEmptyBlock(pos.above())) return false; -+ -+ int reedHeight = 0; -+ while (world.getBlockState(pos.below(reedHeight)).is(this)) { -+ reedHeight++; -+ } -+ -+ return reedHeight < ((net.minecraft.world.level.Level) world).paperConfig().maxGrowthHeight.reeds; -+ } -+ -+ @Override -+ public boolean isBonemealSuccess(net.minecraft.world.level.Level world, RandomSource random, BlockPos pos, BlockState state) { -+ return true; -+ } -+ -+ @Override -+ public void performBonemeal(ServerLevel world, RandomSource random, BlockPos pos, BlockState state) { -+ int reedHeight = 0; -+ while (world.getBlockState(pos.below(reedHeight)).is(this)) { -+ reedHeight++; -+ } -+ for (int i = 0; i <= world.paperConfig().maxGrowthHeight.reeds - reedHeight; i++) { -+ world.setBlockAndUpdate(pos.above(i), state.setValue(SugarCaneBlock.AGE, 0)); -+ } -+ } -+ // Purpur end - } -diff --git a/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java b/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java -index 6c1a0e6f961e46a1a89850746a71e97b32514adf..a8c227e2cb62cfa8225798329cde9078d194c776 100644 ---- a/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/TurtleEggBlock.java -@@ -160,7 +160,7 @@ public class TurtleEggBlock extends Block { - private boolean shouldUpdateHatchLevel(Level world) { - float f = world.getTimeOfDay(1.0F); - -- return (double) f < 0.69D && (double) f > 0.65D ? true : world.random.nextInt(500) == 0; -+ return (double) f < 0.69D && (double) f > 0.65D ? true : world.random.nextInt(world.purpurConfig.turtleEggsRandomTickCrackChance) == 0; - } - - @Override -@@ -193,6 +193,31 @@ public class TurtleEggBlock extends Block { - } - - private boolean canDestroyEgg(Level world, Entity entity) { -- return !(entity instanceof Turtle) && !(entity instanceof Bat) ? (!(entity instanceof LivingEntity) ? false : entity instanceof Player || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) : false; -+ // Purpur start -+ if (entity instanceof Turtle || entity instanceof Bat) { -+ return false; -+ } -+ if (world.purpurConfig.turtleEggsBreakFromExpOrbs && entity instanceof net.minecraft.world.entity.ExperienceOrb) { -+ return true; -+ } -+ if (world.purpurConfig.turtleEggsBreakFromItems && entity instanceof net.minecraft.world.entity.item.ItemEntity) { -+ return true; -+ } -+ if (world.purpurConfig.turtleEggsBreakFromMinecarts && entity instanceof net.minecraft.world.entity.vehicle.AbstractMinecart) { -+ return true; -+ } -+ if (!(entity instanceof LivingEntity)) { -+ return false; -+ } -+ if (world.purpurConfig.turtleEggsTramplingFeatherFalling) { -+ java.util.Iterator armor = entity.getArmorSlots().iterator(); -+ return !armor.hasNext() || net.minecraft.world.item.enchantment.EnchantmentHelper.getItemEnchantmentLevel(net.minecraft.world.item.enchantment.Enchantments.FALL_PROTECTION, armor.next()) < (int) entity.fallDistance; -+ } -+ if (entity instanceof Player) { -+ return true; -+ } -+ -+ return world.purpurConfig.turtleEggsBypassMobGriefing || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING); -+ // Purpur end - } - } -diff --git a/src/main/java/net/minecraft/world/level/block/TwistingVinesBlock.java b/src/main/java/net/minecraft/world/level/block/TwistingVinesBlock.java -index 6866605c7ef5361b21130a19a59c3fa3660dfb19..dee5d76d29da13f8639ab5d392cd0143201e71ba 100644 ---- a/src/main/java/net/minecraft/world/level/block/TwistingVinesBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/TwistingVinesBlock.java -@@ -27,4 +27,11 @@ public class TwistingVinesBlock extends GrowingPlantHeadBlock { - protected boolean canGrowInto(BlockState state) { - return NetherVines.isValidGrowthState(state); - } -+ -+ // Purpur start -+ @Override -+ public int getMaxGrowthAge() { -+ return org.purpurmc.purpur.PurpurConfig.twistingVinesMaxGrowthAge; -+ } -+ // Purpur end - } -diff --git a/src/main/java/net/minecraft/world/level/block/WeepingVinesBlock.java b/src/main/java/net/minecraft/world/level/block/WeepingVinesBlock.java -index e5c135ec059746b75fe58516809584221285cdbe..713c7e6e31a3e1097b612c77a4fce147c9252e0b 100644 ---- a/src/main/java/net/minecraft/world/level/block/WeepingVinesBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/WeepingVinesBlock.java -@@ -27,4 +27,11 @@ public class WeepingVinesBlock extends GrowingPlantHeadBlock { - protected boolean canGrowInto(BlockState state) { - return NetherVines.isValidGrowthState(state); - } -+ -+ // Purpur start -+ @Override -+ public int getMaxGrowthAge() { -+ return org.purpurmc.purpur.PurpurConfig.weepingVinesMaxGrowthAge; -+ } -+ // Purpur end - } -diff --git a/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java b/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java -index b91effe91dad2e1aeea0ea31140f7432833b343f..bb628bd3fe8b185f356968697b17e1c4a442a6d2 100644 ---- a/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/WitherSkullBlock.java -@@ -71,6 +71,7 @@ public class WitherSkullBlock extends SkullBlock { - entitywither.moveTo((double) blockposition1.getX() + 0.5D, (double) blockposition1.getY() + 0.55D, (double) blockposition1.getZ() + 0.5D, shapedetector_shapedetectorcollection.getForwards().getAxis() == Direction.Axis.X ? 0.0F : 90.0F, 0.0F); - entitywither.yBodyRot = shapedetector_shapedetectorcollection.getForwards().getAxis() == Direction.Axis.X ? 0.0F : 90.0F; - entitywither.makeInvulnerable(); -+ entitywither.setSummoner(iblockdata.getBlock().placer == null ? null : iblockdata.getBlock().placer.getUUID()); // Purpur - // CraftBukkit start - if (!world.addFreshEntity(entitywither, SpawnReason.BUILD_WITHER)) { - return; -diff --git a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -index 13f9226d4f8f591cd0095fda2f68bfad8e89b2d4..7a5075529c2dab16b5b5e2cf06a1c3719d96fef6 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -@@ -46,6 +46,7 @@ import net.minecraft.world.level.Level; - import net.minecraft.world.level.block.AbstractFurnaceBlock; - import net.minecraft.world.level.block.Blocks; - import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.material.FluidState; - import net.minecraft.world.phys.Vec3; - // CraftBukkit start - import org.bukkit.craftbukkit.block.CraftBlock; -@@ -209,6 +210,22 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit - // Paper end - } - -+ // Purpur start -+ public static void addFuel(ItemStack itemStack, Integer burnTime) { -+ Map map = Maps.newLinkedHashMap(); -+ map.putAll(getFuel()); -+ map.put(itemStack.getItem(), burnTime); -+ cachedBurnDurations = com.google.common.collect.ImmutableMap.copyOf(map); -+ } -+ -+ public static void removeFuel(ItemStack itemStack) { -+ Map map = Maps.newLinkedHashMap(); -+ map.putAll(getFuel()); -+ map.remove(itemStack.getItem()); -+ cachedBurnDurations = com.google.common.collect.ImmutableMap.copyOf(map); -+ } -+ // Purpur End -+ - // CraftBukkit start - add fields and methods - private int maxStack = MAX_STACK; - public List transaction = new java.util.ArrayList(); -@@ -326,6 +343,21 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit - } - - ItemStack itemstack = (ItemStack) blockEntity.items.get(1); -+ // Purpur start -+ boolean usedLavaFromUnderneath = false; -+ if (world.purpurConfig.furnaceUseLavaFromUnderneath && !blockEntity.isLit() && itemstack.isEmpty() && !blockEntity.items.get(0).isEmpty() && world.getGameTime() % 20 == 0) { -+ BlockPos below = blockEntity.getBlockPos().below(); -+ BlockState belowState = world.getBlockStateIfLoaded(below); -+ if (belowState != null && belowState.is(Blocks.LAVA)) { -+ FluidState fluidState = belowState.getFluidState(); -+ if (fluidState != null && fluidState.isSource()) { -+ world.setBlock(below, Blocks.AIR.defaultBlockState(), 3); -+ itemstack = Items.LAVA_BUCKET.getDefaultInstance(); -+ usedLavaFromUnderneath = true; -+ } -+ } -+ } -+ // Purpur end - boolean flag2 = !((ItemStack) blockEntity.items.get(0)).isEmpty(); - boolean flag3 = !itemstack.isEmpty(); - -@@ -411,6 +443,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit - setChanged(world, pos, state); - } - -+ if (usedLavaFromUnderneath) blockEntity.items.set(1, ItemStack.EMPTY); // Purpur - } - - private static boolean canBurn(RegistryAccess registryManager, @Nullable Recipe recipe, NonNullList slots, int count) { -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java -index 416aa989ebb18a8741cc9d605a1180ab830f6643..e38a0adf5463c48311ad08b8d2e5b5c2d989a3b5 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BarrelBlockEntity.java -@@ -67,7 +67,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity { - - public BarrelBlockEntity(BlockPos pos, BlockState state) { - super(BlockEntityType.BARREL, pos, state); -- this.items = NonNullList.withSize(27, ItemStack.EMPTY); -+ // Purpur start -+ this.items = NonNullList.withSize(switch (org.purpurmc.purpur.PurpurConfig.barrelRows) { -+ case 6 -> 54; -+ case 5 -> 45; -+ case 4 -> 36; -+ case 2 -> 18; -+ case 1 -> 9; -+ default -> 27; -+ }, ItemStack.EMPTY); -+ // Purpur end - this.openersCounter = new ContainerOpenersCounter() { - @Override - protected void onOpen(Level world, BlockPos pos, BlockState state) { -@@ -118,7 +127,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity { - - @Override - public int getContainerSize() { -- return 27; -+ // Purpur start -+ return switch (org.purpurmc.purpur.PurpurConfig.barrelRows) { -+ case 6 -> 54; -+ case 5 -> 45; -+ case 4 -> 36; -+ case 2 -> 18; -+ case 1 -> 9; -+ default -> 27; -+ }; -+ // Purpur end - } - - @Override -@@ -138,7 +156,16 @@ public class BarrelBlockEntity extends RandomizableContainerBlockEntity { - - @Override - protected AbstractContainerMenu createMenu(int syncId, Inventory playerInventory) { -- return ChestMenu.threeRows(syncId, playerInventory, this); -+ // Purpur start -+ return switch (org.purpurmc.purpur.PurpurConfig.barrelRows) { -+ case 6 -> ChestMenu.sixRows(syncId, playerInventory, this); -+ case 5 -> ChestMenu.fiveRows(syncId, playerInventory, this); -+ case 4 -> ChestMenu.fourRows(syncId, playerInventory, this); -+ case 2 -> ChestMenu.twoRows(syncId, playerInventory, this); -+ case 1 -> ChestMenu.oneRow(syncId, playerInventory, this); -+ default -> ChestMenu.threeRows(syncId, playerInventory, this); -+ }; -+ // Purpur end - } - - @Override -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -index ef740d1ad6352ca4af299001a081b720bc472d2e..8f82b0ce87afc8890c5b3386d5f6e22c48974b16 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java -@@ -84,6 +84,16 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - - public double getEffectRange() { - if (this.effectRange < 0) { -+ // Purpur Start -+ if (this.level != null) { -+ switch (this.levels) { -+ case 1: return this.level.purpurConfig.beaconLevelOne; -+ case 2: return this.level.purpurConfig.beaconLevelTwo; -+ case 3: return this.level.purpurConfig.beaconLevelThree; -+ case 4: return this.level.purpurConfig.beaconLevelFour; -+ } -+ } -+ // Purpur End - return this.levels * 10 + 10; - } else { - return effectRange; -@@ -155,6 +165,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - int j = pos.getY(); - int k = pos.getZ(); - BlockPos blockposition1; -+ boolean isTintedGlass = false; - - if (blockEntity.lastCheckY < j) { - blockposition1 = pos; -@@ -188,6 +199,9 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - } - } - } else { -+ if (world.purpurConfig.beaconAllowEffectsWithTintedGlass && block.equals(Blocks.TINTED_GLASS)) { -+ isTintedGlass = true; -+ } - if (tileentitybeacon_beaconcolortracker == null || iblockdata1.getLightBlock(world, blockposition1) >= 15 && !iblockdata1.is(Blocks.BEDROCK)) { - blockEntity.checkingBeamSections.clear(); - blockEntity.lastCheckY = l; -@@ -207,7 +221,7 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name - blockEntity.levels = BeaconBlockEntity.updateBase(world, i, j, k); - } - -- if (blockEntity.levels > 0 && !blockEntity.beamSections.isEmpty()) { -+ if (blockEntity.levels > 0 && (!blockEntity.beamSections.isEmpty() || (world.purpurConfig.beaconAllowEffectsWithTintedGlass && isTintedGlass))) { - BeaconBlockEntity.applyEffects(world, pos, blockEntity.levels, blockEntity.primaryPower, blockEntity.secondaryPower, blockEntity); // Paper - BeaconBlockEntity.playSound(world, pos, SoundEvents.BEACON_AMBIENT); - } -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java -index 41c9f074203915c31c1ae7a160ce509c13383f84..7b82842b97ce795745cf6ee6399f618c55acbbf3 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java -@@ -43,7 +43,7 @@ public class BeehiveBlockEntity extends BlockEntity { - private final List stored = Lists.newArrayList(); - @Nullable - public BlockPos savedFlowerPos; -- public int maxBees = 3; // CraftBukkit - allow setting max amount of bees a hive can hold -+ public int maxBees = org.purpurmc.purpur.PurpurConfig.beeInsideBeeHive; // CraftBukkit - allow setting max amount of bees a hive can hold // Purpur - - public BeehiveBlockEntity(BlockPos pos, BlockState state) { - super(BlockEntityType.BEEHIVE, pos, state); -@@ -203,7 +203,7 @@ public class BeehiveBlockEntity extends BlockEntity { - } - - private static boolean releaseBee(Level world, BlockPos blockposition, BlockState iblockdata, BeehiveBlockEntity.BeeData tileentitybeehive_hivebee, @Nullable List list, BeehiveBlockEntity.BeeReleaseStatus tileentitybeehive_releasestatus, @Nullable BlockPos blockposition1, boolean force) { -- if (!force && (world.isNight() || world.isRaining()) && tileentitybeehive_releasestatus != BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY) { -+ if (!force && ((world.isNight() && !world.purpurConfig.beeCanWorkAtNight) || (world.isRaining() && !world.purpurConfig.beeCanWorkInRain)) && tileentitybeehive_releasestatus != BeehiveBlockEntity.BeeReleaseStatus.EMERGENCY) { // Purpur - // CraftBukkit end - return false; - } else { -diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -index 1b248db497500aa6bd346b306dcb908af77626f3..e438e7e018f643d82ddf5efbf72779876c516d1a 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java -@@ -6,6 +6,8 @@ import net.minecraft.CrashReportCategory; - import net.minecraft.core.BlockPos; - import net.minecraft.core.registries.BuiltInRegistries; - import net.minecraft.nbt.CompoundTag; -+import net.minecraft.nbt.ListTag; -+import net.minecraft.nbt.StringTag; - import net.minecraft.network.protocol.Packet; - import net.minecraft.network.protocol.game.ClientGamePacketListener; - import net.minecraft.resources.ResourceLocation; -@@ -74,10 +76,27 @@ public abstract class BlockEntity { - if (persistentDataTag instanceof CompoundTag) { - this.persistentDataContainer.putAll((CompoundTag) persistentDataTag); - } -+ // Purpur start -+ if (nbt.contains("Purpur.persistentDisplayName")) { -+ this.persistentDisplayName = nbt.getString("Purpur.persistentDisplayName"); -+ } -+ if (nbt.contains("Purpur.persistentLore")) { -+ this.persistentLore = nbt.getList("Purpur.persistentLore", 8); -+ } -+ // Purpur end - } - // CraftBukkit end - -- protected void saveAdditional(CompoundTag nbt) {} -+ protected void saveAdditional(CompoundTag nbt) { -+ // Purpur start -+ if (this.persistentDisplayName != null) { -+ nbt.put("Purpur.persistentDisplayName", StringTag.valueOf(this.persistentDisplayName)); -+ } -+ if (this.persistentLore != null) { -+ nbt.put("Purpur.persistentLore", this.persistentLore); -+ } -+ // Purpur end -+ } - - public final CompoundTag saveWithFullMetadata() { - CompoundTag nbttagcompound = this.saveWithoutMetadata(); -@@ -187,10 +206,24 @@ public abstract class BlockEntity { - - @Nullable - public Packet getUpdatePacket() { -+ // Purpur start -+ if (this instanceof net.minecraft.world.Nameable nameable && nameable.hasCustomName()) { -+ CompoundTag nbt = this.saveWithoutMetadata(); -+ nbt.remove("Items"); -+ return net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket.create(this, $ -> nbt); -+ } -+ // Purpur end - return null; - } - - public CompoundTag getUpdateTag() { -+ // Purpur start -+ if (this instanceof net.minecraft.world.Nameable nameable && nameable.hasCustomName()) { -+ CompoundTag nbt = this.saveWithoutMetadata(); -+ nbt.remove("Items"); -+ return nbt; -+ } -+ // Purpur end - return new CompoundTag(); - } - -@@ -264,4 +297,24 @@ public abstract class BlockEntity { - } - // Paper end - -+ // Purpur start -+ private String persistentDisplayName = null; -+ private ListTag persistentLore = null; -+ -+ public void setPersistentDisplayName(String json) { -+ this.persistentDisplayName = json; -+ } -+ -+ public void setPersistentLore(ListTag lore) { -+ this.persistentLore = lore; -+ } -+ -+ public String getPersistentDisplayName() { -+ return this.persistentDisplayName; -+ } -+ -+ public ListTag getPersistentLore() { -+ return this.persistentLore; -+ } -+ // Purpur end - } -diff --git a/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java -index 963a596154091b79ca139af6274aa323518ad1ad..4dcac3899a500d8586580bcfd5b4516e1dcdcd4a 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/ConduitBlockEntity.java -@@ -171,7 +171,7 @@ public class ConduitBlockEntity extends BlockEntity { - if ((l > 1 || i1 > 1 || j1 > 1) && (i == 0 && (i1 == 2 || j1 == 2) || j == 0 && (l == 2 || j1 == 2) || k == 0 && (l == 2 || i1 == 2))) { - BlockPos blockposition2 = pos.offset(i, j, k); - BlockState iblockdata = world.getBlockState(blockposition2); -- Block[] ablock = ConduitBlockEntity.VALID_BLOCKS; -+ Block[] ablock = world.purpurConfig.conduitBlocks; // Purpur - int k1 = ablock.length; - - for (int l1 = 0; l1 < k1; ++l1) { -@@ -191,7 +191,7 @@ public class ConduitBlockEntity extends BlockEntity { - - private static void applyEffects(Level world, BlockPos pos, List activatingBlocks) { - int i = activatingBlocks.size(); -- int j = i / 7 * 16; -+ int j = i / 7 * world.purpurConfig.conduitDistance; // Purpur - int k = pos.getX(); - int l = pos.getY(); - int i1 = pos.getZ(); -@@ -222,21 +222,21 @@ public class ConduitBlockEntity extends BlockEntity { - blockEntity.destroyTarget = ConduitBlockEntity.findDestroyTarget(world, pos, blockEntity.destroyTargetUUID); - blockEntity.destroyTargetUUID = null; - } else if (blockEntity.destroyTarget == null) { -- List list1 = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(pos), (entityliving1) -> { -+ List list1 = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(pos, world), (entityliving1) -> { // Purpur - return entityliving1 instanceof Enemy && entityliving1.isInWaterOrRain(); - }); - - if (!list1.isEmpty()) { - blockEntity.destroyTarget = (LivingEntity) list1.get(world.random.nextInt(list1.size())); - } -- } else if (!blockEntity.destroyTarget.isAlive() || !pos.closerThan(blockEntity.destroyTarget.blockPosition(), 8.0D)) { -+ } else if (!blockEntity.destroyTarget.isAlive() || !pos.closerThan(blockEntity.destroyTarget.blockPosition(), world.purpurConfig.conduitDamageDistance)) { // Purpur - blockEntity.destroyTarget = null; - } - - if (blockEntity.destroyTarget != null) { - // CraftBukkit start - CraftEventFactory.blockDamage = CraftBlock.at(world, pos); -- if (blockEntity.destroyTarget.hurt(world.damageSources().magic(), 4.0F)) { -+ if (blockEntity.destroyTarget.hurt(world.damageSources().magic(), world.purpurConfig.conduitDamageAmount)) { // Purpur - world.playSound((Player) null, blockEntity.destroyTarget.getX(), blockEntity.destroyTarget.getY(), blockEntity.destroyTarget.getZ(), SoundEvents.CONDUIT_ATTACK_TARGET, SoundSource.BLOCKS, 1.0F, 1.0F); - } - CraftEventFactory.blockDamage = null; -@@ -262,16 +262,22 @@ public class ConduitBlockEntity extends BlockEntity { - } - - private static AABB getDestroyRangeAABB(BlockPos pos) { -+ // Purpur start -+ return getDestroyRangeAABB(pos, null); -+ } -+ -+ private static AABB getDestroyRangeAABB(BlockPos pos, Level level) { -+ // Purpur end - int i = pos.getX(); - int j = pos.getY(); - int k = pos.getZ(); - -- return (new AABB((double) i, (double) j, (double) k, (double) (i + 1), (double) (j + 1), (double) (k + 1))).inflate(8.0D); -+ return (new AABB((double) i, (double) j, (double) k, (double) (i + 1), (double) (j + 1), (double) (k + 1))).inflate(level == null ? 8.0D : level.purpurConfig.conduitDamageDistance); // Purpur - } - - @Nullable - private static LivingEntity findDestroyTarget(Level world, BlockPos pos, UUID uuid) { -- List list = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(pos), (entityliving) -> { -+ List list = world.getEntitiesOfClass(LivingEntity.class, ConduitBlockEntity.getDestroyRangeAABB(pos, world), (entityliving) -> { // Purpur - return entityliving.getUUID().equals(uuid); - }); - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/EnchantmentTableBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/EnchantmentTableBlockEntity.java -index 65e1381bb2d10bd212463feb602c60f8fdb9ade1..b7370e64fd0d50e8725d7d5afc30af2e8bc8455d 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/EnchantmentTableBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/EnchantmentTableBlockEntity.java -@@ -24,6 +24,7 @@ public class EnchantmentTableBlockEntity extends BlockEntity implements Nameable - public float tRot; - private static final RandomSource RANDOM = RandomSource.create(); - private Component name; -+ private int lapis = 0; // Purpur - - public EnchantmentTableBlockEntity(BlockPos pos, BlockState state) { - super(BlockEntityType.ENCHANTING_TABLE, pos, state); -@@ -35,6 +36,7 @@ public class EnchantmentTableBlockEntity extends BlockEntity implements Nameable - if (this.hasCustomName()) { - nbt.putString("CustomName", Component.Serializer.toJson(this.name)); - } -+ nbt.putInt("Purpur.Lapis", this.lapis); // Purpur - - } - -@@ -44,6 +46,7 @@ public class EnchantmentTableBlockEntity extends BlockEntity implements Nameable - if (nbt.contains("CustomName", 8)) { - this.name = io.papermc.paper.util.MCUtil.getBaseComponentFromNbt("CustomName", nbt); // Paper - Catch ParseException - } -+ this.lapis = nbt.getInt("Purpur.Lapis"); // Purpur - - } - -@@ -117,4 +120,14 @@ public class EnchantmentTableBlockEntity extends BlockEntity implements Nameable - public Component getCustomName() { - return this.name; - } -+ -+ // Purpur start -+ public int getLapis() { -+ return this.lapis; -+ } -+ -+ public void setLapis(int lapis) { -+ this.lapis = lapis; -+ } -+ // Purpur - } -diff --git a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java -index 4da4edae517a0efec6e03a719ec47b700509dab1..9e760a8e8244b15daaf0abdfc5f8a51d5c663e12 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java -@@ -203,6 +203,23 @@ public class SignBlockEntity extends BlockEntity implements CommandSource { // C - return ClientboundBlockEntityDataPacket.create(this); - } - -+ // Purpur start -+ public ClientboundBlockEntityDataPacket getTranslatedUpdatePacket(boolean filtered) { -+ final CompoundTag nbt = new CompoundTag(); -+ this.saveAdditional(nbt); -+ final Component[] lines = getMessages(filtered); -+ for (int i = 0; i < 4; i++) { -+ final var component = io.papermc.paper.adventure.PaperAdventure.asAdventure(lines[i]); -+ final String line = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacyAmpersand().serialize(component); -+ final var text = net.kyori.adventure.text.Component.text(line); -+ final String json = net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson().serialize(text); -+ nbt.putString("Text" + (i + 1), json); -+ } -+ nbt.putString("PurpurEditor", "true"); -+ return ClientboundBlockEntityDataPacket.create(this, entity -> nbt); -+ } -+ // Purpur end -+ - @Override - public CompoundTag getUpdateTag() { - return this.saveWithoutMetadata(); -diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonStructureResolver.java b/src/main/java/net/minecraft/world/level/block/piston/PistonStructureResolver.java -index 744d91546d1a810f60a43c15ed74b4158f341a4a..354538daefa603f6df5a139b6bff87dbb4cef178 100644 ---- a/src/main/java/net/minecraft/world/level/block/piston/PistonStructureResolver.java -+++ b/src/main/java/net/minecraft/world/level/block/piston/PistonStructureResolver.java -@@ -86,7 +86,7 @@ public class PistonStructureResolver { - return true; - } else { - int i = 1; -- if (i + this.toPush.size() > 12) { -+ if (i + this.toPush.size() > this.level.purpurConfig.pistonBlockPushLimit) { // Purpur - return false; - } else { - while(isSticky(blockState)) { -@@ -98,7 +98,7 @@ public class PistonStructureResolver { - } - - ++i; -- if (i + this.toPush.size() > 12) { -+ if (i + this.toPush.size() > this.level.purpurConfig.pistonBlockPushLimit) { // Purpur - return false; - } - } -@@ -142,7 +142,7 @@ public class PistonStructureResolver { - return true; - } - -- if (this.toPush.size() >= 12) { -+ if (this.toPush.size() >= this.level.purpurConfig.pistonBlockPushLimit) { // Purpur - return false; - } - -diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -index 505503a3f59d4b747649275c6f6faa504b7c7b64..bee42ce7c1cb0f5ebd4890c02bc9c5dd727f7fd6 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -@@ -78,9 +78,9 @@ import net.minecraft.world.phys.shapes.VoxelShape; - public abstract class BlockBehaviour implements FeatureElement { - - protected static final Direction[] UPDATE_SHAPE_ORDER = new Direction[]{Direction.WEST, Direction.EAST, Direction.NORTH, Direction.SOUTH, Direction.DOWN, Direction.UP}; -- protected final Material material; -+ public final Material material; // Purpur - protected -> public - public final boolean hasCollision; -- protected final float explosionResistance; -+ public float explosionResistance; // Purpur - protected final -> public - protected final boolean isRandomlyTicking; - protected final SoundType soundType; - protected final float friction; -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index 43c3ff92bad2b812fbad9af65bde17681690a72e..26b7ddbe017794a969c330286990f680df2da100 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -92,7 +92,7 @@ public class LevelChunk extends ChunkAccess { - // shouldDoLightning compiles down to 29 bytes, which with the default of 35 byte inlining should guarantee an inline - public final boolean shouldDoLightning(net.minecraft.util.RandomSource random) { - if (this.lightningTick-- <= 0) { -- this.lightningTick = random.nextInt(this.level.spigotConfig.thunderChance) << 1; -+ this.lightningTick = java.util.concurrent.ThreadLocalRandom.current().nextInt(this.level.spigotConfig.thunderChance) << 1; // Purpur - any random will do - return true; - } - return false; -@@ -127,7 +127,7 @@ public class LevelChunk extends ChunkAccess { - this.postLoad = entityLoader; - this.blockTicks = blockTickScheduler; - this.fluidTicks = fluidTickScheduler; -- this.lightningTick = this.level.randomTickRandom.nextInt(100000) << 1; // Gale - Airplane - optimize random calls in chunk ticking - initialize lightning tick -+ this.lightningTick = java.util.concurrent.ThreadLocalRandom.current().nextInt(100000) << 1; // Gale - Airplane - optimize random calls in chunk ticking - initialize lightning tick - } - - // CraftBukkit start -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java -index 060e064625969610539dbf969ce773b877a7c579..32cd9df202704cdfb8fa06aaf0e738d483054feb 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java -@@ -112,6 +112,7 @@ public class EntityStorage implements EntityPersistentStorage { - ListTag listTag = new ListTag(); - final java.util.Map, Integer> savedEntityCounts = new java.util.HashMap<>(); // Paper - entities.forEach((entity) -> { // diff here: use entities parameter -+ if (!entity.canSaveToDisk()) return; // Purpur - // Paper start - final EntityType entityType = entity.getType(); - final int saveLimit = level.paperConfig().chunks.entityPerChunkSaveLimit.getOrDefault(entityType, -1); -diff --git a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java -index ffef23b9455613e8d9e4cc4e21e52b76603ee1bf..80eafd6c9e981f4c3223c3388d515e19121e8bc4 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/PhantomSpawner.java -@@ -51,7 +51,7 @@ public class PhantomSpawner implements CustomSpawner { - int spawnAttemptMaxSeconds = world.paperConfig().entities.behavior.phantomsSpawnAttemptMaxSeconds; - this.nextTick += (spawnAttemptMinSeconds + randomsource.nextInt(spawnAttemptMaxSeconds - spawnAttemptMinSeconds + 1)) * 20; - // Paper end -- if (world.getSkyDarken() < 5 && world.dimensionType().hasSkyLight()) { -+ if (world.getSkyDarken() < world.purpurConfig.phantomSpawnMinSkyDarkness && world.dimensionType().hasSkyLight()) { // Purpur - return 0; - } else { - int i = 0; -@@ -63,10 +63,10 @@ public class PhantomSpawner implements CustomSpawner { - if (!entityhuman.isSpectator() && (!world.paperConfig().entities.behavior.phantomsDoNotSpawnOnCreativePlayers || !entityhuman.isCreative())) { // Paper - BlockPos blockposition = entityhuman.blockPosition(); - -- if (!world.dimensionType().hasSkyLight() || blockposition.getY() >= world.getSeaLevel() && world.canSeeSky(blockposition)) { -+ if (!world.dimensionType().hasSkyLight() || (!world.purpurConfig.phantomSpawnOnlyAboveSeaLevel || blockposition.getY() >= world.getSeaLevel()) && (!world.purpurConfig.phantomSpawnOnlyWithVisibleSky || world.canSeeSky(blockposition))) { // Purpur - DifficultyInstance difficultydamagescaler = world.getCurrentDifficultyAt(blockposition); - -- if (difficultydamagescaler.isHarderThan(randomsource.nextFloat() * 3.0F)) { -+ if (difficultydamagescaler.isHarderThan(randomsource.nextFloat() * (float) world.purpurConfig.phantomSpawnLocalDifficultyChance)) { // Purpur - ServerStatsCounter serverstatisticmanager = ((ServerPlayer) entityhuman).getStats(); - int j = Mth.clamp(serverstatisticmanager.getValue(Stats.CUSTOM.get(Stats.TIME_SINCE_REST)), 1, Integer.MAX_VALUE); - boolean flag2 = true; -@@ -86,7 +86,7 @@ public class PhantomSpawner implements CustomSpawner { - - if (NaturalSpawner.isValidEmptySpawnBlock(world, blockposition1, iblockdata, fluid, EntityType.PHANTOM)) { - SpawnGroupData groupdataentity = null; -- int k = 1 + randomsource.nextInt(difficultydamagescaler.getDifficulty().getId() + 1); -+ int k = world.purpurConfig.phantomSpawnMinPerAttempt + world.random.nextInt((world.purpurConfig.phantomSpawnMaxPerAttempt < 0 ? difficultydamagescaler.getDifficulty().getId() : world.purpurConfig.phantomSpawnMaxPerAttempt - world.purpurConfig.phantomSpawnMinPerAttempt) + 1); // Purpur - - for (int l = 0; l < k; ++l) { - // Paper start -diff --git a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java -index 363aed175ecc5cccd8f798503841948b72a98d5b..122e86327a503b79b5ebcaf59e5ce4683d22b3ce 100644 ---- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java -+++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java -@@ -226,7 +226,7 @@ public abstract class FlowingFluid extends Fluid { - } - } - -- if (this.canConvertToSource(world) && j >= 2) { -+ if (this.canConvertToSource(world) && j >= getRequiredSources(world)) { - BlockState iblockdata2 = world.getBlockState(pos.below()); - FluidState fluid1 = iblockdata2.getFluidState(); - -@@ -324,6 +324,12 @@ public abstract class FlowingFluid extends Fluid { - - protected abstract boolean canConvertToSource(Level world); - -+ // Purpur start -+ protected int getRequiredSources(Level level) { -+ return 2; -+ } -+ // Purpur end -+ - protected void spreadTo(LevelAccessor world, BlockPos pos, BlockState state, Direction direction, FluidState fluidState) { - if (state.getBlock() instanceof LiquidBlockContainer) { - ((LiquidBlockContainer) state.getBlock()).placeLiquid(world, pos, state, fluidState); -diff --git a/src/main/java/net/minecraft/world/level/material/LavaFluid.java b/src/main/java/net/minecraft/world/level/material/LavaFluid.java -index 783e315d92227cbcb5cd207b0a06a12e0778d14b..b05b4d3d97bca159c297f150005b5ab5bf6087e0 100644 ---- a/src/main/java/net/minecraft/world/level/material/LavaFluid.java -+++ b/src/main/java/net/minecraft/world/level/material/LavaFluid.java -@@ -180,7 +180,7 @@ public abstract class LavaFluid extends FlowingFluid { - - @Override - public int getTickDelay(LevelReader world) { -- return world.dimensionType().ultraWarm() ? 10 : 30; -+ return world.dimensionType().ultraWarm() ? world.getWorldBorder().world.purpurConfig.lavaSpeedNether : world.getWorldBorder().world.purpurConfig.lavaSpeedNotNether; // Purpur - } - - @Override -@@ -198,6 +198,13 @@ public abstract class LavaFluid extends FlowingFluid { - world.levelEvent(1501, pos, 0); - } - -+ // Purpur start -+ @Override -+ protected int getRequiredSources(Level level) { -+ return level.purpurConfig.lavaInfiniteRequiredSources; -+ } -+ // Purpur end -+ - @Override - protected boolean canConvertToSource(Level world) { - return world.getGameRules().getBoolean(GameRules.RULE_LAVA_SOURCE_CONVERSION); -@@ -232,7 +239,7 @@ public abstract class LavaFluid extends FlowingFluid { - - @Override - protected float getExplosionResistance() { -- return 100.0F; -+ return Blocks.LAVA.getExplosionResistance(); // Purpur - } - - @Override -diff --git a/src/main/java/net/minecraft/world/level/material/WaterFluid.java b/src/main/java/net/minecraft/world/level/material/WaterFluid.java -index 82e85fbbd45244d02df90fa00c9046e7f51275a2..0f16deddd8cbb506ef7886f57ae640a42e841703 100644 ---- a/src/main/java/net/minecraft/world/level/material/WaterFluid.java -+++ b/src/main/java/net/minecraft/world/level/material/WaterFluid.java -@@ -64,6 +64,13 @@ public abstract class WaterFluid extends FlowingFluid { - return world.getGameRules().getBoolean(GameRules.RULE_WATER_SOURCE_CONVERSION); - } - -+ // Purpur start -+ @Override -+ protected int getRequiredSources(Level level) { -+ return level.purpurConfig.waterInfiniteRequiredSources; -+ } -+ // Purpur end -+ - // Paper start - @Override - protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state, BlockPos source) { -@@ -109,7 +116,7 @@ public abstract class WaterFluid extends FlowingFluid { - - @Override - protected float getExplosionResistance() { -- return 100.0F; -+ return Blocks.WATER.getExplosionResistance(); // Purpur - } - - @Override -diff --git a/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java b/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java -index 94a0fde36dda9404e5eb62d323c71dac1929a46b..a7578e112e80ed2585a7eb4fff9542f6068546be 100644 ---- a/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java -+++ b/src/main/java/net/minecraft/world/level/pathfinder/WalkNodeEvaluator.java -@@ -243,7 +243,7 @@ public class WalkNodeEvaluator extends NodeEvaluator { - } - - if (blockPathTypes != BlockPathTypes.WALKABLE && (!this.isAmphibious() || blockPathTypes != BlockPathTypes.WATER)) { -- if ((node == null || node.costMalus < 0.0F) && maxYStep > 0 && (blockPathTypes != BlockPathTypes.FENCE || this.canWalkOverFences()) && blockPathTypes != BlockPathTypes.UNPASSABLE_RAIL && blockPathTypes != BlockPathTypes.TRAPDOOR && blockPathTypes != BlockPathTypes.POWDER_SNOW) { -+ if ((node == null || node.costMalus < 0.0F) && maxYStep > 0 && (blockPathTypes != BlockPathTypes.FENCE || this.canWalkOverFences()) && (this.mob.level.purpurConfig.mobsIgnoreRails || blockPathTypes != BlockPathTypes.UNPASSABLE_RAIL) && blockPathTypes != BlockPathTypes.TRAPDOOR && blockPathTypes != BlockPathTypes.POWDER_SNOW) { // Purpur - node = this.findAcceptedNode(x, y + 1, z, maxYStep - 1, prevFeetY, direction, nodeType); - if (node != null && (node.type == BlockPathTypes.OPEN || node.type == BlockPathTypes.WALKABLE) && this.mob.getBbWidth() < 1.0F) { - double g = (double)(x - direction.getStepX()) + 0.5D; -@@ -463,7 +463,7 @@ public class WalkNodeEvaluator extends NodeEvaluator { - return BlockPathTypes.BLOCKED; - } else { - // Paper end -- if (blockState.is(Blocks.CACTUS) || blockState.is(Blocks.SWEET_BERRY_BUSH)) { -+ if (blockState.is(Blocks.CACTUS) || blockState.is(Blocks.SWEET_BERRY_BUSH) || blockState.is(Blocks.STONECUTTER)) { // Purpur - return BlockPathTypes.DANGER_OTHER; - } - -@@ -493,7 +493,7 @@ public class WalkNodeEvaluator extends NodeEvaluator { - } else if (!blockState.is(BlockTags.TRAPDOORS) && !blockState.is(Blocks.LILY_PAD) && !blockState.is(Blocks.BIG_DRIPLEAF)) { - if (blockState.is(Blocks.POWDER_SNOW)) { - return BlockPathTypes.POWDER_SNOW; -- } else if (!blockState.is(Blocks.CACTUS) && !blockState.is(Blocks.SWEET_BERRY_BUSH)) { -+ } else if (!blockState.is(Blocks.CACTUS) && !blockState.is(Blocks.SWEET_BERRY_BUSH) && !blockState.is(Blocks.STONECUTTER)) { // Purpur - if (blockState.is(Blocks.HONEY_BLOCK)) { - return BlockPathTypes.STICKY_HONEY; - } else if (blockState.is(Blocks.COCOA)) { -diff --git a/src/main/java/net/minecraft/world/level/portal/PortalShape.java b/src/main/java/net/minecraft/world/level/portal/PortalShape.java -index c461e0d04047db9c0c5ecc04063cebd38bf96ec2..e7554ec800f321e4e34c926c53f2375a8c3aa979 100644 ---- a/src/main/java/net/minecraft/world/level/portal/PortalShape.java -+++ b/src/main/java/net/minecraft/world/level/portal/PortalShape.java -@@ -34,7 +34,7 @@ public class PortalShape { - private static final int MIN_HEIGHT = 3; - public static final int MAX_HEIGHT = 21; - private static final BlockBehaviour.StatePredicate FRAME = (iblockdata, iblockaccess, blockposition) -> { -- return iblockdata.is(Blocks.OBSIDIAN); -+ return iblockdata.is(Blocks.OBSIDIAN) || (org.purpurmc.purpur.PurpurConfig.cryingObsidianValidForPortalFrame && iblockdata.is(Blocks.CRYING_OBSIDIAN)); // Purpur - }; - private static final float SAFE_TRAVEL_MAX_ENTITY_XY = 4.0F; - private static final double SAFE_TRAVEL_MAX_VERTICAL_DELTA = 1.0D; -diff --git a/src/main/java/net/minecraft/world/level/storage/loot/functions/LootingEnchantFunction.java b/src/main/java/net/minecraft/world/level/storage/loot/functions/LootingEnchantFunction.java -index 31918fa2eb38e42a5ea5366e559f25ea9d7d59ae..15d8e9261a89da30529ac347462c520920ca4e7d 100644 ---- a/src/main/java/net/minecraft/world/level/storage/loot/functions/LootingEnchantFunction.java -+++ b/src/main/java/net/minecraft/world/level/storage/loot/functions/LootingEnchantFunction.java -@@ -49,6 +49,13 @@ public class LootingEnchantFunction extends LootItemConditionalFunction { - - if (entity instanceof LivingEntity) { - int i = EnchantmentHelper.getMobLooting((LivingEntity) entity); -+ // Purpur start -+ if (org.purpurmc.purpur.PurpurConfig.fixProjectileLootingTransfer && -+ context.getParamOrNull(LootContextParams.DIRECT_KILLER_ENTITY) -+ instanceof net.minecraft.world.entity.projectile.AbstractArrow arrow) { -+ i = arrow.lootingLevel; -+ } -+ // Purpur end - // CraftBukkit start - use lootingModifier if set by plugin - if (context.hasParam(LootContextParams.LOOTING_MOD)) { - i = context.getParamOrNull(LootContextParams.LOOTING_MOD); -diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java -index ffc76354ead6937daf366c3d87bcb51d3e4c47f5..5b98d42b5d6bc07265fbb017e51a6281c148436a 100644 ---- a/src/main/java/net/minecraft/world/phys/AABB.java -+++ b/src/main/java/net/minecraft/world/phys/AABB.java -@@ -374,4 +374,10 @@ public class AABB { - public static AABB ofSize(Vec3 center, double dx, double dy, double dz) { - return new AABB(center.x - dx / 2.0D, center.y - dy / 2.0D, center.z - dz / 2.0D, center.x + dx / 2.0D, center.y + dy / 2.0D, center.z + dz / 2.0D); - } -+ -+ // Purpur - tuinity added method -+ public final AABB offsetY(double dy) { -+ return new AABB(this.minX, this.minY + dy, this.minZ, this.maxX, this.maxY + dy, this.maxZ); -+ } -+ // Purpur - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -index 714afc98b5150907b45a00060be4e41582333204..312a6d90c0a09570aef24c205dc2ff277dcd4279 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java -@@ -549,4 +549,213 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa - manager.save(); - } - } -+ -+ // Purpur start - OfflinePlayer API -+ @Override -+ public boolean getAllowFlight() { -+ if (this.isOnline()) { -+ return this.getPlayer().getAllowFlight(); -+ } else { -+ CompoundTag data = this.getData(); -+ if (data == null) return false; -+ if (!data.contains("abilities")) return false; -+ CompoundTag abilities = data.getCompound("abilities"); -+ return abilities.getByte("mayfly") == (byte) 1; -+ } -+ } -+ -+ @Override -+ public void setAllowFlight(boolean flight) { -+ if (this.isOnline()) { -+ this.getPlayer().setAllowFlight(flight); -+ } else { -+ CompoundTag data = this.getData(); -+ if (data == null) return; -+ if (!data.contains("abilities")) return; -+ CompoundTag abilities = data.getCompound("abilities"); -+ abilities.putByte("mayfly", (byte) (flight ? 1 : 0)); -+ data.put("abilities", abilities); -+ save(data); -+ } -+ } -+ -+ @Override -+ public boolean isFlying() { -+ if (this.isOnline()) { -+ return this.isFlying(); -+ } else { -+ CompoundTag data = this.getData(); -+ if (data == null) return false; -+ if (!data.contains("abilities")) return false; -+ CompoundTag abilities = data.getCompound("abilities"); -+ return abilities.getByte("flying") == (byte) 1; -+ } -+ } -+ -+ @Override -+ public void setFlying(boolean value) { -+ if (this.isOnline()) { -+ this.getPlayer().setFlying(value); -+ } else { -+ CompoundTag data = this.getData(); -+ if (data == null) return; -+ if (!data.contains("abilities")) return; -+ CompoundTag abilities = data.getCompound("abilities"); -+ abilities.putByte("mayfly", (byte) (value ? 1 : 0)); -+ data.put("abilities", abilities); -+ save(data); -+ } -+ } -+ -+ @Override -+ public void setFlySpeed(float value) throws IllegalArgumentException { -+ if (value < -1f || value > 1f) throw new IllegalArgumentException("FlySpeed needs to be between -1 and 1"); -+ if (this.isOnline()) { -+ this.getPlayer().setFlySpeed(value); -+ } else { -+ CompoundTag data = this.getData(); -+ if (data == null) return; -+ if (!data.contains("abilities")) return; -+ CompoundTag abilities = data.getCompound("abilities"); -+ abilities.putFloat("flySpeed", value); -+ data.put("abilities", abilities); -+ save(data); -+ } -+ } -+ -+ @Override -+ public float getFlySpeed() { -+ if (this.isOnline()) { -+ return this.getPlayer().getFlySpeed(); -+ } else { -+ CompoundTag data = this.getData(); -+ if (data == null) return 0; -+ if (!data.contains("abilities")) return 0; -+ CompoundTag abilities = data.getCompound("abilities"); -+ return abilities.getFloat("flySpeed"); -+ } -+ } -+ -+ @Override -+ public void setWalkSpeed(float value) throws IllegalArgumentException { -+ if (value < -1f || value > 1f) throw new IllegalArgumentException("WalkSpeed needs to be between -1 and 1"); -+ if (this.isOnline()) { -+ this.getPlayer().setWalkSpeed(value); -+ } else { -+ CompoundTag data = this.getData(); -+ if (data == null) return; -+ if (!data.contains("abilities")) return; -+ CompoundTag abilities = data.getCompound("abilities"); -+ abilities.putFloat("walkSpeed", value); -+ data.put("abilities", abilities); -+ save(data); -+ } -+ } -+ -+ @Override -+ public float getWalkSpeed() { -+ if (this.isOnline()) { -+ return this.getPlayer().getWalkSpeed(); -+ } else { -+ CompoundTag data = this.getData(); -+ if (data == null) return 0; -+ if (!data.contains("abilities")) return 0; -+ CompoundTag abilities = data.getCompound("abilities"); -+ return abilities.getFloat("walkSpeed"); -+ } -+ } -+ -+ @Override -+ public Location getLocation() { -+ if (this.isOnline()) { -+ return this.getPlayer().getLocation(); -+ } else { -+ CompoundTag data = this.getData(); -+ if (data == null) return null; -+ long worldUUIDMost = data.getLong("WorldUUIDMost"); -+ long worldUUIDLeast = data.getLong("WorldUUIDLeast"); -+ net.minecraft.nbt.ListTag position = data.getList("Pos", org.bukkit.craftbukkit.util.CraftMagicNumbers.NBT.TAG_DOUBLE); -+ net.minecraft.nbt.ListTag rotation = data.getList("Rotation", org.bukkit.craftbukkit.util.CraftMagicNumbers.NBT.TAG_FLOAT); -+ UUID worldUuid = new UUID(worldUUIDMost, worldUUIDLeast); -+ org.bukkit.World world = server.getWorld(worldUuid); -+ double x = position.getDouble(0); -+ double y = position.getDouble(1); -+ double z = position.getDouble(2); -+ float yaw = rotation.getFloat(0); -+ float pitch = rotation.getFloat(1); -+ return new Location(world, x, y, z, yaw, pitch); -+ } -+ } -+ -+ @Override -+ public boolean teleportOffline(Location destination) { -+ if (this.isOnline()) { -+ return this.getPlayer().teleport(destination); -+ } else { -+ return setLocation(destination); -+ } -+ } -+ -+ @Override -+ public boolean teleportOffline(Location destination, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause){ -+ if (this.isOnline()) { -+ return this.getPlayer().teleport(destination, cause); -+ } else { -+ return setLocation(destination); -+ } -+ } -+ -+ @Override -+ public java.util.concurrent.CompletableFuture teleportOfflineAsync(Location destination) { -+ if (this.isOnline()) { -+ return this.getPlayer().teleportAsync(destination); -+ } else { -+ return java.util.concurrent.CompletableFuture.completedFuture(setLocation(destination)); -+ } -+ } -+ -+ @Override -+ public java.util.concurrent.CompletableFuture teleportOfflineAsync(Location destination, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) { -+ if (this.isOnline()) { -+ return this.getPlayer().teleportAsync(destination, cause); -+ } else { -+ return java.util.concurrent.CompletableFuture.completedFuture(setLocation(destination)); -+ } -+ } -+ -+ private boolean setLocation(Location location) { -+ CompoundTag data = this.getData(); -+ if (data == null) return false; -+ data.putLong("WorldUUIDMost", location.getWorld().getUID().getMostSignificantBits()); -+ data.putLong("WorldUUIDLeast", location.getWorld().getUID().getLeastSignificantBits()); -+ net.minecraft.nbt.ListTag position = new net.minecraft.nbt.ListTag(); -+ position.add(net.minecraft.nbt.DoubleTag.valueOf(location.getX())); -+ position.add(net.minecraft.nbt.DoubleTag.valueOf(location.getY())); -+ position.add(net.minecraft.nbt.DoubleTag.valueOf(location.getZ())); -+ data.put("Pos", position); -+ net.minecraft.nbt.ListTag rotation = new net.minecraft.nbt.ListTag(); -+ rotation.add(net.minecraft.nbt.FloatTag.valueOf(location.getYaw())); -+ rotation.add(net.minecraft.nbt.FloatTag.valueOf(location.getPitch())); -+ data.put("Rotation", rotation); -+ save(data); -+ return true; -+ } -+ -+ /** -+ * Safely replaces player's .dat file with provided CompoundTag -+ * @param compoundTag -+ */ -+ private void save(CompoundTag compoundTag) { -+ File playerDir = server.console.playerDataStorage.getPlayerDir(); -+ try { -+ File tempFile = File.createTempFile(this.getUniqueId()+"-", ".dat", playerDir); -+ net.minecraft.nbt.NbtIo.writeCompressed(compoundTag, tempFile); -+ File playerDataFile = new File(playerDir, this.getUniqueId()+".dat"); -+ File playerDataFileOld = new File(playerDir, this.getUniqueId()+".dat_old"); -+ net.minecraft.Util.safeReplaceFile(playerDataFile, tempFile, playerDataFileOld); -+ } catch (java.io.IOException e) { -+ e.printStackTrace(); -+ } -+ } -+ // Purpur end - OfflinePlayer API - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index d9934a9cfccd324dc35c4225e4a859f70a5c083d..aa276ff3319b7eda1cf84de40924236175febb66 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -330,6 +330,20 @@ public final class CraftServer implements Server { - this.dataPackManager = new CraftDataPackManager(this.getServer().getPackRepository()); - - Bukkit.setServer(this); -+ // Purpur start -+ org.purpurmc.purpur.language.Language.setLanguage(new org.purpurmc.purpur.language.Language() { -+ private net.minecraft.locale.Language language = net.minecraft.locale.Language.getInstance(); -+ @Override -+ public boolean has(@org.jetbrains.annotations.NotNull String key) { -+ return language.has(key); -+ } -+ -+ @Override -+ public @org.jetbrains.annotations.NotNull String getOrDefault(@org.jetbrains.annotations.NotNull String key) { -+ return language.getOrDefault(key); -+ } -+ }); -+ // Purpur end - - // Register all the Enchantments and PotionTypes now so we can stop new registration immediately after - Enchantments.SHARPNESS.getClass(); -@@ -985,6 +999,7 @@ public final class CraftServer implements Server { - - org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings")); // Spigot - this.console.paperConfigurations.reloadConfigs(this.console); -+ org.purpurmc.purpur.PurpurConfig.init((File) console.options.valueOf("purpur-settings")); // Purpur - this.console.galeConfigurations.reloadConfigs(this.console); // Gale - Gale configuration - for (ServerLevel world : this.console.getAllLevelsArray()) { // Gale - base thread pool - optimize server levels - // world.serverLevelData.setDifficulty(config.difficulty); // Paper - per level difficulty -@@ -1001,6 +1016,7 @@ public final class CraftServer implements Server { - } - } - world.spigotConfig.init(); // Spigot -+ world.purpurConfig.init(); // Purpur - } - - Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper -@@ -1016,6 +1032,7 @@ public final class CraftServer implements Server { - this.reloadData(); - org.spigotmc.SpigotConfig.registerCommands(); // Spigot - io.papermc.paper.command.PaperCommands.registerCommands(this.console); // Paper -+ org.purpurmc.purpur.PurpurConfig.registerCommands(); // Purpur - this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*"); - this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions"); - -@@ -1457,6 +1474,55 @@ public final class CraftServer implements Server { - return true; - } - -+ // Purpur Start -+ @Override -+ public void addFuel(org.bukkit.Material material, int burnTime) { -+ Preconditions.checkArgument(burnTime > 0, "BurnTime must be greater than 0"); -+ net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity.addFuel(net.minecraft.world.item.ItemStack.fromBukkitCopy(new ItemStack(material)), burnTime); -+ } -+ -+ @Override -+ public void removeFuel(org.bukkit.Material material) { -+ net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity.removeFuel(net.minecraft.world.item.ItemStack.fromBukkitCopy(new ItemStack(material))); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration) { -+ sendBlockHighlight(location, duration, "", 0x6400FF00); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, int argb) { -+ sendBlockHighlight(location, duration, "", argb); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, String text) { -+ sendBlockHighlight(location, duration, text, 0x6400FF00); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, String text, int argb) { -+ this.worlds.forEach((name, world) -> world.sendBlockHighlight(location, duration, text, argb)); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, org.bukkit.Color color, int transparency) { -+ sendBlockHighlight(location, duration, "", color, transparency); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, String text, org.bukkit.Color color, int transparency) { -+ if (transparency < 0 || transparency > 255) throw new IllegalArgumentException("transparency is outside of 0-255 range"); -+ sendBlockHighlight(location, duration, text, transparency << 24 | color.asRGB()); -+ } -+ -+ @Override -+ public void clearBlockHighlights() { -+ this.worlds.forEach((name, world) -> clearBlockHighlights()); -+ } -+ // Purpur End -+ - @Override - public List getRecipesFor(ItemStack result) { - Validate.notNull(result, "Result cannot be null"); -@@ -2728,6 +2794,7 @@ public final class CraftServer implements Server { - @Override - public double[] getTPS() { - return new double[] { -+ net.minecraft.server.MinecraftServer.getServer().tps5s.getAverage(), // Purpur - net.minecraft.server.MinecraftServer.getServer().tps1.getAverage(), - net.minecraft.server.MinecraftServer.getServer().tps5.getAverage(), - net.minecraft.server.MinecraftServer.getServer().tps15.getAverage() -@@ -2820,6 +2887,18 @@ public final class CraftServer implements Server { - } - // Gale end - Gale configuration - API - -+ // Purpur start -+ @Override -+ public YamlConfiguration getPurpurConfig() { -+ return org.purpurmc.purpur.PurpurConfig.config; -+ } -+ -+ @Override -+ public java.util.Properties getServerProperties() { -+ return getProperties().properties; -+ } -+ // Purpur end -+ - @Override - public void restart() { - org.spigotmc.RestartCommand.restart(); -@@ -3051,4 +3130,15 @@ public final class CraftServer implements Server { - } - // Gale end - YAPFA - last tick time - API - -+ // Purpur start -+ @Override -+ public String getServerName() { -+ return this.getProperties().serverName; -+ } -+ -+ @Override -+ public boolean isLagging() { -+ return getServer().lagging; -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 1cdcf0b7ec11c7bda92aeed2f3470e9419e1214e..d888994c07299c890c2fc6ee2dce0a1632bf1ea4 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -2282,6 +2282,48 @@ public class CraftWorld extends CraftRegionAccessor implements World { - return (this.getHandle().dragonFight() == null) ? null : new CraftDragonBattle(this.getHandle().dragonFight()); - } - -+ // Purpur start -+ public float getLocalDifficultyAt(Location location) { -+ return getHandle().getCurrentDifficultyAt(io.papermc.paper.util.MCUtil.toBlockPosition(location)).getEffectiveDifficulty(); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration) { -+ sendBlockHighlight(location, duration, "", 0x6400FF00); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, int argb) { -+ sendBlockHighlight(location, duration, "", argb); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, String text) { -+ sendBlockHighlight(location, duration, text, 0x6400FF00); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, String text, int argb) { -+ net.minecraft.network.protocol.game.DebugPackets.sendGameTestAddMarker(getHandle(), io.papermc.paper.util.MCUtil.toBlockPosition(location), text, argb, duration); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, org.bukkit.Color color, int transparency) { -+ sendBlockHighlight(location, duration, "", color, transparency); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, String text, org.bukkit.Color color, int transparency) { -+ if (transparency < 0 || transparency > 255) throw new IllegalArgumentException("transparency is outside of 0-255 range"); -+ sendBlockHighlight(location, duration, text, transparency << 24 | color.asRGB()); -+ } -+ -+ @Override -+ public void clearBlockHighlights() { -+ net.minecraft.network.protocol.game.DebugPackets.sendGameTestClearPacket(getHandle()); -+ } -+ // Purpur end -+ - @Override - public PersistentDataContainer getPersistentDataContainer() { - return this.persistentDataContainer; -diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index de002d388bcf8e39407d1f84ae1286e8aede3d1d..a1f0af0a5f171ae80b7dc4dcf1c8669f0763da11 100644 ---- a/src/main/java/org/bukkit/craftbukkit/Main.java -+++ b/src/main/java/org/bukkit/craftbukkit/Main.java -@@ -242,6 +242,20 @@ public class Main { - .describedAs("Jar file"); - // Paper end - -+ // Purpur Start -+ acceptsAll(asList("purpur", "purpur-settings"), "File for purpur settings") -+ .withRequiredArg() -+ .ofType(File.class) -+ .defaultsTo(new File("purpur.yml")) -+ .describedAs("Yml file"); -+ -+ acceptsAll(asList("pufferfish", "pufferfish-settings"), "File for pufferfish settings") -+ .withRequiredArg() -+ .ofType(File.class) -+ .defaultsTo(new File("pufferfish.yml")) -+ .describedAs("Yml file"); -+ // Purpur end -+ - // Paper start - acceptsAll(asList("server-name"), "Name of the server") - .withRequiredArg() -@@ -353,7 +367,7 @@ public class Main { - System.setProperty(net.minecrell.terminalconsole.TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY, "false"); // Paper - } - -- if (Main.class.getPackage().getImplementationVendor() != null && System.getProperty("IReallyKnowWhatIAmDoingISwear") == null) { -+ if (false && Main.class.getPackage().getImplementationVendor() != null && System.getProperty("IReallyKnowWhatIAmDoingISwear") == null) { // Purpur - Date buildDate = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z").parse(Main.class.getPackage().getImplementationVendor()); // Paper - - Calendar deadline = Calendar.getInstance(); -diff --git a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java -index 3d0ce0803e1da8a2681a3cb41096ac942ece54a1..bcd075a771c7f43c6d1549aeec2ccb20ee168b57 100644 ---- a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java -+++ b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java -@@ -59,6 +59,7 @@ public class CraftEnchantment extends Enchantment { - return EnchantmentTarget.CROSSBOW; - case VANISHABLE: - return EnchantmentTarget.VANISHABLE; -+ case BOW_AND_CROSSBOW: return EnchantmentTarget.BOW_AND_CROSSBOW; // Purpur - default: - return null; - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java -index 75c7645fb5732c43d1da15181cf5c7ee4c3ecd6c..e7f5ea4d8d72672cf03483e720c6389425f28f6d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java -@@ -27,12 +27,12 @@ public class CraftEndermite extends CraftMonster implements Endermite { - - @Override - public boolean isPlayerSpawned() { -- return false; -+ return getHandle().isPlayerSpawned(); // Purpur - } - - @Override - public void setPlayerSpawned(boolean playerSpawned) { -- // Nop -+ getHandle().setPlayerSpawned(playerSpawned); // Purpur - } - // Paper start - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 7b4f51358d36a52e90d2cda25cd884772f62d65b..68d64aeed647d58b981a18b21c42ed8d74708717 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -210,6 +210,11 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - this.entity = entity; - } - -+ @Override -+ public boolean isInDaylight() { -+ return getHandle().isSunBurnTick(); -+ } -+ - public static CraftEntity getEntity(CraftServer server, Entity entity) { - /* - * Order is *EXTREMELY* important -- keep it right! =D -@@ -587,6 +592,10 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - // Paper end - - if ((!ignorePassengers && this.entity.isVehicle()) || this.entity.isRemoved()) { // Paper - Teleport passenger API -+ // Purpur start -+ if (!entity.isRemoved() && new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, cause).callEvent()) -+ return teleport(location, cause); -+ // Purpur end - return false; - } - -@@ -1443,4 +1452,37 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - return !this.getHandle().level.noCollision(this.getHandle(), aabb); - } - // Paper End - Collision API -+ -+ // Purpur start -+ @Override -+ public org.bukkit.entity.Player getRider() { -+ net.minecraft.world.entity.player.Player rider = getHandle().getRider(); -+ return rider != null ? (org.bukkit.entity.Player) rider.getBukkitEntity() : null; -+ } -+ -+ @Override -+ public boolean hasRider() { -+ return getHandle().getRider() != null; -+ } -+ -+ @Override -+ public boolean isRidable() { -+ return getHandle().isRidable(); -+ } -+ -+ @Override -+ public boolean isRidableInWater() { -+ return !getHandle().dismountsUnderwater(); -+ } -+ -+ @Override -+ public boolean isImmuneToFire() { -+ return getHandle().fireImmune(); -+ } -+ -+ @Override -+ public void setImmuneToFire(Boolean fireImmune) { -+ getHandle().immuneToFire = fireImmune; -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -index 1b008e5217c5bbf566a213abb92e1c7c43a3a7c2..468023414b4a9119a3418b8e8a5e38375bbd2407 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java -@@ -266,6 +266,7 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { - @Override - public void recalculatePermissions() { - this.perm.recalculatePermissions(); -+ getHandle().canPortalInstant = hasPermission("purpur.portal.instant"); // Purpur - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java -index 2966d4d466f44751b2f02afda2273a708c12b251..55f19324f92f98e497da49d3022e0edfc2351461 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftIronGolem.java -@@ -33,4 +33,17 @@ public class CraftIronGolem extends CraftGolem implements IronGolem { - public EntityType getType() { - return EntityType.IRON_GOLEM; - } -+ -+ // Purpur start -+ @Override -+ @org.jetbrains.annotations.Nullable -+ public java.util.UUID getSummoner() { -+ return getHandle().getSummoner(); -+ } -+ -+ @Override -+ public void setSummoner(@org.jetbrains.annotations.Nullable java.util.UUID summoner) { -+ getHandle().setSummoner(summoner); -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java -index a925b5c490e7129b27370aa57b5fad1cf05530c6..ea15690da167ec5e653da6f5afb55b33c45d1622 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java -@@ -160,4 +160,51 @@ public class CraftItem extends CraftEntity implements Item { - public EntityType getType() { - return EntityType.DROPPED_ITEM; - } -+ -+ // Purpur start -+ @Override -+ public void setImmuneToCactus(boolean immuneToCactus) { -+ item.immuneToCactus = immuneToCactus; -+ } -+ -+ @Override -+ public boolean isImmuneToCactus() { -+ return item.immuneToCactus; -+ } -+ -+ @Override -+ public void setImmuneToExplosion(boolean immuneToExplosion) { -+ item.immuneToExplosion = immuneToExplosion; -+ } -+ -+ @Override -+ public boolean isImmuneToExplosion() { -+ return item.immuneToExplosion; -+ } -+ -+ @Override -+ public void setImmuneToFire(@org.jetbrains.annotations.Nullable Boolean immuneToFire) { -+ item.immuneToFire = (immuneToFire != null && immuneToFire); -+ } -+ -+ @Override -+ public void setImmuneToFire(boolean immuneToFire) { -+ this.setImmuneToFire((Boolean) immuneToFire); -+ } -+ -+ @Override -+ public boolean isImmuneToFire() { -+ return item.immuneToFire; -+ } -+ -+ @Override -+ public void setImmuneToLightning(boolean immuneToLightning) { -+ item.immuneToLightning = immuneToLightning; -+ } -+ -+ @Override -+ public boolean isImmuneToLightning() { -+ return item.immuneToLightning; -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -index d43859f8aa7beed82dd3a146bb1086982cd0cda7..186e1549d67300abeee518cf8cea3c6d040d8008 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java -@@ -444,7 +444,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - net.minecraft.server.level.ServerPlayer entityPlayer = killer == null ? null : ((CraftPlayer) killer).getHandle(); - getHandle().lastHurtByPlayer = entityPlayer; - getHandle().lastHurtByMob = entityPlayer; -- getHandle().lastHurtByPlayerTime = entityPlayer == null ? 0 : 100; // 100 value taken from EntityLiving#damageEntity -+ getHandle().lastHurtByPlayerTime = entityPlayer == null ? 0 : getHandle().level.purpurConfig.mobLastHurtByPlayerTime; // 100 value taken from EntityLiving#damageEntity // Purpur - } - // Paper end - -@@ -456,7 +456,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - @Override - public boolean addPotionEffect(PotionEffect effect, boolean force) { - org.spigotmc.AsyncCatcher.catchOp("effect add"); // Paper -- this.getHandle().addEffect(new MobEffectInstance(MobEffect.byId(effect.getType().getId()), effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles(), effect.hasIcon()), EntityPotionEffectEvent.Cause.PLUGIN); // Paper - Don't ignore icon -+ this.getHandle().addEffect(new MobEffectInstance(MobEffect.byId(effect.getType().getId()), effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles(), effect.hasIcon(), effect.getKey()), EntityPotionEffectEvent.Cause.PLUGIN); // Purpur - add key // Paper - Don't ignore icon - return true; - } - -@@ -477,7 +477,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - @Override - public PotionEffect getPotionEffect(PotionEffectType type) { - MobEffectInstance handle = this.getHandle().getEffect(MobEffect.byId(type.getId())); -- return (handle == null) ? null : new PotionEffect(PotionEffectType.getById(MobEffect.getId(handle.getEffect())), handle.getDuration(), handle.getAmplifier(), handle.isAmbient(), handle.isVisible()); -+ return (handle == null) ? null : new PotionEffect(PotionEffectType.getById(MobEffect.getId(handle.getEffect())), handle.getDuration(), handle.getAmplifier(), handle.isAmbient(), handle.isVisible(), handle.getKey()); // Purpur - add key - } - - @Override -@@ -489,7 +489,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - public Collection getActivePotionEffects() { - List effects = new ArrayList(); - for (MobEffectInstance handle : this.getHandle().activeEffects.values()) { -- effects.add(new PotionEffect(PotionEffectType.getById(MobEffect.getId(handle.getEffect())), handle.getDuration(), handle.getAmplifier(), handle.isAmbient(), handle.isVisible())); -+ effects.add(new PotionEffect(PotionEffectType.getById(MobEffect.getId(handle.getEffect())), handle.getDuration(), handle.getAmplifier(), handle.isAmbient(), handle.isVisible(), handle.getKey())); // Purpur - add key - } - return effects; - } -@@ -1071,4 +1071,32 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity { - getHandle().knockback(strength, directionX, directionZ); - }; - // Paper end -+ -+ // Purpur start -+ @Override -+ public float getSafeFallDistance() { -+ return getHandle().safeFallDistance; -+ } -+ -+ @Override -+ public void setSafeFallDistance(float safeFallDistance) { -+ getHandle().safeFallDistance = safeFallDistance; -+ } -+ -+ @Override -+ public void broadcastItemBreak(org.bukkit.inventory.EquipmentSlot slot) { -+ if (slot == null) return; -+ getHandle().broadcastBreakEvent(org.bukkit.craftbukkit.CraftEquipmentSlot.getNMS(slot)); -+ } -+ -+ @Override -+ public boolean shouldBurnInDay() { -+ return getHandle().shouldBurnInDay(); -+ } -+ -+ @Override -+ public void setShouldBurnInDay(boolean shouldBurnInDay) { -+ getHandle().setShouldBurnInDay(shouldBurnInDay); -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java -index 4d7a2c4c1001aefe9fcd4be8dbcb414f721bfff9..2c7716a9d65ebda209a144b82c2126b602aa9182 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java -@@ -96,4 +96,16 @@ public class CraftLlama extends CraftChestedHorse implements Llama, com.destroys - return this.getHandle().caravanTail == null ? null : (Llama) this.getHandle().caravanTail.getBukkitEntity(); - } - // Paper end -+ -+ // Purpur start -+ @Override -+ public boolean shouldJoinCaravan() { -+ return getHandle().shouldJoinCaravan; -+ } -+ -+ @Override -+ public void setShouldJoinCaravan(boolean shouldJoinCaravan) { -+ getHandle().shouldJoinCaravan = shouldJoinCaravan; -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index be64633c8bcee96f2ad5247525cac965b7b031b1..e363891e8ab872ed24c557e3f94110f36c6fb277 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -534,10 +534,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - - @Override - public void setPlayerListName(String name) { -+ // Purpur start -+ setPlayerListName(name, false); -+ } -+ public void setPlayerListName(String name, boolean useMM) { -+ // Purpur end - if (name == null) { - name = getName(); - } -- this.getHandle().listName = name.equals(getName()) ? null : CraftChatMessage.fromStringOrNull(name); -+ this.getHandle().listName = name.equals(getName()) ? null : useMM ? io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.minimessage.MiniMessage.miniMessage().deserialize(name)) : CraftChatMessage.fromStringOrNull(name); // Purpur - for (ServerPlayer player : (List) server.getHandle().players) { - if (player.getBukkitEntity().canSee(this)) { - player.connection.send(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, this.getHandle())); -@@ -1358,6 +1363,10 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - - if (entity.isVehicle() && !ignorePassengers) { // Paper - Teleport API -+ // Purpur start -+ if (new org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent(entity.getBukkitEntity(), org.purpurmc.purpur.event.entity.EntityTeleportHinderedEvent.Reason.IS_VEHICLE, cause).callEvent()) -+ return teleport(location, cause); -+ // Purpur end - return false; - } - -@@ -2405,6 +2414,28 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - return this.getHandle().getAbilities().walkingSpeed * 2f; - } - -+ // Purpur start - OfflinePlayer API -+ @Override -+ public boolean teleportOffline(@NotNull Location destination) { -+ return this.teleport(destination); -+ } -+ -+ @Override -+ public boolean teleportOffline(Location destination, PlayerTeleportEvent.TeleportCause cause) { -+ return this.teleport(destination, cause); -+ } -+ -+ @Override -+ public java.util.concurrent.CompletableFuture teleportOfflineAsync(@NotNull Location destination) { -+ return this.teleportAsync(destination); -+ } -+ -+ @Override -+ public java.util.concurrent.CompletableFuture teleportOfflineAsync(@NotNull Location destination, PlayerTeleportEvent.TeleportCause cause) { -+ return this.teleportAsync(destination, cause); -+ } -+ // Purpur end - OfflinePlayer API -+ - private void validateSpeed(float value) { - if (value < 0) { - if (value < -1f) { -@@ -3180,4 +3211,97 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - return this.spigot; - } - // Spigot end -+ -+ // Purpur start -+ @Override -+ public boolean usesPurpurClient() { -+ return getHandle().purpurClient; -+ } -+ -+ @Override -+ public boolean isAfk() { -+ return getHandle().isAfk(); -+ } -+ -+ @Override -+ public void setAfk(boolean setAfk) { -+ getHandle().setAfk(setAfk); -+ } -+ -+ @Override -+ public void resetIdleTimer() { -+ getHandle().resetLastActionTime(); -+ } -+ -+ @Override -+ public boolean isSpawnInvulnerable() { -+ return getHandle().isSpawnInvulnerable(); -+ } -+ -+ @Override -+ public int getSpawnInvulnerableTicks() { -+ return getHandle().spawnInvulnerableTime; -+ } -+ -+ @Override -+ public void setSpawnInvulnerableTicks(int spawnInvulnerableTime) { -+ getHandle().spawnInvulnerableTime = spawnInvulnerableTime; -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration) { -+ sendBlockHighlight(location, duration, "", 0x6400FF00); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, int argb) { -+ sendBlockHighlight(location, duration, "", argb); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, String text) { -+ sendBlockHighlight(location, duration, text, 0x6400FF00); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, String text, int argb) { -+ if (this.getHandle().connection == null) return; -+ FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); -+ buf.writeBlockPos(io.papermc.paper.util.MCUtil.toBlockPosition(location)); -+ buf.writeInt(argb); -+ buf.writeUtf(text); -+ buf.writeInt(duration); -+ this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundCustomPayloadPacket(ClientboundCustomPayloadPacket.DEBUG_GAME_TEST_ADD_MARKER, buf)); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, org.bukkit.Color color, int transparency) { -+ sendBlockHighlight(location, duration, "", color, transparency); -+ } -+ -+ @Override -+ public void sendBlockHighlight(Location location, int duration, String text, org.bukkit.Color color, int transparency) { -+ if (transparency < 0 || transparency > 255) throw new IllegalArgumentException("transparency is outside of 0-255 range"); -+ sendBlockHighlight(location, duration, text, transparency << 24 | color.asRGB()); -+ } -+ -+ @Override -+ public void clearBlockHighlights() { -+ if (this.getHandle().connection == null) return; -+ this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundCustomPayloadPacket(ClientboundCustomPayloadPacket.DEBUG_GAME_TEST_CLEAR, new FriendlyByteBuf(io.netty.buffer.Unpooled.buffer()))); -+ } -+ -+ @Override -+ public void sendDeathScreen(net.kyori.adventure.text.Component message) { -+ sendDeathScreen(message, null); -+ } -+ -+ @Override -+ public void sendDeathScreen(net.kyori.adventure.text.Component message, org.bukkit.entity.Entity killer) { -+ if (this.getHandle().connection == null) return; -+ net.minecraft.network.protocol.game.ClientboundPlayerCombatKillPacket packet = new net.minecraft.network.protocol.game.ClientboundPlayerCombatKillPacket(getEntityId(), killer == null ? -1 : killer.getEntityId(), null); -+ packet.adventure$message = message; -+ this.getHandle().connection.send(packet); -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java -index 42b7058d93fab8cbee49dba130734e1df9910096..5c6f55527cc0016f09b443528463b3906c433f8b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSnowman.java -@@ -34,4 +34,17 @@ public class CraftSnowman extends CraftGolem implements Snowman, com.destroystok - public EntityType getType() { - return EntityType.SNOWMAN; - } -+ -+ // Purpur start -+ @Override -+ @org.jetbrains.annotations.Nullable -+ public java.util.UUID getSummoner() { -+ return getHandle().getSummoner(); -+ } -+ -+ @Override -+ public void setSummoner(@org.jetbrains.annotations.Nullable java.util.UUID summoner) { -+ getHandle().setSummoner(summoner); -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java -index 4e880409b06086568627f3e930159f1abb979984..48fb7302b54f8e7f5c424210b550c03d4d071ea9 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java -@@ -223,4 +223,11 @@ public class CraftVillager extends CraftAbstractVillager implements Villager { - getHandle().getGossips().gossips.clear(); - } - // Paper end -+ -+ // Purpur start -+ @Override -+ public boolean isLobotomized() { -+ return getHandle().isLobotomized(); -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java -index 1a21d30620f13a48976da5ead7edab201ea68b21..a50a04dc2009515032058562627eba8e4406c5bb 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java -@@ -105,4 +105,17 @@ public class CraftWither extends CraftMonster implements Wither, com.destroystok - this.getHandle().makeInvulnerable(); - } - // Paper end -+ -+ // Purpur start -+ @Override -+ @org.jetbrains.annotations.Nullable -+ public java.util.UUID getSummoner() { -+ return getHandle().getSummoner(); -+ } -+ -+ @Override -+ public void setSummoner(@org.jetbrains.annotations.Nullable java.util.UUID summoner) { -+ getHandle().setSummoner(summoner); -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java -index e43fd3e59fd8c74828ae65965fade27f56beef65..b2f133c8baabba1cffa6e92ea0f854532f4c181b 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java -@@ -63,4 +63,16 @@ public class CraftWolf extends CraftTameableAnimal implements Wolf { - public void setInterested(boolean flag) { - this.getHandle().setIsInterested(flag); - } -+ -+ // Purpur start -+ @Override -+ public boolean isRabid() { -+ return getHandle().isRabid(); -+ } -+ -+ @Override -+ public void setRabid(boolean isRabid) { -+ getHandle().setRabid(isRabid); -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index a153c134cf26e86d49ef419eca35994539af0db3..e9599e0f3d2122c3843ebde81743bc8d558bfd30 100644 ---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -@@ -563,6 +563,15 @@ public class CraftEventFactory { - // Paper end - craftServer.getPluginManager().callEvent(event); - -+ // Purpur start -+ if (who != null) { -+ switch (action) { -+ case LEFT_CLICK_BLOCK, LEFT_CLICK_AIR -> who.processClick(InteractionHand.MAIN_HAND); -+ case RIGHT_CLICK_BLOCK, RIGHT_CLICK_AIR -> who.processClick(InteractionHand.OFF_HAND); -+ } -+ } -+ // Purpur end -+ - return event; - } - -@@ -1000,6 +1009,7 @@ public class CraftEventFactory { - damageCause = DamageCause.ENTITY_EXPLOSION; - } - event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), entity.getBukkitEntity(), damageCause, modifiers, modifierFunctions, source.isCritical()); // Paper - add critical damage API -+ damager.processClick(InteractionHand.MAIN_HAND); // Purpur - } - event.setCancelled(cancelled); - -@@ -1114,6 +1124,7 @@ public class CraftEventFactory { - } else { - entity.lastDamageCancelled = true; // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Keep track if the event was canceled - } -+ damager.getHandle().processClick(InteractionHand.MAIN_HAND); // Purpur - return event; - } - -@@ -1173,6 +1184,7 @@ public class CraftEventFactory { - EntityDamageEvent event; - if (damager != null) { - event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, modifiers, modifierFunctions, critical); // Paper - add critical damage API -+ damager.processClick(InteractionHand.MAIN_HAND); // Purpur - } else { - event = new EntityDamageEvent(damagee.getBukkitEntity(), cause, modifiers, modifierFunctions); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java -index 04088918e172eecb8d53b0e6de9be0071ccf33b5..eddd6073e0feb7b046db1d169020ca067fdf689c 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftContainer.java -@@ -182,8 +182,19 @@ public class CraftContainer extends AbstractContainerMenu { - case PLAYER: - case CHEST: - case ENDER_CHEST: -+ // Purpur start -+ this.delegate = new ChestMenu(org.purpurmc.purpur.PurpurConfig.enderChestSixRows ? MenuType.GENERIC_9x6 : MenuType.GENERIC_9x3, windowId, bottom, top, top.getContainerSize() / 9); -+ break; - case BARREL: -- this.delegate = new ChestMenu(MenuType.GENERIC_9x3, windowId, bottom, top, top.getContainerSize() / 9); -+ this.delegate = new ChestMenu(switch (org.purpurmc.purpur.PurpurConfig.barrelRows) { -+ case 6 -> MenuType.GENERIC_9x6; -+ case 5 -> MenuType.GENERIC_9x5; -+ case 4 -> MenuType.GENERIC_9x4; -+ case 2 -> MenuType.GENERIC_9x2; -+ case 1 -> MenuType.GENERIC_9x1; -+ default -> MenuType.GENERIC_9x3; -+ }, windowId, bottom, top, top.getContainerSize() / 9); -+ // Purpur end - break; - case DISPENSER: - case DROPPER: -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java -index 092f6843e3b43d4c615d2eee344f5966e96ae850..cb0c851ab5fcf676da2397040835a94d4bdb4be1 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java -@@ -83,7 +83,7 @@ public class CraftInventory implements Inventory { - - @Override - public void setContents(ItemStack[] items) { -- if (this.getSize() < items.length) { -+ if (false && this.getSize() < items.length) { // Purpur - throw new IllegalArgumentException("Invalid inventory size; expected " + this.getSize() + " or less"); - } - -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java -index 88d3ca586ff6905f18a8ab9f0e229f440ed44088..27dd4eb4781a3c75772860c11db886e1038cecd2 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryAnvil.java -@@ -9,7 +9,7 @@ import org.bukkit.inventory.AnvilInventory; - public class CraftInventoryAnvil extends CraftResultInventory implements AnvilInventory { - - private final Location location; -- private final AnvilMenu container; -+ public final AnvilMenu container; // Purpur - private -> public - - public CraftInventoryAnvil(Location location, Container inventory, Container resultInventory, AnvilMenu container) { - super(inventory, resultInventory); -@@ -57,4 +57,26 @@ public class CraftInventoryAnvil extends CraftResultInventory implements AnvilIn - Preconditions.checkArgument(levels >= 0, "Maximum repair cost must be positive (or 0)"); - container.maximumRepairCost = levels; - } -+ -+ // Purpur start -+ @Override -+ public boolean canBypassCost() { -+ return container.bypassCost; -+ } -+ -+ @Override -+ public void setBypassCost(boolean bypassCost) { -+ container.bypassCost = bypassCost; -+ } -+ -+ @Override -+ public boolean canDoUnsafeEnchants() { -+ return container.canDoUnsafeEnchants; -+ } -+ -+ @Override -+ public void setDoUnsafeEnchants(boolean canDoUnsafeEnchants) { -+ container.canDoUnsafeEnchants = canDoUnsafeEnchants; -+ } -+ // Purpur end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java -index 0d4348ce1c4ec9bb779eaebf8606ea578f17d2cb..486768894f130cd23905cc7a8be16ce705667bbb 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java -@@ -13,6 +13,7 @@ import net.minecraft.nbt.ListTag; - import org.apache.commons.lang.Validate; - import org.bukkit.Color; - import org.bukkit.Material; -+import org.bukkit.NamespacedKey; - import org.bukkit.configuration.serialization.DelegateDeserialization; - import org.bukkit.craftbukkit.inventory.CraftMetaItem.SerializableMeta; - import org.bukkit.craftbukkit.potion.CraftPotionUtil; -@@ -42,6 +43,7 @@ class CraftMetaPotion extends CraftMetaItem implements PotionMeta { - static final ItemMetaKey POTION_COLOR = new ItemMetaKey("CustomPotionColor", "custom-color"); - static final ItemMetaKey ID = new ItemMetaKey("Id", "potion-id"); - static final ItemMetaKey DEFAULT_POTION = new ItemMetaKey("Potion", "potion-type"); -+ static final ItemMetaKey KEY = new ItemMetaKey("Key", "namespacedkey"); // Purpur - add key - - // Having an initial "state" in ItemMeta seems bit dirty but the UNCRAFTABLE potion type - // is treated as the empty form of the meta because it represents an empty potion with no effect -@@ -92,7 +94,13 @@ class CraftMetaPotion extends CraftMetaItem implements PotionMeta { - boolean ambient = effect.getBoolean(AMBIENT.NBT); - boolean particles = effect.contains(SHOW_PARTICLES.NBT, CraftMagicNumbers.NBT.TAG_BYTE) ? effect.getBoolean(SHOW_PARTICLES.NBT) : true; - boolean icon = effect.contains(SHOW_ICON.NBT, CraftMagicNumbers.NBT.TAG_BYTE) ? effect.getBoolean(SHOW_ICON.NBT) : particles; -- this.customEffects.add(new PotionEffect(type, duration, amp, ambient, particles, icon)); -+ // Purpur start -+ NamespacedKey key = null; -+ if (tag.contains(KEY.NBT)) { -+ key = NamespacedKey.fromString(effect.getString(KEY.NBT)); -+ } -+ this.customEffects.add(new PotionEffect(type, duration, amp, ambient, particles, icon, key)); -+ // Purpur end - } - } - } -@@ -141,6 +149,11 @@ class CraftMetaPotion extends CraftMetaItem implements PotionMeta { - effectData.putBoolean(AMBIENT.NBT, effect.isAmbient()); - effectData.putBoolean(SHOW_PARTICLES.NBT, effect.hasParticles()); - effectData.putBoolean(SHOW_ICON.NBT, effect.hasIcon()); -+ // Purpur start -+ if (effect.hasKey()) { -+ effectData.putString(KEY.NBT, effect.getKey().toString()); -+ } -+ // Purpur end - effectList.add(effectData); - } - } -@@ -202,7 +215,7 @@ class CraftMetaPotion extends CraftMetaItem implements PotionMeta { - if (index != -1) { - if (overwrite) { - PotionEffect old = this.customEffects.get(index); -- if (old.getAmplifier() == effect.getAmplifier() && old.getDuration() == effect.getDuration() && old.isAmbient() == effect.isAmbient()) { -+ if (old.getAmplifier() == effect.getAmplifier() && old.getDuration() == effect.getDuration() && old.isAmbient() == effect.isAmbient() && old.getKey() == effect.getKey()) { // Purpur - add key - return false; - } - this.customEffects.set(index, effect); -diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java -index 8563fcf77eef0e1e354857b9a4256d78a523a8d0..b94c964790433cb7bd88ad16c5d82d1a8e39312d 100644 ---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java -+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java -@@ -29,6 +29,7 @@ public interface CraftRecipe extends Recipe { - } else if (bukkit instanceof RecipeChoice.ExactChoice) { - stack = new Ingredient(((RecipeChoice.ExactChoice) bukkit).getChoices().stream().map((mat) -> new net.minecraft.world.item.crafting.Ingredient.ItemValue(CraftItemStack.asNMSCopy(mat)))); - stack.exact = true; -+ stack.predicate = ((RecipeChoice.ExactChoice) bukkit).getPredicate(); // Purpur - } else { - throw new IllegalArgumentException("Unknown recipe stack instance " + bukkit); - } -diff --git a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java -index acb69821a99aa69bce6d127e10976089c85be223..c5abd73981c5f4b41605eba0d44e6573dfd2a77a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java -+++ b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java -@@ -101,7 +101,7 @@ public class CraftPotionUtil { - - public static MobEffectInstance fromBukkit(PotionEffect effect) { - MobEffect type = MobEffect.byId(effect.getType().getId()); -- return new MobEffectInstance(type, effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles()); -+ return new MobEffectInstance(type, effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles(), effect.getKey()); // Purpur - add key - } - - public static PotionEffect toBukkit(MobEffectInstance effect) { -@@ -110,7 +110,7 @@ public class CraftPotionUtil { - int duration = effect.getDuration(); - boolean ambient = effect.isAmbient(); - boolean particles = effect.isVisible(); -- return new PotionEffect(type, duration, amp, ambient, particles); -+ return new PotionEffect(type, duration, amp, ambient, particles, effect.getKey()); // Purpur - add key - } - - public static boolean equals(MobEffect mobEffect, PotionEffectType type) { -diff --git a/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java b/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java -index ec771c480156db393c326fa2fbdc2d432fb76f53..71940bf3a4162d12a422a5b3100ad8def85f95ac 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/permissions/CommandPermissions.java -@@ -23,7 +23,15 @@ public final class CommandPermissions { - DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "kick", "Allows the user to kick players", PermissionDefault.OP, commands); - DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "stop", "Allows the user to stop the server", PermissionDefault.OP, commands); - DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "list", "Allows the user to list all online players", PermissionDefault.OP, commands); -- DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "gamemode", "Allows the user to change the gamemode of another player", PermissionDefault.OP, commands); -+ // Purpur start -+ Permission gamemodeVanilla = DefaultPermissions.registerPermission(PREFIX + "gamemode", "Allows the user to change the gamemode", PermissionDefault.OP, commands); -+ for (net.minecraft.world.level.GameType gametype : net.minecraft.world.level.GameType.values()) { -+ Permission gamemodeSelf = DefaultPermissions.registerPermission(PREFIX + "gamemode." + gametype.getName(), "Allows the user to set " + gametype.getName() + " gamemode for self", PermissionDefault.OP); -+ Permission gamemodeOther = DefaultPermissions.registerPermission(PREFIX + "gamemode." + gametype.getName() + ".other", "Allows the user to set " + gametype.getName() + " gamemode for other players", PermissionDefault.OP); -+ gamemodeSelf.addParent(gamemodeOther, true); -+ gamemodeVanilla.addParent(gamemodeSelf, true); -+ } -+ // Purpur end - DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "xp", "Allows the user to give themselves or others arbitrary values of experience", PermissionDefault.OP, commands); - DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "defaultgamemode", "Allows the user to change the default gamemode of the server", PermissionDefault.OP, commands); - DefaultPermissions.registerPermission(CommandPermissions.PREFIX + "seed", "Allows the user to view the seed of the world", PermissionDefault.OP, commands); -diff --git a/src/main/java/org/purpurmc/purpur/PurpurConfig.java b/src/main/java/org/purpurmc/purpur/PurpurConfig.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1c4bdc4410122684a499dae45c2d9595233acd39 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/PurpurConfig.java -@@ -0,0 +1,636 @@ -+package org.purpurmc.purpur; -+ -+import com.google.common.base.Throwables; -+import com.google.common.collect.ImmutableMap; -+import com.mojang.datafixers.util.Pair; -+import net.kyori.adventure.bossbar.BossBar; -+import net.kyori.adventure.text.minimessage.MiniMessage; -+import net.minecraft.core.registries.BuiltInRegistries; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.world.effect.MobEffect; -+import net.minecraft.world.effect.MobEffectInstance; -+import net.minecraft.world.entity.EntityDimensions; -+import net.minecraft.world.entity.EntityType; -+import net.minecraft.world.food.FoodProperties; -+import net.minecraft.world.food.Foods; -+import net.minecraft.world.item.enchantment.Enchantment; -+import net.minecraft.world.level.block.Block; -+import net.minecraft.world.level.block.Blocks; -+import org.bukkit.Bukkit; -+import org.bukkit.command.Command; -+import org.bukkit.configuration.ConfigurationSection; -+import org.bukkit.configuration.InvalidConfigurationException; -+import org.bukkit.configuration.file.YamlConfiguration; -+import org.purpurmc.purpur.command.PurpurCommand; -+import org.purpurmc.purpur.task.TPSBarTask; -+ -+import java.io.File; -+import java.io.IOException; -+import java.lang.reflect.InvocationTargetException; -+import java.lang.reflect.Method; -+import java.lang.reflect.Modifier; -+import java.util.ArrayList; -+import java.util.Collections; -+import java.util.HashMap; -+import java.util.HashSet; -+import java.util.List; -+import java.util.Map; -+import java.util.Set; -+import java.util.logging.Level; -+ -+@SuppressWarnings("unused") -+public class PurpurConfig { -+ private static final String HEADER = "This is the main configuration file for Purpur.\n" -+ + "As you can see, there's tons to configure. Some options may impact gameplay, so use\n" -+ + "with caution, and make sure you know what each option does before configuring.\n" -+ + "\n" -+ + "If you need help with the configuration or have any questions related to Purpur,\n" -+ + "join us in our Discord guild.\n" -+ + "\n" -+ + "Website: https://purpurmc.org \n" -+ + "Docs: https://purpurmc.org/docs \n"; -+ private static File CONFIG_FILE; -+ public static YamlConfiguration config; -+ -+ private static Map commands; -+ -+ public static int version; -+ static boolean verbose; -+ -+ public static void init(File configFile) { -+ CONFIG_FILE = configFile; -+ config = new YamlConfiguration(); -+ try { -+ config.load(CONFIG_FILE); -+ } catch (IOException ignore) { -+ } catch (InvalidConfigurationException ex) { -+ Bukkit.getLogger().log(Level.SEVERE, "Could not load purpur.yml, please correct your syntax errors", ex); -+ throw Throwables.propagate(ex); -+ } -+ config.options().header(HEADER); -+ config.options().copyDefaults(true); -+ verbose = getBoolean("verbose", false); -+ -+ commands = new HashMap<>(); -+ commands.put("purpur", new PurpurCommand("purpur")); -+ -+ version = getInt("config-version", 32); -+ set("config-version", 32); -+ -+ readConfig(PurpurConfig.class, null); -+ -+ Blocks.rebuildCache(); -+ } -+ -+ protected static void log(String s) { -+ if (verbose) { -+ log(Level.INFO, s); -+ } -+ } -+ -+ protected static void log(Level level, String s) { -+ Bukkit.getLogger().log(level, s); -+ } -+ -+ public static void registerCommands() { -+ for (Map.Entry entry : commands.entrySet()) { -+ MinecraftServer.getServer().server.getCommandMap().register(entry.getKey(), "Purpur", entry.getValue()); -+ } -+ } -+ -+ static void readConfig(Class clazz, Object instance) { -+ for (Method method : clazz.getDeclaredMethods()) { -+ if (Modifier.isPrivate(method.getModifiers())) { -+ if (method.getParameterTypes().length == 0 && method.getReturnType() == Void.TYPE) { -+ try { -+ method.setAccessible(true); -+ method.invoke(instance); -+ } catch (InvocationTargetException ex) { -+ throw Throwables.propagate(ex.getCause()); -+ } catch (Exception ex) { -+ Bukkit.getLogger().log(Level.SEVERE, "Error invoking " + method, ex); -+ } -+ } -+ } -+ } -+ -+ try { -+ config.save(CONFIG_FILE); -+ } catch (IOException ex) { -+ Bukkit.getLogger().log(Level.SEVERE, "Could not save " + CONFIG_FILE, ex); -+ } -+ } -+ -+ private static void set(String path, Object val) { -+ config.addDefault(path, val); -+ config.set(path, val); -+ } -+ -+ private static String getString(String path, String def) { -+ config.addDefault(path, def); -+ return config.getString(path, config.getString(path)); -+ } -+ -+ private static boolean getBoolean(String path, boolean def) { -+ config.addDefault(path, def); -+ return config.getBoolean(path, config.getBoolean(path)); -+ } -+ -+ private static double getDouble(String path, double def) { -+ config.addDefault(path, def); -+ return config.getDouble(path, config.getDouble(path)); -+ } -+ -+ private static int getInt(String path, int def) { -+ config.addDefault(path, def); -+ return config.getInt(path, config.getInt(path)); -+ } -+ -+ private static List getList(String path, T def) { -+ config.addDefault(path, def); -+ return config.getList(path, config.getList(path)); -+ } -+ -+ static Map getMap(String path, Map def) { -+ if (def != null && config.getConfigurationSection(path) == null) { -+ config.addDefault(path, def); -+ return def; -+ } -+ return toMap(config.getConfigurationSection(path)); -+ } -+ -+ private static Map toMap(ConfigurationSection section) { -+ ImmutableMap.Builder builder = ImmutableMap.builder(); -+ if (section != null) { -+ for (String key : section.getKeys(false)) { -+ Object obj = section.get(key); -+ if (obj != null) { -+ builder.put(key, obj instanceof ConfigurationSection val ? toMap(val) : obj); -+ } -+ } -+ } -+ return builder.build(); -+ } -+ -+ public static String cannotRideMob = "You cannot mount that mob"; -+ public static String afkBroadcastAway = "%s is now AFK"; -+ public static String afkBroadcastBack = "%s is no longer AFK"; -+ public static String afkTabListPrefix = "[AFK] "; -+ public static String afkTabListSuffix = ""; -+ public static String creditsCommandOutput = "%s has been shown the end credits"; -+ public static String demoCommandOutput = "%s has been shown the demo screen"; -+ public static String pingCommandOutput = "%s's ping is %sms"; -+ public static String ramCommandOutput = "Ram Usage: / ()"; -+ public static String rambarCommandOutput = "Rambar toggled for "; -+ public static String tpsbarCommandOutput = "Tpsbar toggled for "; -+ public static String dontRunWithScissors = "Don't run with scissors!"; -+ public static String uptimeCommandOutput = "Server uptime is "; -+ public static String unverifiedUsername = "default"; -+ public static String sleepSkippingNight = "default"; -+ public static String sleepingPlayersPercent = "default"; -+ private static void messages() { -+ cannotRideMob = getString("settings.messages.cannot-ride-mob", cannotRideMob); -+ afkBroadcastAway = getString("settings.messages.afk-broadcast-away", afkBroadcastAway); -+ afkBroadcastBack = getString("settings.messages.afk-broadcast-back", afkBroadcastBack); -+ afkTabListPrefix = MiniMessage.miniMessage().serialize(MiniMessage.miniMessage().deserialize(getString("settings.messages.afk-tab-list-prefix", afkTabListPrefix))); -+ afkTabListSuffix = MiniMessage.miniMessage().serialize(MiniMessage.miniMessage().deserialize(getString("settings.messages.afk-tab-list-suffix", afkTabListSuffix))); -+ creditsCommandOutput = getString("settings.messages.credits-command-output", creditsCommandOutput); -+ demoCommandOutput = getString("settings.messages.demo-command-output", demoCommandOutput); -+ pingCommandOutput = getString("settings.messages.ping-command-output", pingCommandOutput); -+ ramCommandOutput = getString("settings.messages.ram-command-output", ramCommandOutput); -+ rambarCommandOutput = getString("settings.messages.rambar-command-output", rambarCommandOutput); -+ tpsbarCommandOutput = getString("settings.messages.tpsbar-command-output", tpsbarCommandOutput); -+ dontRunWithScissors = getString("settings.messages.dont-run-with-scissors", dontRunWithScissors); -+ uptimeCommandOutput = getString("settings.messages.uptime-command-output", uptimeCommandOutput); -+ unverifiedUsername = getString("settings.messages.unverified-username", unverifiedUsername); -+ sleepSkippingNight = getString("settings.messages.sleep-skipping-night", sleepSkippingNight); -+ sleepingPlayersPercent = getString("settings.messages.sleeping-players-percent", sleepingPlayersPercent); -+ } -+ -+ public static String deathMsgRunWithScissors = " slipped and fell on their shears"; -+ public static String deathMsgStonecutter = " has sawed themself in half"; -+ private static void deathMessages() { -+ deathMsgRunWithScissors = getString("settings.messages.death-message.run-with-scissors", deathMsgRunWithScissors); -+ deathMsgStonecutter = getString("settings.messages.death-message.stonecutter", deathMsgStonecutter); -+ } -+ -+ public static boolean advancementOnlyBroadcastToAffectedPlayer = false; -+ public static boolean deathMessageOnlyBroadcastToAffectedPlayer = false; -+ private static void broadcastSettings() { -+ if (version < 13) { -+ boolean oldValue = getBoolean("settings.advancement.only-broadcast-to-affected-player", false); -+ set("settings.broadcasts.advancement.only-broadcast-to-affected-player", oldValue); -+ set("settings.advancement.only-broadcast-to-affected-player", null); -+ } -+ advancementOnlyBroadcastToAffectedPlayer = getBoolean("settings.broadcasts.advancement.only-broadcast-to-affected-player", advancementOnlyBroadcastToAffectedPlayer); -+ deathMessageOnlyBroadcastToAffectedPlayer = getBoolean("settings.broadcasts.death.only-broadcast-to-affected-player", deathMessageOnlyBroadcastToAffectedPlayer); -+ } -+ -+ public static String serverModName = "Leaf"; // Leaf -+ private static void serverModName() { -+ serverModName = getString("settings.server-mod-name", serverModName); -+ } -+ -+ public static double laggingThreshold = 19.0D; -+ private static void tickLoopSettings() { -+ laggingThreshold = getDouble("settings.lagging-threshold", laggingThreshold); -+ } -+ -+ public static boolean useAlternateKeepAlive = false; -+ private static void useAlternateKeepAlive() { -+ useAlternateKeepAlive = getBoolean("settings.use-alternate-keepalive", useAlternateKeepAlive); -+ } -+ -+ public static boolean disableGiveCommandDrops = false; -+ private static void disableGiveCommandDrops() { -+ disableGiveCommandDrops = getBoolean("settings.disable-give-dropping", disableGiveCommandDrops); -+ } -+ -+ public static String commandRamBarTitle = "Ram: / ()"; -+ public static BossBar.Overlay commandRamBarProgressOverlay = BossBar.Overlay.NOTCHED_20; -+ public static BossBar.Color commandRamBarProgressColorGood = BossBar.Color.GREEN; -+ public static BossBar.Color commandRamBarProgressColorMedium = BossBar.Color.YELLOW; -+ public static BossBar.Color commandRamBarProgressColorLow = BossBar.Color.RED; -+ public static String commandRamBarTextColorGood = ""; -+ public static String commandRamBarTextColorMedium = ""; -+ public static String commandRamBarTextColorLow = ""; -+ public static int commandRamBarTickInterval = 20; -+ public static String commandTPSBarTitle = "TPS: MSPT: Ping: ms"; -+ public static BossBar.Overlay commandTPSBarProgressOverlay = BossBar.Overlay.NOTCHED_20; -+ public static TPSBarTask.FillMode commandTPSBarProgressFillMode = TPSBarTask.FillMode.MSPT; -+ public static BossBar.Color commandTPSBarProgressColorGood = BossBar.Color.GREEN; -+ public static BossBar.Color commandTPSBarProgressColorMedium = BossBar.Color.YELLOW; -+ public static BossBar.Color commandTPSBarProgressColorLow = BossBar.Color.RED; -+ public static String commandTPSBarTextColorGood = ""; -+ public static String commandTPSBarTextColorMedium = ""; -+ public static String commandTPSBarTextColorLow = ""; -+ public static int commandTPSBarTickInterval = 20; -+ public static String commandCompassBarTitle = "S \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 SW \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 W \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 NW \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 N \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 NE \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 E \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 SE \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 S \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 SW \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 W \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 NW \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 N \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 NE \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 E \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 SE \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 \u25C8 \u00B7 "; -+ public static BossBar.Overlay commandCompassBarProgressOverlay = BossBar.Overlay.PROGRESS; -+ public static BossBar.Color commandCompassBarProgressColor = BossBar.Color.BLUE; -+ public static float commandCompassBarProgressPercent = 1.0F; -+ public static int commandCompassBarTickInterval = 5; -+ public static boolean commandGamemodeRequiresPermission = false; -+ public static boolean hideHiddenPlayersFromEntitySelector = false; -+ public static String uptimeFormat = ""; -+ public static String uptimeDay = "%02d day, "; -+ public static String uptimeDays = "%02d days, "; -+ public static String uptimeHour = "%02d hour, "; -+ public static String uptimeHours = "%02d hours, "; -+ public static String uptimeMinute = "%02d minute, and "; -+ public static String uptimeMinutes = "%02d minutes, and "; -+ public static String uptimeSecond = "%02d second"; -+ public static String uptimeSeconds = "%02d seconds"; -+ private static void commandSettings() { -+ commandRamBarTitle = getString("settings.command.rambar.title", commandRamBarTitle); -+ commandRamBarProgressOverlay = BossBar.Overlay.valueOf(getString("settings.command.rambar.overlay", commandRamBarProgressOverlay.name())); -+ commandRamBarProgressColorGood = BossBar.Color.valueOf(getString("settings.command.rambar.progress-color.good", commandRamBarProgressColorGood.name())); -+ commandRamBarProgressColorMedium = BossBar.Color.valueOf(getString("settings.command.rambar.progress-color.medium", commandRamBarProgressColorMedium.name())); -+ commandRamBarProgressColorLow = BossBar.Color.valueOf(getString("settings.command.rambar.progress-color.low", commandRamBarProgressColorLow.name())); -+ commandRamBarTextColorGood = getString("settings.command.rambar.text-color.good", commandRamBarTextColorGood); -+ commandRamBarTextColorMedium = getString("settings.command.rambar.text-color.medium", commandRamBarTextColorMedium); -+ commandRamBarTextColorLow = getString("settings.command.rambar.text-color.low", commandRamBarTextColorLow); -+ commandRamBarTickInterval = getInt("settings.command.rambar.tick-interval", commandRamBarTickInterval); -+ -+ commandTPSBarTitle = getString("settings.command.tpsbar.title", commandTPSBarTitle); -+ commandTPSBarProgressOverlay = BossBar.Overlay.valueOf(getString("settings.command.tpsbar.overlay", commandTPSBarProgressOverlay.name())); -+ commandTPSBarProgressFillMode = TPSBarTask.FillMode.valueOf(getString("settings.command.tpsbar.fill-mode", commandTPSBarProgressFillMode.name())); -+ commandTPSBarProgressColorGood = BossBar.Color.valueOf(getString("settings.command.tpsbar.progress-color.good", commandTPSBarProgressColorGood.name())); -+ commandTPSBarProgressColorMedium = BossBar.Color.valueOf(getString("settings.command.tpsbar.progress-color.medium", commandTPSBarProgressColorMedium.name())); -+ commandTPSBarProgressColorLow = BossBar.Color.valueOf(getString("settings.command.tpsbar.progress-color.low", commandTPSBarProgressColorLow.name())); -+ commandTPSBarTextColorGood = getString("settings.command.tpsbar.text-color.good", commandTPSBarTextColorGood); -+ commandTPSBarTextColorMedium = getString("settings.command.tpsbar.text-color.medium", commandTPSBarTextColorMedium); -+ commandTPSBarTextColorLow = getString("settings.command.tpsbar.text-color.low", commandTPSBarTextColorLow); -+ commandTPSBarTickInterval = getInt("settings.command.tpsbar.tick-interval", commandTPSBarTickInterval); -+ -+ commandCompassBarTitle = getString("settings.command.compass.title", commandCompassBarTitle); -+ commandCompassBarProgressOverlay = BossBar.Overlay.valueOf(getString("settings.command.compass.overlay", commandCompassBarProgressOverlay.name())); -+ commandCompassBarProgressColor = BossBar.Color.valueOf(getString("settings.command.compass.progress-color", commandCompassBarProgressColor.name())); -+ commandCompassBarProgressPercent = (float) getDouble("settings.command.compass.percent", commandCompassBarProgressPercent); -+ commandCompassBarTickInterval = getInt("settings.command.compass.tick-interval", commandCompassBarTickInterval); -+ -+ commandGamemodeRequiresPermission = getBoolean("settings.command.gamemode.requires-specific-permission", commandGamemodeRequiresPermission); -+ hideHiddenPlayersFromEntitySelector = getBoolean("settings.command.hide-hidden-players-from-entity-selector", hideHiddenPlayersFromEntitySelector); -+ uptimeFormat = getString("settings.command.uptime.format", uptimeFormat); -+ uptimeDay = getString("settings.command.uptime.day", uptimeDay); -+ uptimeDays = getString("settings.command.uptime.days", uptimeDays); -+ uptimeHour = getString("settings.command.uptime.hour", uptimeHour); -+ uptimeHours = getString("settings.command.uptime.hours", uptimeHours); -+ uptimeMinute = getString("settings.command.uptime.minute", uptimeMinute); -+ uptimeMinutes = getString("settings.command.uptime.minutes", uptimeMinutes); -+ uptimeSecond = getString("settings.command.uptime.second", uptimeSecond); -+ uptimeSeconds = getString("settings.command.uptime.seconds", uptimeSeconds); -+ } -+ -+ public static int barrelRows = 3; -+ public static boolean enderChestSixRows = false; -+ public static boolean enderChestPermissionRows = false; -+ public static boolean cryingObsidianValidForPortalFrame = false; -+ public static int beeInsideBeeHive = 3; -+ public static boolean anvilCumulativeCost = true; -+ public static int lightningRodRange = 128; -+ public static Set grindstoneIgnoredEnchants = new HashSet<>(); -+ public static boolean grindstoneRemoveAttributes = false; -+ public static boolean grindstoneRemoveDisplay = false; -+ public static int caveVinesMaxGrowthAge = 25; -+ public static int kelpMaxGrowthAge = 25; -+ public static int twistingVinesMaxGrowthAge = 25; -+ public static int weepingVinesMaxGrowthAge = 25; -+ private static void blockSettings() { -+ if (version < 3) { -+ boolean oldValue = getBoolean("settings.barrel.packed-barrels", true); -+ set("settings.blocks.barrel.six-rows", oldValue); -+ set("settings.packed-barrels", null); -+ oldValue = getBoolean("settings.large-ender-chests", true); -+ set("settings.blocks.ender_chest.six-rows", oldValue); -+ set("settings.large-ender-chests", null); -+ } -+ if (version < 20) { -+ boolean oldValue = getBoolean("settings.blocks.barrel.six-rows", false); -+ set("settings.blocks.barrel.rows", oldValue ? 6 : 3); -+ set("settings.blocks.barrel.six-rows", null); -+ } -+ barrelRows = getInt("settings.blocks.barrel.rows", barrelRows); -+ if (barrelRows < 1 || barrelRows > 6) { -+ Bukkit.getLogger().severe("settings.blocks.barrel.rows must be 1-6, resetting to default"); -+ barrelRows = 3; -+ } -+ org.bukkit.event.inventory.InventoryType.BARREL.setDefaultSize(switch (barrelRows) { -+ case 6 -> 54; -+ case 5 -> 45; -+ case 4 -> 36; -+ case 2 -> 18; -+ case 1 -> 9; -+ default -> 27; -+ }); -+ enderChestSixRows = getBoolean("settings.blocks.ender_chest.six-rows", enderChestSixRows); -+ org.bukkit.event.inventory.InventoryType.ENDER_CHEST.setDefaultSize(enderChestSixRows ? 54 : 27); -+ enderChestPermissionRows = getBoolean("settings.blocks.ender_chest.use-permissions-for-rows", enderChestPermissionRows); -+ cryingObsidianValidForPortalFrame = getBoolean("settings.blocks.crying_obsidian.valid-for-portal-frame", cryingObsidianValidForPortalFrame); -+ beeInsideBeeHive = getInt("settings.blocks.beehive.max-bees-inside", beeInsideBeeHive); -+ anvilCumulativeCost = getBoolean("settings.blocks.anvil.cumulative-cost", anvilCumulativeCost); -+ lightningRodRange = getInt("settings.blocks.lightning_rod.range", lightningRodRange); -+ ArrayList defaultCurses = new ArrayList<>(){{ -+ add("minecraft:binding_curse"); -+ add("minecraft:vanishing_curse"); -+ }}; -+ if (version < 24 && !getBoolean("settings.blocks.grindstone.ignore-curses", true)) { -+ defaultCurses.clear(); -+ } -+ getList("settings.blocks.grindstone.ignored-enchants", defaultCurses).forEach(key -> { -+ Enchantment enchantment = BuiltInRegistries.ENCHANTMENT.get(new ResourceLocation(key.toString())); -+ grindstoneIgnoredEnchants.add(enchantment); -+ }); -+ grindstoneRemoveAttributes = getBoolean("settings.blocks.grindstone.remove-attributes", grindstoneRemoveAttributes); -+ grindstoneRemoveDisplay = getBoolean("settings.blocks.grindstone.remove-name-and-lore", grindstoneRemoveDisplay); -+ caveVinesMaxGrowthAge = getInt("settings.blocks.cave_vines.max-growth-age", caveVinesMaxGrowthAge); -+ if (caveVinesMaxGrowthAge > 25) { -+ caveVinesMaxGrowthAge = 25; -+ log(Level.WARNING, "blocks.cave_vines.max-growth-age is set to above maximum allowed value of 25"); -+ log(Level.WARNING, "Using value of 25 to prevent issues"); -+ } -+ kelpMaxGrowthAge = getInt("settings.blocks.kelp.max-growth-age", kelpMaxGrowthAge); -+ if (kelpMaxGrowthAge > 25) { -+ kelpMaxGrowthAge = 25; -+ log(Level.WARNING, "blocks.kelp.max-growth-age is set to above maximum allowed value of 25"); -+ log(Level.WARNING, "Using value of 25 to prevent issues"); -+ } -+ twistingVinesMaxGrowthAge = getInt("settings.blocks.twisting_vines.max-growth-age", twistingVinesMaxGrowthAge); -+ if (twistingVinesMaxGrowthAge > 25) { -+ twistingVinesMaxGrowthAge = 25; -+ log(Level.WARNING, "blocks.twisting_vines.max-growth-age is set to above maximum allowed value of 25"); -+ log(Level.WARNING, "Using value of 25 to prevent issues"); -+ } -+ weepingVinesMaxGrowthAge = getInt("settings.blocks.weeping_vines.max-growth-age", weepingVinesMaxGrowthAge); -+ if (weepingVinesMaxGrowthAge > 25) { -+ weepingVinesMaxGrowthAge = 25; -+ log(Level.WARNING, "blocks.weeping_vines.max-growth-age is set to above maximum allowed value of 25"); -+ log(Level.WARNING, "Using value of 25 to prevent issues"); -+ } -+ } -+ -+ public static boolean allowInfinityMending = false; -+ public static boolean allowCrossbowInfinity = false; -+ public static boolean allowShearsLooting = false; -+ public static boolean allowTransparentBlocksInEnchantmentBox = false; -+ public static boolean allowUnsafeEnchants = false; -+ public static boolean allowInapplicableEnchants = true; -+ public static boolean allowIncompatibleEnchants = true; -+ public static boolean allowHigherEnchantsLevels = true; -+ public static boolean allowUnsafeEnchantCommand = false; -+ public static boolean clampEnchantLevels = true; -+ private static void enchantmentSettings() { -+ if (version < 5) { -+ boolean oldValue = getBoolean("settings.enchantment.allow-infinite-and-mending-together", false); -+ set("settings.enchantment.allow-infinity-and-mending-together", oldValue); -+ set("settings.enchantment.allow-infinite-and-mending-together", null); -+ } -+ if (version < 30) { -+ boolean oldValue = getBoolean("settings.enchantment.allow-unsafe-enchants", false); -+ set("settings.enchantment.anvil.allow-unsafe-enchants", oldValue); -+ set("settings.enchantment.anvil.allow-inapplicable-enchants", true); -+ set("settings.enchantment.anvil.allow-incompatible-enchants", true); -+ set("settings.enchantment.anvil.allow-higher-enchants-levels", true); -+ set("settings.enchantment.allow-unsafe-enchants", null); -+ } -+ allowInfinityMending = getBoolean("settings.enchantment.allow-infinity-and-mending-together", allowInfinityMending); -+ allowCrossbowInfinity = getBoolean("settings.enchantment.allow-infinity-on-crossbow", allowCrossbowInfinity); -+ allowShearsLooting = getBoolean("settings.enchantment.allow-looting-on-shears", allowShearsLooting); -+ allowTransparentBlocksInEnchantmentBox = getBoolean("settings.enchantment.allow-transparent-blocks-in-enchantment-box", allowTransparentBlocksInEnchantmentBox); -+ allowUnsafeEnchants = getBoolean("settings.enchantment.anvil.allow-unsafe-enchants", allowUnsafeEnchants); -+ allowInapplicableEnchants = getBoolean("settings.enchantment.anvil.allow-inapplicable-enchants", allowInapplicableEnchants); -+ allowIncompatibleEnchants = getBoolean("settings.enchantment.anvil.allow-incompatible-enchants", allowIncompatibleEnchants); -+ allowHigherEnchantsLevels = getBoolean("settings.enchantment.anvil.allow-higher-enchants-levels", allowHigherEnchantsLevels); -+ allowUnsafeEnchantCommand = getBoolean("settings.enchantment.allow-unsafe-enchant-command", allowUnsafeEnchants); // allowUnsafeEnchants as default for backwards compatability -+ clampEnchantLevels = getBoolean("settings.enchantment.clamp-levels", clampEnchantLevels); -+ } -+ -+ public static boolean endermanShortHeight = false; -+ private static void entitySettings() { -+ endermanShortHeight = getBoolean("settings.entity.enderman.short-height", endermanShortHeight); -+ if (endermanShortHeight) EntityType.ENDERMAN.setDimensions(EntityDimensions.scalable(0.6F, 1.9F)); -+ } -+ -+ public static boolean allowWaterPlacementInTheEnd = true; -+ private static void allowWaterPlacementInEnd() { -+ allowWaterPlacementInTheEnd = getBoolean("settings.allow-water-placement-in-the-end", allowWaterPlacementInTheEnd); -+ } -+ -+ public static boolean disableMushroomBlockUpdates = false; -+ public static boolean disableNoteBlockUpdates = false; -+ public static boolean disableChorusPlantUpdates = false; -+ private static void blockUpdatesSettings() { -+ disableMushroomBlockUpdates = getBoolean("settings.blocks.disable-mushroom-updates", disableMushroomBlockUpdates); -+ disableNoteBlockUpdates = getBoolean("settings.blocks.disable-note-block-updates", disableNoteBlockUpdates); -+ disableChorusPlantUpdates = getBoolean("settings.blocks.disable-chorus-plant-updates", disableChorusPlantUpdates); -+ } -+ -+ public static boolean loggerSuppressInitLegacyMaterialError = false; -+ public static boolean loggerSuppressIgnoredAdvancementWarnings = false; -+ public static boolean loggerSuppressUnrecognizedRecipeErrors = false; -+ public static boolean loggerSuppressSetBlockFarChunk = false; -+ public static boolean loggerSuppressLibraryLoader = false; -+ private static void loggerSettings() { -+ loggerSuppressInitLegacyMaterialError = getBoolean("settings.logger.suppress-init-legacy-material-errors", loggerSuppressInitLegacyMaterialError); -+ loggerSuppressIgnoredAdvancementWarnings = getBoolean("settings.logger.suppress-ignored-advancement-warnings", loggerSuppressIgnoredAdvancementWarnings); -+ loggerSuppressUnrecognizedRecipeErrors = getBoolean("settings.logger.suppress-unrecognized-recipe-errors", loggerSuppressUnrecognizedRecipeErrors); -+ loggerSuppressSetBlockFarChunk = getBoolean("settings.logger.suppress-setblock-in-far-chunk-errors", loggerSuppressSetBlockFarChunk); -+ loggerSuppressLibraryLoader = getBoolean("settings.logger.suppress-library-loader", loggerSuppressLibraryLoader); -+ org.bukkit.plugin.java.JavaPluginLoader.SuppressLibraryLoaderLogger = loggerSuppressLibraryLoader; -+ } -+ -+ public static boolean tpsCatchup = true; -+ private static void tpsCatchup() { -+ tpsCatchup = getBoolean("settings.tps-catchup", tpsCatchup); -+ } -+ -+ public static boolean useUPnP = false; -+ public static boolean maxJoinsPerSecond = false; -+ public static boolean kickForOutOfOrderChat = true; -+ private static void networkSettings() { -+ useUPnP = getBoolean("settings.network.upnp-port-forwarding", useUPnP); -+ maxJoinsPerSecond = getBoolean("settings.network.max-joins-per-second", maxJoinsPerSecond); -+ kickForOutOfOrderChat = getBoolean("settings.network.kick-for-out-of-order-chat", kickForOutOfOrderChat); -+ } -+ -+ public static java.util.regex.Pattern usernameValidCharactersPattern; -+ private static void usernameValidationSettings() { -+ String defaultPattern = "^[a-zA-Z0-9_.]*$"; -+ String setPattern = getString("settings.username-valid-characters", defaultPattern); -+ usernameValidCharactersPattern = java.util.regex.Pattern.compile(setPattern == null || setPattern.isBlank() ? defaultPattern : setPattern); -+ } -+ -+ private static void foodSettings() { -+ ConfigurationSection properties = config.getConfigurationSection("settings.food-properties"); -+ if (properties == null) { -+ config.addDefault("settings.food-properties", new HashMap<>()); -+ return; -+ } -+ properties.getKeys(false).forEach(foodKey -> { -+ FoodProperties food = Foods.ALL_PROPERTIES.get(foodKey); -+ if (food == null) { -+ PurpurConfig.log(Level.SEVERE, "Invalid food property: " + foodKey); -+ return; -+ } -+ FoodProperties foodDefaults = Foods.DEFAULT_PROPERTIES.get(foodKey); -+ food.setNutrition(properties.getInt(foodKey + ".nutrition", foodDefaults.getNutrition())); -+ food.setSaturationModifier((float) properties.getDouble(foodKey + ".saturation-modifier", foodDefaults.getSaturationModifier())); -+ food.setIsMeat(properties.getBoolean(foodKey + ".is-meat", foodDefaults.isMeat())); -+ food.setCanAlwaysEat(properties.getBoolean(foodKey + ".can-always-eat", foodDefaults.canAlwaysEat())); -+ food.setFastFood(properties.getBoolean(foodKey + ".fast-food", foodDefaults.isFastFood())); -+ ConfigurationSection effects = properties.getConfigurationSection(foodKey + ".effects"); -+ if (effects != null) { -+ Map effectDefaults = new HashMap<>(); -+ foodDefaults.getEffects().forEach(pair -> { -+ effectDefaults.put("chance", pair.getSecond()); -+ MobEffectInstance effect = pair.getFirst(); -+ effectDefaults.put("duration", effect.getDuration()); -+ effectDefaults.put("amplifier", effect.getAmplifier()); -+ effectDefaults.put("ambient", effect.isAmbient()); -+ effectDefaults.put("visible", effect.isVisible()); -+ effectDefaults.put("show-icon", effect.showIcon()); -+ }); -+ effects.getKeys(false).forEach(effectKey -> { -+ MobEffect effect = BuiltInRegistries.MOB_EFFECT.get(new ResourceLocation(effectKey)); -+ if (effect == null) { -+ PurpurConfig.log(Level.SEVERE, "Invalid food property effect for " + foodKey + ": " + effectKey); -+ return; -+ } -+ food.getEffects().removeIf(pair -> pair.getFirst().getEffect() == effect); -+ float chance = (float) effects.getDouble(effectKey + ".chance", ((Float) effectDefaults.get("chance")).doubleValue()); -+ int duration = effects.getInt(effectKey + ".duration", (int) effectDefaults.get("duration")); -+ if (chance <= 0.0F || duration < 0) { -+ return; -+ } -+ int amplifier = effects.getInt(effectKey + ".amplifier", (int) effectDefaults.get("amplifier")); -+ boolean ambient = effects.getBoolean(effectKey + ".ambient", (boolean) effectDefaults.get("ambient")); -+ boolean visible = effects.getBoolean(effectKey + ".visible", (boolean) effectDefaults.get("visible")); -+ boolean showIcon = effects.getBoolean(effectKey + ".show-icon", (boolean) effectDefaults.get("show-icon")); -+ food.getEffects().add(Pair.of(new MobEffectInstance(effect, duration, amplifier, ambient, visible, showIcon), chance)); -+ }); -+ } -+ }); -+ } -+ -+ public static boolean fixNetworkSerializedItemsInCreative = false; -+ private static void fixNetworkSerializedCreativeItems() { -+ fixNetworkSerializedItemsInCreative = getBoolean("settings.fix-network-serialized-items-in-creative", fixNetworkSerializedItemsInCreative); -+ } -+ -+ public static boolean fixProjectileLootingTransfer = false; -+ private static void fixProjectileLootingTransfer() { -+ fixProjectileLootingTransfer = getBoolean("settings.fix-projectile-looting-transfer", fixProjectileLootingTransfer); -+ } -+ -+ public static boolean clampAttributes = true; -+ private static void clampAttributes() { -+ clampAttributes = getBoolean("settings.clamp-attributes", clampAttributes); -+ } -+ -+ public static boolean limitArmor = true; -+ private static void limitArmor() { -+ limitArmor = getBoolean("settings.limit-armor", limitArmor); -+ } -+ -+ private static void blastResistanceSettings() { -+ getMap("settings.blast-resistance-overrides", Collections.emptyMap()).forEach((blockId, value) -> { -+ Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(blockId)); -+ if (block == Blocks.AIR) { -+ log(Level.SEVERE, "Invalid block for `settings.blast-resistance-overrides`: " + blockId); -+ return; -+ } -+ if (!(value instanceof Number blastResistance)) { -+ log(Level.SEVERE, "Invalid blast resistance for `settings.blast-resistance-overrides." + blockId + "`: " + value); -+ return; -+ } -+ block.explosionResistance = blastResistance.floatValue(); -+ }); -+ } -+ private static void blockFallMultiplierSettings() { -+ getMap("settings.block-fall-multipliers", Map.ofEntries( -+ Map.entry("minecraft:hay_block", Map.of("damage", 0.2F)), -+ Map.entry("minecraft:white_bed", Map.of("distance", 0.5F)), -+ Map.entry("minecraft:light_gray_bed", Map.of("distance", 0.5F)), -+ Map.entry("minecraft:gray_bed", Map.of("distance", 0.5F)), -+ Map.entry("minecraft:black_bed", Map.of("distance", 0.5F)), -+ Map.entry("minecraft:brown_bed", Map.of("distance", 0.5F)), -+ Map.entry("minecraft:pink_bed", Map.of("distance", 0.5F)), -+ Map.entry("minecraft:red_bed", Map.of("distance", 0.5F)), -+ Map.entry("minecraft:orange_bed", Map.of("distance", 0.5F)), -+ Map.entry("minecraft:yellow_bed", Map.of("distance", 0.5F)), -+ Map.entry("minecraft:green_bed", Map.of("distance", 0.5F)), -+ Map.entry("minecraft:lime_bed", Map.of("distance", 0.5F)), -+ Map.entry("minecraft:cyan_bed", Map.of("distance", 0.5F)), -+ Map.entry("minecraft:light_blue_bed", Map.of("distance", 0.5F)), -+ Map.entry("minecraft:blue_bed", Map.of("distance", 0.5F)), -+ Map.entry("minecraft:purple_bed", Map.of("distance", 0.5F)), -+ Map.entry("minecraft:magenta_bed", Map.of("distance", 0.5F)) -+ )).forEach((blockId, value) -> { -+ Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(blockId)); -+ if (block == Blocks.AIR) { -+ log(Level.SEVERE, "Invalid block for `settings.block-fall-multipliers`: " + blockId); -+ return; -+ } -+ if (!(value instanceof Map map)) { -+ log(Level.SEVERE, "Invalid fall multiplier for `settings.block-fall-multipliers." + blockId + "`: " + value -+ + ", expected a map with keys `damage` and `distance` to floats."); -+ return; -+ } -+ Object rawFallDamageMultiplier = map.get("damage"); -+ if (rawFallDamageMultiplier == null) rawFallDamageMultiplier = 1F; -+ if (!(rawFallDamageMultiplier instanceof Number fallDamageMultiplier)) { -+ log(Level.SEVERE, "Invalid multiplier for `settings.block-fall-multipliers." + blockId + ".damage`: " + map.get("damage")); -+ return; -+ } -+ Object rawFallDistanceMultiplier = map.get("distance"); -+ if (rawFallDistanceMultiplier == null) rawFallDistanceMultiplier = 1F; -+ if (!(rawFallDistanceMultiplier instanceof Number fallDistanceMultiplier)) { -+ log(Level.SEVERE, "Invalid multiplier for `settings.block-fall-multipliers." + blockId + ".distance`: " + map.get("distance")); -+ return; -+ } -+ block.fallDamageMultiplier = fallDamageMultiplier.floatValue(); -+ block.fallDistanceMultiplier = fallDistanceMultiplier.floatValue(); -+ }); -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0db8a1a51c857a3930d0b20028964fb355d8e5b4 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/PurpurWorldConfig.java -@@ -0,0 +1,3201 @@ -+package org.purpurmc.purpur; -+ -+import net.minecraft.core.registries.BuiltInRegistries; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.util.Mth; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.EntityType; -+import net.minecraft.world.item.DyeColor; -+import net.minecraft.world.item.Item; -+import net.minecraft.world.item.Items; -+import net.minecraft.world.level.Explosion; -+import net.minecraft.world.level.block.Block; -+import net.minecraft.world.level.block.Blocks; -+import net.minecraft.world.level.block.state.properties.Tilt; -+import org.purpurmc.purpur.entity.GlowSquidColor; -+import org.purpurmc.purpur.tool.Strippable; -+import org.purpurmc.purpur.tool.Tillable; -+import org.purpurmc.purpur.tool.Waxable; -+import org.purpurmc.purpur.tool.Weatherable; -+import org.apache.commons.lang.BooleanUtils; -+import org.bukkit.ChatColor; -+import org.bukkit.World; -+import org.bukkit.configuration.ConfigurationSection; -+import java.util.ArrayList; -+import java.util.HashMap; -+import java.util.List; -+import java.util.Locale; -+import java.util.Map; -+import java.util.function.Predicate; -+import java.util.logging.Level; -+import static org.purpurmc.purpur.PurpurConfig.log; -+ -+@SuppressWarnings("unused") -+public class PurpurWorldConfig { -+ -+ private final String worldName; -+ private final World.Environment environment; -+ -+ public PurpurWorldConfig(String worldName, World.Environment environment) { -+ this.worldName = worldName; -+ this.environment = environment; -+ init(); -+ } -+ -+ public void init() { -+ log("-------- World Settings For [" + worldName + "] --------"); -+ PurpurConfig.readConfig(PurpurWorldConfig.class, this); -+ } -+ -+ private void set(String path, Object val) { -+ PurpurConfig.config.addDefault("world-settings.default." + path, val); -+ PurpurConfig.config.set("world-settings.default." + path, val); -+ if (PurpurConfig.config.get("world-settings." + worldName + "." + path) != null) { -+ PurpurConfig.config.addDefault("world-settings." + worldName + "." + path, val); -+ PurpurConfig.config.set("world-settings." + worldName + "." + path, val); -+ } -+ } -+ -+ private ConfigurationSection getConfigurationSection(String path) { -+ ConfigurationSection section = PurpurConfig.config.getConfigurationSection("world-settings." + worldName + "." + path); -+ return section != null ? section : PurpurConfig.config.getConfigurationSection("world-settings.default." + path); -+ } -+ -+ private String getString(String path, String def) { -+ PurpurConfig.config.addDefault("world-settings.default." + path, def); -+ return PurpurConfig.config.getString("world-settings." + worldName + "." + path, PurpurConfig.config.getString("world-settings.default." + path)); -+ } -+ -+ private boolean getBoolean(String path, boolean def) { -+ PurpurConfig.config.addDefault("world-settings.default." + path, def); -+ return PurpurConfig.config.getBoolean("world-settings." + worldName + "." + path, PurpurConfig.config.getBoolean("world-settings.default." + path)); -+ } -+ -+ private boolean getBoolean(String path, Predicate predicate) { -+ String val = getString(path, "default").toLowerCase(); -+ Boolean bool = BooleanUtils.toBooleanObject(val, "true", "false", "default"); -+ return predicate.test(bool); -+ } -+ -+ private double getDouble(String path, double def) { -+ PurpurConfig.config.addDefault("world-settings.default." + path, def); -+ return PurpurConfig.config.getDouble("world-settings." + worldName + "." + path, PurpurConfig.config.getDouble("world-settings.default." + path)); -+ } -+ -+ private int getInt(String path, int def) { -+ PurpurConfig.config.addDefault("world-settings.default." + path, def); -+ return PurpurConfig.config.getInt("world-settings." + worldName + "." + path, PurpurConfig.config.getInt("world-settings.default." + path)); -+ } -+ -+ private List getList(String path, T def) { -+ PurpurConfig.config.addDefault("world-settings.default." + path, def); -+ return PurpurConfig.config.getList("world-settings." + worldName + "." + path, PurpurConfig.config.getList("world-settings.default." + path)); -+ } -+ -+ private Map getMap(String path, Map def) { -+ final Map fallback = PurpurConfig.getMap("world-settings.default." + path, def); -+ final Map value = PurpurConfig.getMap("world-settings." + worldName + "." + path, null); -+ return value.isEmpty() ? fallback : value; -+ } -+ -+ public float armorstandStepHeight = 0.0F; -+ public boolean armorstandSetNameVisible = true; -+ public boolean armorstandFixNametags = false; -+ public boolean armorstandMovement = true; -+ public boolean armorstandWaterMovement = true; -+ public boolean armorstandWaterFence = true; -+ public boolean armorstandPlaceWithArms = false; -+ private void armorstandSettings() { -+ armorstandStepHeight = (float) getDouble("gameplay-mechanics.armorstand.step-height", armorstandStepHeight); -+ armorstandSetNameVisible = getBoolean("gameplay-mechanics.armorstand.set-name-visible-when-placing-with-custom-name", armorstandSetNameVisible); -+ armorstandFixNametags = getBoolean("gameplay-mechanics.armorstand.fix-nametags", armorstandFixNametags); -+ armorstandMovement = getBoolean("gameplay-mechanics.armorstand.can-movement-tick", armorstandMovement); -+ armorstandWaterMovement = getBoolean("gameplay-mechanics.armorstand.can-move-in-water", armorstandWaterMovement); -+ armorstandWaterFence = getBoolean("gameplay-mechanics.armorstand.can-move-in-water-over-fence", armorstandWaterFence); -+ armorstandPlaceWithArms = getBoolean("gameplay-mechanics.armorstand.place-with-arms-visible", armorstandPlaceWithArms); -+ } -+ -+ public boolean arrowMovementResetsDespawnCounter = true; -+ private void arrowSettings() { -+ arrowMovementResetsDespawnCounter = getBoolean("gameplay-mechanics.arrow.movement-resets-despawn-counter", arrowMovementResetsDespawnCounter); -+ } -+ -+ public boolean useBetterMending = false; -+ public boolean alwaysTameInCreative = false; -+ public boolean boatEjectPlayersOnLand = false; -+ public boolean boatsDoFallDamage = false; -+ public boolean disableDropsOnCrammingDeath = false; -+ public boolean entitiesCanUsePortals = true; -+ public boolean entitiesPickUpLootBypassMobGriefing = false; -+ public boolean fireballsBypassMobGriefing = false; -+ public boolean imposeTeleportRestrictionsOnGateways = false; -+ public boolean milkCuresBadOmen = true; -+ public boolean milkClearsBeneficialEffects = true; -+ public boolean noteBlockIgnoreAbove = false; -+ public boolean persistentDroppableEntityDisplayNames = true; -+ public boolean persistentTileEntityDisplayNames = false; -+ public boolean projectilesBypassMobGriefing = false; -+ public boolean tickFluids = true; -+ public double mobsBlindnessMultiplier = 1; -+ public double tridentLoyaltyVoidReturnHeight = 0.0D; -+ public double voidDamageHeight = -64.0D; -+ public double voidDamageDealt = 4.0D; -+ public int raidCooldownSeconds = 0; -+ public int animalBreedingCooldownSeconds = 0; -+ public boolean mobsIgnoreRails = false; -+ public boolean rainStopsAfterSleep = true; -+ public boolean thunderStopsAfterSleep = true; -+ public int mobLastHurtByPlayerTime = 100; -+ private void miscGameplayMechanicsSettings() { -+ useBetterMending = getBoolean("gameplay-mechanics.use-better-mending", useBetterMending); -+ alwaysTameInCreative = getBoolean("gameplay-mechanics.always-tame-in-creative", alwaysTameInCreative); -+ boatEjectPlayersOnLand = getBoolean("gameplay-mechanics.boat.eject-players-on-land", boatEjectPlayersOnLand); -+ boatsDoFallDamage = getBoolean("gameplay-mechanics.boat.do-fall-damage", boatsDoFallDamage); -+ disableDropsOnCrammingDeath = getBoolean("gameplay-mechanics.disable-drops-on-cramming-death", disableDropsOnCrammingDeath); -+ entitiesCanUsePortals = getBoolean("gameplay-mechanics.entities-can-use-portals", entitiesCanUsePortals); -+ entitiesPickUpLootBypassMobGriefing = getBoolean("gameplay-mechanics.entities-pick-up-loot-bypass-mob-griefing", entitiesPickUpLootBypassMobGriefing); -+ fireballsBypassMobGriefing = getBoolean("gameplay-mechanics.fireballs-bypass-mob-griefing", fireballsBypassMobGriefing); -+ imposeTeleportRestrictionsOnGateways = getBoolean("gameplay-mechanics.impose-teleport-restrictions-on-gateways", imposeTeleportRestrictionsOnGateways); -+ milkCuresBadOmen = getBoolean("gameplay-mechanics.milk-cures-bad-omen", milkCuresBadOmen); -+ milkClearsBeneficialEffects = getBoolean("gameplay-mechanics.milk-clears-beneficial-effects", milkClearsBeneficialEffects); -+ noteBlockIgnoreAbove = getBoolean("gameplay-mechanics.note-block-ignore-above", noteBlockIgnoreAbove); -+ persistentTileEntityDisplayNames = getBoolean("gameplay-mechanics.persistent-tileentity-display-names-and-lore", persistentTileEntityDisplayNames); -+ persistentDroppableEntityDisplayNames = getBoolean("gameplay-mechanics.persistent-droppable-entity-display-names", persistentDroppableEntityDisplayNames); -+ projectilesBypassMobGriefing = getBoolean("gameplay-mechanics.projectiles-bypass-mob-griefing", projectilesBypassMobGriefing); -+ tickFluids = getBoolean("gameplay-mechanics.tick-fluids", tickFluids); -+ mobsBlindnessMultiplier = getDouble("gameplay-mechanics.entity-blindness-multiplier", mobsBlindnessMultiplier); -+ tridentLoyaltyVoidReturnHeight = getDouble("gameplay-mechanics.trident-loyalty-void-return-height", tridentLoyaltyVoidReturnHeight); -+ voidDamageHeight = getDouble("gameplay-mechanics.void-damage-height", voidDamageHeight); -+ voidDamageDealt = getDouble("gameplay-mechanics.void-damage-dealt", voidDamageDealt); -+ raidCooldownSeconds = getInt("gameplay-mechanics.raid-cooldown-seconds", raidCooldownSeconds); -+ animalBreedingCooldownSeconds = getInt("gameplay-mechanics.animal-breeding-cooldown-seconds", animalBreedingCooldownSeconds); -+ mobsIgnoreRails = getBoolean("gameplay-mechanics.mobs-ignore-rails", mobsIgnoreRails); -+ rainStopsAfterSleep = getBoolean("gameplay-mechanics.rain-stops-after-sleep", rainStopsAfterSleep); -+ thunderStopsAfterSleep = getBoolean("gameplay-mechanics.thunder-stops-after-sleep", thunderStopsAfterSleep); -+ mobLastHurtByPlayerTime = getInt("gameplay-mechanics.mob-last-hurt-by-player-time", mobLastHurtByPlayerTime); -+ } -+ -+ public int daytimeTicks = 12000; -+ public int nighttimeTicks = 12000; -+ private void daytimeCycleSettings() { -+ daytimeTicks = getInt("gameplay-mechanics.daylight-cycle-ticks.daytime", daytimeTicks); -+ nighttimeTicks = getInt("gameplay-mechanics.daylight-cycle-ticks.nighttime", nighttimeTicks); -+ } -+ -+ public int drowningAirTicks = 300; -+ public int drowningDamageInterval = 20; -+ public double damageFromDrowning = 2.0F; -+ private void drowningSettings() { -+ drowningAirTicks = getInt("gameplay-mechanics.drowning.air-ticks", drowningAirTicks); -+ drowningDamageInterval = getInt("gameplay-mechanics.drowning.ticks-per-damage", drowningDamageInterval); -+ damageFromDrowning = getDouble("gameplay-mechanics.drowning.damage-from-drowning", damageFromDrowning); -+ } -+ -+ public int elytraDamagePerSecond = 1; -+ public double elytraDamageMultiplyBySpeed = 0; -+ public boolean elytraIgnoreUnbreaking = false; -+ public int elytraDamagePerFireworkBoost = 0; -+ public int elytraDamagePerTridentBoost = 0; -+ public boolean elytraKineticDamage = true; -+ private void elytraSettings() { -+ elytraDamagePerSecond = getInt("gameplay-mechanics.elytra.damage-per-second", elytraDamagePerSecond); -+ elytraDamageMultiplyBySpeed = getDouble("gameplay-mechanics.elytra.damage-multiplied-by-speed", elytraDamageMultiplyBySpeed); -+ elytraIgnoreUnbreaking = getBoolean("gameplay-mechanics.elytra.ignore-unbreaking", elytraIgnoreUnbreaking); -+ elytraDamagePerFireworkBoost = getInt("gameplay-mechanics.elytra.damage-per-boost.firework", elytraDamagePerFireworkBoost); -+ elytraDamagePerTridentBoost = getInt("gameplay-mechanics.elytra.damage-per-boost.trident", elytraDamagePerTridentBoost); -+ elytraKineticDamage = getBoolean("gameplay-mechanics.elytra.kinetic-damage", elytraKineticDamage); -+ } -+ -+ public int entityLifeSpan = 0; -+ public float entityLeftHandedChance = 0.05f; -+ public boolean entitySharedRandom = true; -+ private void entitySettings() { -+ entityLifeSpan = getInt("gameplay-mechanics.entity-lifespan", entityLifeSpan); -+ entityLeftHandedChance = (float) getDouble("gameplay-mechanics.entity-left-handed-chance", entityLeftHandedChance); -+ entitySharedRandom = getBoolean("settings.entity.shared-random", entitySharedRandom); -+ } -+ -+ public boolean explosionClampRadius = true; -+ private void explosionSettings() { -+ explosionClampRadius = getBoolean("gameplay-mechanics.clamp-explosion-radius", explosionClampRadius); -+ } -+ -+ public boolean infinityWorksWithoutArrows = false; -+ public boolean infinityWorksWithNormalArrows = true; -+ public boolean infinityWorksWithSpectralArrows = false; -+ public boolean infinityWorksWithTippedArrows = false; -+ private void infinityArrowsSettings() { -+ infinityWorksWithoutArrows = getBoolean("gameplay-mechanics.infinity-bow.works-without-arrows", infinityWorksWithoutArrows); -+ infinityWorksWithNormalArrows = getBoolean("gameplay-mechanics.infinity-bow.normal-arrows", infinityWorksWithNormalArrows); -+ infinityWorksWithSpectralArrows = getBoolean("gameplay-mechanics.infinity-bow.spectral-arrows", infinityWorksWithSpectralArrows); -+ infinityWorksWithTippedArrows = getBoolean("gameplay-mechanics.infinity-bow.tipped-arrows", infinityWorksWithTippedArrows); -+ } -+ -+ public List itemImmuneToCactus = new ArrayList<>(); -+ public List itemImmuneToExplosion = new ArrayList<>(); -+ public List itemImmuneToFire = new ArrayList<>(); -+ public List itemImmuneToLightning = new ArrayList<>(); -+ public boolean dontRunWithScissors = false; -+ public boolean ignoreScissorsInWater = false; -+ public boolean ignoreScissorsInLava = false; -+ public double scissorsRunningDamage = 1D; -+ public float enderPearlDamage = 5.0F; -+ public int enderPearlCooldown = 20; -+ public int enderPearlCooldownCreative = 20; -+ public float enderPearlEndermiteChance = 0.05F; -+ public int glowBerriesEatGlowDuration = 0; -+ public boolean shulkerBoxItemDropContentsWhenDestroyed = true; -+ public boolean compassItemShowsBossBar = false; -+ public boolean snowballExtinguishesFire = false; -+ public boolean snowballExtinguishesCandles = false; -+ public boolean snowballExtinguishesCampfires = false; -+ private void itemSettings() { -+ itemImmuneToCactus.clear(); -+ getList("gameplay-mechanics.item.immune.cactus", new ArrayList<>()).forEach(key -> { -+ if (key.toString().equals("*")) { -+ BuiltInRegistries.ITEM.stream().filter(item -> item != Items.AIR).forEach((item) -> itemImmuneToCactus.add(item)); -+ return; -+ } -+ Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(key.toString())); -+ if (item != Items.AIR) itemImmuneToCactus.add(item); -+ }); -+ itemImmuneToExplosion.clear(); -+ getList("gameplay-mechanics.item.immune.explosion", new ArrayList<>()).forEach(key -> { -+ if (key.toString().equals("*")) { -+ BuiltInRegistries.ITEM.stream().filter(item -> item != Items.AIR).forEach((item) -> itemImmuneToExplosion.add(item)); -+ return; -+ } -+ Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(key.toString())); -+ if (item != Items.AIR) itemImmuneToExplosion.add(item); -+ }); -+ itemImmuneToFire.clear(); -+ getList("gameplay-mechanics.item.immune.fire", new ArrayList<>()).forEach(key -> { -+ if (key.toString().equals("*")) { -+ BuiltInRegistries.ITEM.stream().filter(item -> item != Items.AIR).forEach((item) -> itemImmuneToFire.add(item)); -+ return; -+ } -+ Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(key.toString())); -+ if (item != Items.AIR) itemImmuneToFire.add(item); -+ }); -+ itemImmuneToLightning.clear(); -+ getList("gameplay-mechanics.item.immune.lightning", new ArrayList<>()).forEach(key -> { -+ if (key.toString().equals("*")) { -+ BuiltInRegistries.ITEM.stream().filter(item -> item != Items.AIR).forEach((item) -> itemImmuneToLightning.add(item)); -+ return; -+ } -+ Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(key.toString())); -+ if (item != Items.AIR) itemImmuneToLightning.add(item); -+ }); -+ dontRunWithScissors = getBoolean("gameplay-mechanics.item.shears.damage-if-sprinting", dontRunWithScissors); -+ ignoreScissorsInWater = getBoolean("gameplay-mechanics.item.shears.ignore-in-water", ignoreScissorsInWater); -+ ignoreScissorsInLava = getBoolean("gameplay-mechanics.item.shears.ignore-in-lava", ignoreScissorsInLava); -+ scissorsRunningDamage = getDouble("gameplay-mechanics.item.shears.sprinting-damage", scissorsRunningDamage); -+ enderPearlDamage = (float) getDouble("gameplay-mechanics.item.ender-pearl.damage", enderPearlDamage); -+ enderPearlCooldown = getInt("gameplay-mechanics.item.ender-pearl.cooldown", enderPearlCooldown); -+ enderPearlCooldownCreative = getInt("gameplay-mechanics.item.ender-pearl.creative-cooldown", enderPearlCooldownCreative); -+ enderPearlEndermiteChance = (float) getDouble("gameplay-mechanics.item.ender-pearl.endermite-spawn-chance", enderPearlEndermiteChance); -+ glowBerriesEatGlowDuration = getInt("gameplay-mechanics.item.glow_berries.eat-glow-duration", glowBerriesEatGlowDuration); -+ shulkerBoxItemDropContentsWhenDestroyed = getBoolean("gameplay-mechanics.item.shulker_box.drop-contents-when-destroyed", shulkerBoxItemDropContentsWhenDestroyed); -+ compassItemShowsBossBar = getBoolean("gameplay-mechanics.item.compass.holding-shows-bossbar", compassItemShowsBossBar); -+ snowballExtinguishesFire = getBoolean("gameplay-mechanics.item.snowball.extinguish.fire", snowballExtinguishesFire); -+ snowballExtinguishesCandles = getBoolean("gameplay-mechanics.item.snowball.extinguish.candles", snowballExtinguishesCandles); -+ snowballExtinguishesCampfires = getBoolean("gameplay-mechanics.item.snowball.extinguish.campfires", snowballExtinguishesCampfires); -+ } -+ -+ public double minecartMaxSpeed = 0.4D; -+ public boolean minecartPlaceAnywhere = false; -+ public boolean minecartControllable = false; -+ public float minecartControllableStepHeight = 1.0F; -+ public double minecartControllableHopBoost = 0.5D; -+ public boolean minecartControllableFallDamage = true; -+ public double minecartControllableBaseSpeed = 0.1D; -+ public Map minecartControllableBlockSpeeds = new HashMap<>(); -+ public double poweredRailBoostModifier = 0.06; -+ private void minecartSettings() { -+ if (PurpurConfig.version < 12) { -+ boolean oldBool = getBoolean("gameplay-mechanics.controllable-minecarts.place-anywhere", minecartPlaceAnywhere); -+ set("gameplay-mechanics.controllable-minecarts.place-anywhere", null); -+ set("gameplay-mechanics.minecart.place-anywhere", oldBool); -+ oldBool = getBoolean("gameplay-mechanics.controllable-minecarts.enabled", minecartControllable); -+ set("gameplay-mechanics.controllable-minecarts.enabled", null); -+ set("gameplay-mechanics.minecart.controllable.enabled", oldBool); -+ double oldDouble = getDouble("gameplay-mechanics.controllable-minecarts.step-height", minecartControllableStepHeight); -+ set("gameplay-mechanics.controllable-minecarts.step-height", null); -+ set("gameplay-mechanics.minecart.controllable.step-height", oldDouble); -+ oldDouble = getDouble("gameplay-mechanics.controllable-minecarts.hop-boost", minecartControllableHopBoost); -+ set("gameplay-mechanics.controllable-minecarts.hop-boost", null); -+ set("gameplay-mechanics.minecart.controllable.hop-boost", oldDouble); -+ oldBool = getBoolean("gameplay-mechanics.controllable-minecarts.fall-damage", minecartControllableFallDamage); -+ set("gameplay-mechanics.controllable-minecarts.fall-damage", null); -+ set("gameplay-mechanics.minecart.controllable.fall-damage", oldBool); -+ oldDouble = getDouble("gameplay-mechanics.controllable-minecarts.base-speed", minecartControllableBaseSpeed); -+ set("gameplay-mechanics.controllable-minecarts.base-speed", null); -+ set("gameplay-mechanics.minecart.controllable.base-speed", oldDouble); -+ ConfigurationSection section = getConfigurationSection("gameplay-mechanics.controllable-minecarts.block-speed"); -+ if (section != null) { -+ for (String key : section.getKeys(false)) { -+ if ("grass-block".equals(key)) key = "grass_block"; // oopsie -+ oldDouble = section.getDouble(key, minecartControllableBaseSpeed); -+ set("gameplay-mechanics.controllable-minecarts.block-speed." + key, null); -+ set("gameplay-mechanics.minecart.controllable.block-speed." + key, oldDouble); -+ } -+ set("gameplay-mechanics.controllable-minecarts.block-speed", null); -+ } -+ set("gameplay-mechanics.controllable-minecarts", null); -+ } -+ -+ minecartMaxSpeed = getDouble("gameplay-mechanics.minecart.max-speed", minecartMaxSpeed); -+ minecartPlaceAnywhere = getBoolean("gameplay-mechanics.minecart.place-anywhere", minecartPlaceAnywhere); -+ minecartControllable = getBoolean("gameplay-mechanics.minecart.controllable.enabled", minecartControllable); -+ minecartControllableStepHeight = (float) getDouble("gameplay-mechanics.minecart.controllable.step-height", minecartControllableStepHeight); -+ minecartControllableHopBoost = getDouble("gameplay-mechanics.minecart.controllable.hop-boost", minecartControllableHopBoost); -+ minecartControllableFallDamage = getBoolean("gameplay-mechanics.minecart.controllable.fall-damage", minecartControllableFallDamage); -+ minecartControllableBaseSpeed = getDouble("gameplay-mechanics.minecart.controllable.base-speed", minecartControllableBaseSpeed); -+ ConfigurationSection section = getConfigurationSection("gameplay-mechanics.minecart.controllable.block-speed"); -+ if (section != null) { -+ for (String key : section.getKeys(false)) { -+ Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(key)); -+ if (block != Blocks.AIR) { -+ minecartControllableBlockSpeeds.put(block, section.getDouble(key, minecartControllableBaseSpeed)); -+ } -+ } -+ } else { -+ set("gameplay-mechanics.minecart.controllable.block-speed.grass_block", 0.3D); -+ set("gameplay-mechanics.minecart.controllable.block-speed.stone", 0.5D); -+ } -+ poweredRailBoostModifier = getDouble("gameplay-mechanics.minecart.powered-rail.boost-modifier", poweredRailBoostModifier); -+ } -+ -+ public float entityHealthRegenAmount = 1.0F; -+ public float entityMinimalHealthPoison = 1.0F; -+ public float entityPoisonDegenerationAmount = 1.0F; -+ public float entityWitherDegenerationAmount = 1.0F; -+ public float humanHungerExhaustionAmount = 0.005F; -+ public float humanSaturationRegenAmount = 1.0F; -+ private void mobEffectSettings() { -+ entityHealthRegenAmount = (float) getDouble("gameplay-mechanics.mob-effects.health-regen-amount", entityHealthRegenAmount); -+ entityMinimalHealthPoison = (float) getDouble("gameplay-mechanics.mob-effects.minimal-health-poison-amount", entityMinimalHealthPoison); -+ entityPoisonDegenerationAmount = (float) getDouble("gameplay-mechanics.mob-effects.poison-degeneration-amount", entityPoisonDegenerationAmount); -+ entityWitherDegenerationAmount = (float) getDouble("gameplay-mechanics.mob-effects.wither-degeneration-amount", entityWitherDegenerationAmount); -+ humanHungerExhaustionAmount = (float) getDouble("gameplay-mechanics.mob-effects.hunger-exhaustion-amount", humanHungerExhaustionAmount); -+ humanSaturationRegenAmount = (float) getDouble("gameplay-mechanics.mob-effects.saturation-regen-amount", humanSaturationRegenAmount); -+ } -+ -+ public boolean catSpawning; -+ public boolean patrolSpawning; -+ public boolean phantomSpawning; -+ public boolean villagerTraderSpawning; -+ public boolean villageSiegeSpawning; -+ public boolean mobSpawningIgnoreCreativePlayers = false; -+ private void mobSpawnerSettings() { -+ // values of "default" or null will default to true only if the world environment is normal (aka overworld) -+ Predicate predicate = (bool) -> (bool != null && bool) || (bool == null && environment == World.Environment.NORMAL); -+ catSpawning = getBoolean("gameplay-mechanics.mob-spawning.village-cats", predicate); -+ patrolSpawning = getBoolean("gameplay-mechanics.mob-spawning.raid-patrols", predicate); -+ phantomSpawning = getBoolean("gameplay-mechanics.mob-spawning.phantoms", predicate); -+ villagerTraderSpawning = getBoolean("gameplay-mechanics.mob-spawning.wandering-traders", predicate); -+ villageSiegeSpawning = getBoolean("gameplay-mechanics.mob-spawning.village-sieges", predicate); -+ mobSpawningIgnoreCreativePlayers = getBoolean("gameplay-mechanics.mob-spawning.ignore-creative-players", mobSpawningIgnoreCreativePlayers); -+ } -+ -+ public boolean disableObserverClocks = false; -+ private void observerSettings() { -+ disableObserverClocks = getBoolean("blocks.observer.disable-clock", disableObserverClocks); -+ } -+ -+ public int playerNetheriteFireResistanceDuration = 0; -+ public int playerNetheriteFireResistanceAmplifier = 0; -+ public boolean playerNetheriteFireResistanceAmbient = false; -+ public boolean playerNetheriteFireResistanceShowParticles = false; -+ public boolean playerNetheriteFireResistanceShowIcon = true; -+ private void playerNetheriteFireResistance() { -+ playerNetheriteFireResistanceDuration = getInt("gameplay-mechanics.player.netherite-fire-resistance.duration", playerNetheriteFireResistanceDuration); -+ playerNetheriteFireResistanceAmplifier = getInt("gameplay-mechanics.player.netherite-fire-resistance.amplifier", playerNetheriteFireResistanceAmplifier); -+ playerNetheriteFireResistanceAmbient = getBoolean("gameplay-mechanics.player.netherite-fire-resistance.ambient", playerNetheriteFireResistanceAmbient); -+ playerNetheriteFireResistanceShowParticles = getBoolean("gameplay-mechanics.player.netherite-fire-resistance.show-particles", playerNetheriteFireResistanceShowParticles); -+ playerNetheriteFireResistanceShowIcon = getBoolean("gameplay-mechanics.player.netherite-fire-resistance.show-icon", playerNetheriteFireResistanceShowIcon); -+ } -+ -+ public boolean idleTimeoutKick = true; -+ public boolean idleTimeoutTickNearbyEntities = true; -+ public boolean idleTimeoutCountAsSleeping = false; -+ public boolean idleTimeoutUpdateTabList = false; -+ public boolean idleTimeoutTargetPlayer = true; -+ public int playerSpawnInvulnerableTicks = 60; -+ public boolean playerInvulnerableWhileAcceptingResourcePack = false; -+ public String playerDeathExpDropEquation = "expLevel * 7"; -+ public int playerDeathExpDropMax = 100; -+ public boolean teleportIfOutsideBorder = false; -+ public boolean teleportOnNetherCeilingDamage = false; -+ public boolean totemOfUndyingWorksInInventory = false; -+ public boolean playerFixStuckPortal = false; -+ public boolean creativeOnePunch = false; -+ public boolean playerSleepNearMonsters = false; -+ public boolean playersSkipNight = true; -+ public double playerCriticalDamageMultiplier = 1.5D; -+ public int playerBurpDelay = 10; -+ public boolean playerBurpWhenFull = false; -+ public int playerPortalWaitTime = 80; -+ public int playerCreativePortalWaitTime = 1; -+ public boolean playerRidableInWater = false; -+ public boolean playerRemoveBindingWithWeakness = false; -+ public int shiftRightClickRepairsMendingPoints = 0; -+ public int playerExpPickupDelay = 2; -+ public boolean playerVoidTrading = false; -+ private void playerSettings() { -+ if (PurpurConfig.version < 19) { -+ boolean oldVal = getBoolean("gameplay-mechanics.player.idle-timeout.mods-target", idleTimeoutTargetPlayer); -+ set("gameplay-mechanics.player.idle-timeout.mods-target", null); -+ set("gameplay-mechanics.player.idle-timeout.mobs-target", oldVal); -+ } -+ idleTimeoutKick = System.getenv("PURPUR_FORCE_IDLE_KICK") == null ? getBoolean("gameplay-mechanics.player.idle-timeout.kick-if-idle", idleTimeoutKick) : Boolean.parseBoolean(System.getenv("PURPUR_FORCE_IDLE_KICK")); -+ idleTimeoutTickNearbyEntities = getBoolean("gameplay-mechanics.player.idle-timeout.tick-nearby-entities", idleTimeoutTickNearbyEntities); -+ idleTimeoutCountAsSleeping = getBoolean("gameplay-mechanics.player.idle-timeout.count-as-sleeping", idleTimeoutCountAsSleeping); -+ idleTimeoutUpdateTabList = getBoolean("gameplay-mechanics.player.idle-timeout.update-tab-list", idleTimeoutUpdateTabList); -+ idleTimeoutTargetPlayer = getBoolean("gameplay-mechanics.player.idle-timeout.mobs-target", idleTimeoutTargetPlayer); -+ playerSpawnInvulnerableTicks = getInt("gameplay-mechanics.player.spawn-invulnerable-ticks", playerSpawnInvulnerableTicks); -+ playerInvulnerableWhileAcceptingResourcePack = getBoolean("gameplay-mechanics.player.invulnerable-while-accepting-resource-pack", playerInvulnerableWhileAcceptingResourcePack); -+ playerDeathExpDropEquation = getString("gameplay-mechanics.player.exp-dropped-on-death.equation", playerDeathExpDropEquation); -+ playerDeathExpDropMax = getInt("gameplay-mechanics.player.exp-dropped-on-death.maximum", playerDeathExpDropMax); -+ teleportIfOutsideBorder = getBoolean("gameplay-mechanics.player.teleport-if-outside-border", teleportIfOutsideBorder); -+ teleportOnNetherCeilingDamage = getBoolean("gameplay-mechanics.player.teleport-on-nether-ceiling-damage", teleportOnNetherCeilingDamage); -+ totemOfUndyingWorksInInventory = getBoolean("gameplay-mechanics.player.totem-of-undying-works-in-inventory", totemOfUndyingWorksInInventory); -+ playerFixStuckPortal = getBoolean("gameplay-mechanics.player.fix-stuck-in-portal", playerFixStuckPortal); -+ creativeOnePunch = getBoolean("gameplay-mechanics.player.one-punch-in-creative", creativeOnePunch); -+ playerSleepNearMonsters = getBoolean("gameplay-mechanics.player.sleep-ignore-nearby-mobs", playerSleepNearMonsters); -+ playersSkipNight = getBoolean("gameplay-mechanics.player.can-skip-night", playersSkipNight); -+ playerCriticalDamageMultiplier = getDouble("gameplay-mechanics.player.critical-damage-multiplier", playerCriticalDamageMultiplier); -+ playerBurpDelay = getInt("gameplay-mechanics.player.burp-delay", playerBurpDelay); -+ playerBurpWhenFull = getBoolean("gameplay-mechanics.player.burp-when-full", playerBurpWhenFull); -+ playerPortalWaitTime = getInt("gameplay-mechanics.player.portal-wait-time", playerPortalWaitTime); -+ playerCreativePortalWaitTime = getInt("gameplay-mechanics.player.creative-portal-wait-time", playerCreativePortalWaitTime); -+ playerRidableInWater = getBoolean("gameplay-mechanics.player.ridable-in-water", playerRidableInWater); -+ playerRemoveBindingWithWeakness = getBoolean("gameplay-mechanics.player.curse-of-binding.remove-with-weakness", playerRemoveBindingWithWeakness); -+ shiftRightClickRepairsMendingPoints = getInt("gameplay-mechanics.player.shift-right-click-repairs-mending-points", shiftRightClickRepairsMendingPoints); -+ playerExpPickupDelay = getInt("gameplay-mechanics.player.exp-pickup-delay-ticks", playerExpPickupDelay); -+ playerVoidTrading = getBoolean("gameplay-mechanics.player.allow-void-trading", playerVoidTrading); -+ } -+ -+ private static boolean projectileDespawnRateSettingsMigrated = false; -+ private void projectileDespawnRateSettings() { -+ if (PurpurConfig.version < 28 && !projectileDespawnRateSettingsMigrated) { -+ migrateProjectileDespawnRateSettings(EntityType.DRAGON_FIREBALL); -+ migrateProjectileDespawnRateSettings(EntityType.EGG); -+ migrateProjectileDespawnRateSettings(EntityType.ENDER_PEARL); -+ migrateProjectileDespawnRateSettings(EntityType.EXPERIENCE_BOTTLE); -+ migrateProjectileDespawnRateSettings(EntityType.FIREWORK_ROCKET); -+ migrateProjectileDespawnRateSettings(EntityType.FISHING_BOBBER); -+ migrateProjectileDespawnRateSettings(EntityType.FIREBALL); -+ migrateProjectileDespawnRateSettings(EntityType.LLAMA_SPIT); -+ migrateProjectileDespawnRateSettings(EntityType.POTION); -+ migrateProjectileDespawnRateSettings(EntityType.SHULKER_BULLET); -+ migrateProjectileDespawnRateSettings(EntityType.SMALL_FIREBALL); -+ migrateProjectileDespawnRateSettings(EntityType.SNOWBALL); -+ migrateProjectileDespawnRateSettings(EntityType.WITHER_SKULL); -+ //PufferfishConfig.save(); -+ set("gameplay-mechanics.projectile-despawn-rates", null); -+ // pufferfish's entity_timeout is a global config -+ // we only want to migrate values from the -+ // default world (first world loaded) -+ projectileDespawnRateSettingsMigrated = true; -+ } -+ } -+ private void migrateProjectileDespawnRateSettings(EntityType type) { -+ //String pufferName = "entity_timeouts." + type.id.toUpperCase(Locale.ROOT); -+ //int value = getInt("gameplay-mechanics.projectile-despawn-rates." + type.id, -1); -+ //if (value != -1 && PufferfishConfig.getRawInt(pufferName, -1) == -1) { -+ // PufferfishConfig.setInt(pufferName, value); -+ // type.ttl = value; -+ //} -+ } -+ -+ public double bowProjectileOffset = 1.0D; -+ public double crossbowProjectileOffset = 1.0D; -+ public double eggProjectileOffset = 1.0D; -+ public double enderPearlProjectileOffset = 1.0D; -+ public double throwablePotionProjectileOffset = 1.0D; -+ public double tridentProjectileOffset = 1.0D; -+ public double snowballProjectileOffset = 1.0D; -+ private void projectileOffsetSettings() { -+ bowProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.bow", bowProjectileOffset); -+ crossbowProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.crossbow", crossbowProjectileOffset); -+ eggProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.egg", eggProjectileOffset); -+ enderPearlProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.ender-pearl", enderPearlProjectileOffset); -+ throwablePotionProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.throwable-potion", throwablePotionProjectileOffset); -+ tridentProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.trident", tridentProjectileOffset); -+ snowballProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.snowball", snowballProjectileOffset); -+ } -+ -+ public int snowballDamage = -1; -+ private void snowballSettings() { -+ snowballDamage = getInt("gameplay-mechanics.projectile-damage.snowball", snowballDamage); -+ } -+ -+ public List shovelTurnsBlockToGrassPath = new ArrayList<>(); -+ private void shovelSettings() { -+ getList("gameplay-mechanics.shovel-turns-block-to-grass-path", new ArrayList(){{ -+ add("minecraft:coarse_dirt"); -+ add("minecraft:dirt"); -+ add("minecraft:grass_block"); -+ add("minecraft:mycelium"); -+ add("minecraft:podzol"); -+ add("minecraft:rooted_dirt"); -+ }}).forEach(key -> { -+ Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(key.toString())); -+ if (block != Blocks.AIR) shovelTurnsBlockToGrassPath.add(block); -+ }); -+ } -+ -+ public boolean silkTouchEnabled = false; -+ public String silkTouchSpawnerName = "Monster Spawner"; -+ public List silkTouchSpawnerLore = new ArrayList<>(); -+ public List silkTouchTools = new ArrayList<>(); -+ public int minimumSilkTouchSpawnerRequire = 1; -+ private void silkTouchSettings() { -+ if (PurpurConfig.version < 21) { -+ String oldName = getString("gameplay-mechanics.silk-touch.spawner-name", silkTouchSpawnerName); -+ set("gameplay-mechanics.silk-touch.spawner-name", "" + ChatColor.toMM(oldName.replace("{mob}", ""))); -+ List list = new ArrayList<>(); -+ getList("gameplay-mechanics.silk-touch.spawner-lore", List.of("Spawns a ")) -+ .forEach(line -> list.add("" + ChatColor.toMM(line.toString().replace("{mob}", "")))); -+ set("gameplay-mechanics.silk-touch.spawner-lore", list); -+ } -+ silkTouchEnabled = getBoolean("gameplay-mechanics.silk-touch.enabled", silkTouchEnabled); -+ silkTouchSpawnerName = getString("gameplay-mechanics.silk-touch.spawner-name", silkTouchSpawnerName); -+ minimumSilkTouchSpawnerRequire = getInt("gameplay-mechanics.silk-touch.minimal-level", minimumSilkTouchSpawnerRequire); -+ silkTouchSpawnerLore.clear(); -+ getList("gameplay-mechanics.silk-touch.spawner-lore", List.of("Spawns a ")) -+ .forEach(line -> silkTouchSpawnerLore.add(line.toString())); -+ silkTouchTools.clear(); -+ getList("gameplay-mechanics.silk-touch.tools", List.of( -+ "minecraft:iron_pickaxe", -+ "minecraft:golden_pickaxe", -+ "minecraft:diamond_pickaxe", -+ "minecraft:netherite_pickaxe" -+ )).forEach(key -> { -+ Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(key.toString())); -+ if (item != Items.AIR) silkTouchTools.add(item); -+ }); -+ } -+ -+ public Map axeStrippables = new HashMap<>(); -+ public Map axeWaxables = new HashMap<>(); -+ public Map axeWeatherables = new HashMap<>(); -+ public Map hoeTillables = new HashMap<>(); -+ public boolean hoeReplantsCrops = false; -+ public boolean hoeReplantsNetherWarts = false; -+ private void toolSettings() { -+ axeStrippables.clear(); -+ axeWaxables.clear(); -+ axeWeatherables.clear(); -+ hoeTillables.clear(); -+ if (PurpurConfig.version < 18) { -+ ConfigurationSection section = PurpurConfig.config.getConfigurationSection("world-settings." + worldName + ".tools.hoe.tilling"); -+ if (section != null) { -+ PurpurConfig.config.set("world-settings." + worldName + ".tools.hoe.tillables", section); -+ PurpurConfig.config.set("world-settings." + worldName + ".tools.hoe.tilling", null); -+ } -+ section = PurpurConfig.config.getConfigurationSection("world-settings.default.tools.hoe.tilling"); -+ if (section != null) { -+ PurpurConfig.config.set("world-settings.default.tools.hoe.tillables", section); -+ PurpurConfig.config.set("world-settings.default.tools.hoe.tilling", null); -+ } -+ } -+ if (PurpurConfig.version < 29) { -+ PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:mangrove_log", Map.of("into", "minecraft:stripped_mangrove_log", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:mangrove_wood", Map.of("into", "minecraft:stripped_mangrove_wood", "drops", new HashMap())); -+ } -+ if (PurpurConfig.version < 32) { -+ PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:cherry_log", Map.of("into", "minecraft:stripped_cherry_log", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:cherry_wood", Map.of("into", "minecraft:stripped_cherry_wood", "drops", new HashMap())); -+ PurpurConfig.config.set("world-settings.default.tools.axe.strippables.minecraft:bamboo_block", Map.of("into", "minecraft:stripped_bamboo_block", "drops", new HashMap())); -+ } -+ getMap("tools.axe.strippables", Map.ofEntries( -+ Map.entry("minecraft:oak_wood", Map.of("into", "minecraft:stripped_oak_wood", "drops", new HashMap())), -+ Map.entry("minecraft:oak_log", Map.of("into", "minecraft:stripped_oak_log", "drops", new HashMap())), -+ Map.entry("minecraft:dark_oak_wood", Map.of("into", "minecraft:stripped_dark_oak_wood", "drops", new HashMap())), -+ Map.entry("minecraft:dark_oak_log", Map.of("into", "minecraft:stripped_dark_oak_log", "drops", new HashMap())), -+ Map.entry("minecraft:acacia_wood", Map.of("into", "minecraft:stripped_acacia_wood", "drops", new HashMap())), -+ Map.entry("minecraft:acacia_log", Map.of("into", "minecraft:stripped_acacia_log", "drops", new HashMap())), -+ Map.entry("minecraft:birch_wood", Map.of("into", "minecraft:stripped_birch_wood", "drops", new HashMap())), -+ Map.entry("minecraft:birch_log", Map.of("into", "minecraft:stripped_birch_log", "drops", new HashMap())), -+ Map.entry("minecraft:jungle_wood", Map.of("into", "minecraft:stripped_jungle_wood", "drops", new HashMap())), -+ Map.entry("minecraft:jungle_log", Map.of("into", "minecraft:stripped_jungle_log", "drops", new HashMap())), -+ Map.entry("minecraft:spruce_wood", Map.of("into", "minecraft:stripped_spruce_wood", "drops", new HashMap())), -+ Map.entry("minecraft:spruce_log", Map.of("into", "minecraft:stripped_spruce_log", "drops", new HashMap())), -+ Map.entry("minecraft:mangrove_wood", Map.of("into", "minecraft:stripped_mangrove_wood", "drops", new HashMap())), -+ Map.entry("minecraft:mangrove_log", Map.of("into", "minecraft:stripped_mangrove_log", "drops", new HashMap())), -+ Map.entry("minecraft:cherry_log", Map.of("into", "minecraft:stripped_cherry_log", "drops", new HashMap())), -+ Map.entry("minecraft:cherry_wood", Map.of("into", "minecraft:stripped_cherry_wood", "drops", new HashMap())), -+ Map.entry("minecraft:bamboo_block", Map.of("into", "minecraft:stripped_bamboo_block", "drops", new HashMap())), -+ Map.entry("minecraft:warped_stem", Map.of("into", "minecraft:stripped_warped_stem", "drops", new HashMap())), -+ Map.entry("minecraft:warped_hyphae", Map.of("into", "minecraft:stripped_warped_hyphae", "drops", new HashMap())), -+ Map.entry("minecraft:crimson_stem", Map.of("into", "minecraft:stripped_crimson_stem", "drops", new HashMap())), -+ Map.entry("minecraft:crimson_hyphae", Map.of("into", "minecraft:stripped_crimson_hyphae", "drops", new HashMap()))) -+ ).forEach((blockId, obj) -> { -+ Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(blockId)); -+ if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.strippables`: " + blockId); return; } -+ if (!(obj instanceof Map map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.strippables." + blockId + "`"); return; } -+ String intoId = (String) map.get("into"); -+ Block into = BuiltInRegistries.BLOCK.get(new ResourceLocation(intoId)); -+ if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.strippables." + blockId + ".into`: " + intoId); return; } -+ Object dropsObj = map.get("drops"); -+ if (!(dropsObj instanceof Map dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.strippables." + blockId + ".drops`"); return; } -+ Map drops = new HashMap<>(); -+ dropsMap.forEach((itemId, chance) -> { -+ Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(itemId.toString())); -+ if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.axe.strippables." + blockId + ".drops`: " + itemId); return; } -+ drops.put(item, (double) chance); -+ }); -+ axeStrippables.put(block, new Strippable(into, drops)); -+ }); -+ getMap("tools.axe.waxables", Map.ofEntries( -+ Map.entry("minecraft:waxed_copper_block", Map.of("into", "minecraft:copper_block", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_exposed_copper", Map.of("into", "minecraft:exposed_copper", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_weathered_copper", Map.of("into", "minecraft:weathered_copper", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_oxidized_copper", Map.of("into", "minecraft:oxidized_copper", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_cut_copper", Map.of("into", "minecraft:cut_copper", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_exposed_cut_copper", Map.of("into", "minecraft:exposed_cut_copper", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_weathered_cut_copper", Map.of("into", "minecraft:weathered_cut_copper", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_oxidized_cut_copper", Map.of("into", "minecraft:oxidized_cut_copper", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_cut_copper_slab", Map.of("into", "minecraft:cut_copper_slab", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_exposed_cut_copper_slab", Map.of("into", "minecraft:exposed_cut_copper_slab", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_weathered_cut_copper_slab", Map.of("into", "minecraft:weathered_cut_copper_slab", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_oxidized_cut_copper_slab", Map.of("into", "minecraft:oxidized_cut_copper_slab", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_cut_copper_stairs", Map.of("into", "minecraft:cut_copper_stairs", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_exposed_cut_copper_stairs", Map.of("into", "minecraft:exposed_cut_copper_stairs", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_weathered_cut_copper_stairs", Map.of("into", "minecraft:weathered_cut_copper_stairs", "drops", new HashMap())), -+ Map.entry("minecraft:waxed_oxidized_cut_copper_stairs", Map.of("into", "minecraft:oxidized_cut_copper_stairs", "drops", new HashMap()))) -+ ).forEach((blockId, obj) -> { -+ Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(blockId)); -+ if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.waxables`: " + blockId); return; } -+ if (!(obj instanceof Map map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.waxables." + blockId + "`"); return; } -+ String intoId = (String) map.get("into"); -+ Block into = BuiltInRegistries.BLOCK.get(new ResourceLocation(intoId)); -+ if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.waxables." + blockId + ".into`: " + intoId); return; } -+ Object dropsObj = map.get("drops"); -+ if (!(dropsObj instanceof Map dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.waxables." + blockId + ".drops`"); return; } -+ Map drops = new HashMap<>(); -+ dropsMap.forEach((itemId, chance) -> { -+ Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(itemId.toString())); -+ if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.axe.waxables." + blockId + ".drops`: " + itemId); return; } -+ drops.put(item, (double) chance); -+ }); -+ axeWaxables.put(block, new Waxable(into, drops)); -+ }); -+ getMap("tools.axe.weatherables", Map.ofEntries( -+ Map.entry("minecraft:exposed_copper", Map.of("into", "minecraft:copper_block", "drops", new HashMap())), -+ Map.entry("minecraft:weathered_copper", Map.of("into", "minecraft:exposed_copper", "drops", new HashMap())), -+ Map.entry("minecraft:oxidized_copper", Map.of("into", "minecraft:weathered_copper", "drops", new HashMap())), -+ Map.entry("minecraft:exposed_cut_copper", Map.of("into", "minecraft:cut_copper", "drops", new HashMap())), -+ Map.entry("minecraft:weathered_cut_copper", Map.of("into", "minecraft:exposed_cut_copper", "drops", new HashMap())), -+ Map.entry("minecraft:oxidized_cut_copper", Map.of("into", "minecraft:weathered_cut_copper", "drops", new HashMap())), -+ Map.entry("minecraft:exposed_cut_copper_slab", Map.of("into", "minecraft:cut_copper_slab", "drops", new HashMap())), -+ Map.entry("minecraft:weathered_cut_copper_slab", Map.of("into", "minecraft:exposed_cut_copper_slab", "drops", new HashMap())), -+ Map.entry("minecraft:oxidized_cut_copper_slab", Map.of("into", "minecraft:weathered_cut_copper_slab", "drops", new HashMap())), -+ Map.entry("minecraft:exposed_cut_copper_stairs", Map.of("into", "minecraft:cut_copper_stairs", "drops", new HashMap())), -+ Map.entry("minecraft:weathered_cut_copper_stairs", Map.of("into", "minecraft:exposed_cut_copper_stairs", "drops", new HashMap())), -+ Map.entry("minecraft:oxidized_cut_copper_stairs", Map.of("into", "minecraft:weathered_cut_copper_stairs", "drops", new HashMap()))) -+ ).forEach((blockId, obj) -> { -+ Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(blockId)); -+ if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.weatherables`: " + blockId); return; } -+ if (!(obj instanceof Map map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.weatherables." + blockId + "`"); return; } -+ String intoId = (String) map.get("into"); -+ Block into = BuiltInRegistries.BLOCK.get(new ResourceLocation(intoId)); -+ if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.axe.weatherables." + blockId + ".into`: " + intoId); return; } -+ Object dropsObj = map.get("drops"); -+ if (!(dropsObj instanceof Map dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.axe.weatherables." + blockId + ".drops`"); return; } -+ Map drops = new HashMap<>(); -+ dropsMap.forEach((itemId, chance) -> { -+ Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(itemId.toString())); -+ if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.axe.weatherables." + blockId + ".drops`: " + itemId); return; } -+ drops.put(item, (double) chance); -+ }); -+ axeWeatherables.put(block, new Weatherable(into, drops)); -+ }); -+ getMap("tools.hoe.tillables", Map.ofEntries( -+ Map.entry("minecraft:grass_block", Map.of("condition", "air_above", "into", "minecraft:farmland", "drops", new HashMap())), -+ Map.entry("minecraft:dirt_path", Map.of("condition", "air_above", "into", "minecraft:farmland", "drops", new HashMap())), -+ Map.entry("minecraft:dirt", Map.of("condition", "air_above", "into", "minecraft:farmland", "drops", new HashMap())), -+ Map.entry("minecraft:coarse_dirt", Map.of("condition", "air_above", "into", "minecraft:dirt", "drops", new HashMap())), -+ Map.entry("minecraft:rooted_dirt", Map.of("condition", "always", "into", "minecraft:dirt", "drops", Map.of("minecraft:hanging_roots", 1.0D)))) -+ ).forEach((blockId, obj) -> { -+ Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(blockId)); -+ if (block == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.hoe.tillables`: " + blockId); return; } -+ if (!(obj instanceof Map map)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.hoe.tillables." + blockId + "`"); return; } -+ String conditionId = (String) map.get("condition"); -+ Tillable.Condition condition = Tillable.Condition.get(conditionId); -+ if (condition == null) { PurpurConfig.log(Level.SEVERE, "Invalid condition for `tools.hoe.tillables." + blockId + ".condition`: " + conditionId); return; } -+ String intoId = (String) map.get("into"); -+ Block into = BuiltInRegistries.BLOCK.get(new ResourceLocation(intoId)); -+ if (into == Blocks.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid block for `tools.hoe.tillables." + blockId + ".into`: " + intoId); return; } -+ Object dropsObj = map.get("drops"); -+ if (!(dropsObj instanceof Map dropsMap)) { PurpurConfig.log(Level.SEVERE, "Invalid yaml for `tools.hoe.tillables." + blockId + ".drops`"); return; } -+ Map drops = new HashMap<>(); -+ dropsMap.forEach((itemId, chance) -> { -+ Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(itemId.toString())); -+ if (item == Items.AIR) { PurpurConfig.log(Level.SEVERE, "Invalid item for `tools.hoe.tillables." + blockId + ".drops`: " + itemId); return; } -+ drops.put(item, (double) chance); -+ }); -+ hoeTillables.put(block, new Tillable(condition, into, drops)); -+ }); -+ hoeReplantsCrops = getBoolean("tools.hoe.replant-crops", hoeReplantsCrops); -+ hoeReplantsNetherWarts = getBoolean("tools.hoe.replant-nether-warts", hoeReplantsNetherWarts); -+ } -+ -+ public boolean anvilAllowColors = false; -+ public boolean anvilColorsUseMiniMessage; -+ public int anvilRepairIngotsAmount = 0; -+ public int anvilDamageObsidianAmount = 0; -+ private void anvilSettings() { -+ anvilAllowColors = getBoolean("blocks.anvil.allow-colors", anvilAllowColors); -+ anvilColorsUseMiniMessage = getBoolean("blocks.anvil.use-mini-message", anvilColorsUseMiniMessage); -+ anvilRepairIngotsAmount = getInt("blocks.anvil.iron-ingots-used-for-repair", anvilRepairIngotsAmount); -+ anvilDamageObsidianAmount = getInt("blocks.anvil.obsidian-used-for-damage", anvilDamageObsidianAmount); -+ } -+ -+ public double azaleaGrowthChance = 0.0D; -+ private void azaleaSettings() { -+ azaleaGrowthChance = getDouble("blocks.azalea.growth-chance", azaleaGrowthChance); -+ } -+ -+ public int beaconLevelOne = 20; -+ public int beaconLevelTwo = 30; -+ public int beaconLevelThree = 40; -+ public int beaconLevelFour = 50; -+ public boolean beaconAllowEffectsWithTintedGlass = false; -+ private void beaconSettings() { -+ beaconLevelOne = getInt("blocks.beacon.effect-range.level-1", beaconLevelOne); -+ beaconLevelTwo = getInt("blocks.beacon.effect-range.level-2", beaconLevelTwo); -+ beaconLevelThree = getInt("blocks.beacon.effect-range.level-3", beaconLevelThree); -+ beaconLevelFour = getInt("blocks.beacon.effect-range.level-4", beaconLevelFour); -+ beaconAllowEffectsWithTintedGlass = getBoolean("blocks.beacon.allow-effects-with-tinted-glass", beaconAllowEffectsWithTintedGlass); -+ } -+ -+ public boolean bedExplode = true; -+ public boolean bedExplodeOnVillagerSleep = false; -+ public double bedExplosionPower = 5.0D; -+ public boolean bedExplosionFire = true; -+ public net.minecraft.world.level.Level.ExplosionInteraction bedExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK; -+ private void bedSettings() { -+ if (PurpurConfig.version < 31) { -+ if ("DESTROY".equals(getString("blocks.bed.explosion-effect", bedExplosionEffect.name()))) { -+ set("blocks.bed.explosion-effect", "BLOCK"); -+ } -+ } -+ bedExplode = getBoolean("blocks.bed.explode", bedExplode); -+ bedExplodeOnVillagerSleep = getBoolean("blocks.bed.explode-on-villager-sleep", bedExplodeOnVillagerSleep); -+ bedExplosionPower = getDouble("blocks.bed.explosion-power", bedExplosionPower); -+ bedExplosionFire = getBoolean("blocks.bed.explosion-fire", bedExplosionFire); -+ try { -+ bedExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.valueOf(getString("blocks.bed.explosion-effect", bedExplosionEffect.name())); -+ } catch (IllegalArgumentException e) { -+ log(Level.SEVERE, "Unknown value for `blocks.bed.explosion-effect`! Using default of `BLOCK`"); -+ bedExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK; -+ } -+ } -+ -+ public Map bigDripleafTiltDelay = new HashMap<>(); -+ private void bigDripleafSettings() { -+ bigDripleafTiltDelay.clear(); -+ getMap("blocks.big_dripleaf.tilt-delay", Map.ofEntries( -+ Map.entry("UNSTABLE", 10), -+ Map.entry("PARTIAL", 10), -+ Map.entry("FULL", 100)) -+ ).forEach((tilt, delay) -> { -+ try { -+ bigDripleafTiltDelay.put(Tilt.valueOf(tilt), (int) delay); -+ } catch (IllegalArgumentException e) { -+ PurpurConfig.log(Level.SEVERE, "Invalid big_dripleaf tilt key: " + tilt); -+ } -+ }); -+ } -+ -+ public boolean buddingAmethystSilkTouch = false; -+ private void buddingAmethystSettings() { -+ buddingAmethystSilkTouch = getBoolean("blocks.budding_amethyst.silk-touch", buddingAmethystSilkTouch); -+ } -+ -+ public boolean cactusBreaksFromSolidNeighbors = true; -+ public boolean cactusAffectedByBonemeal = false; -+ private void cactusSettings() { -+ cactusBreaksFromSolidNeighbors = getBoolean("blocks.cactus.breaks-from-solid-neighbors", cactusBreaksFromSolidNeighbors); -+ cactusAffectedByBonemeal = getBoolean("blocks.cactus.affected-by-bonemeal", cactusAffectedByBonemeal); -+ } -+ -+ public boolean sugarCanAffectedByBonemeal = false; -+ private void sugarCaneSettings() { -+ sugarCanAffectedByBonemeal = getBoolean("blocks.sugar_cane.affected-by-bonemeal", sugarCanAffectedByBonemeal); -+ } -+ -+ public boolean netherWartAffectedByBonemeal = false; -+ private void netherWartSettings() { -+ netherWartAffectedByBonemeal = getBoolean("blocks.nether_wart.affected-by-bonemeal", netherWartAffectedByBonemeal); -+ } -+ -+ public boolean campFireLitWhenPlaced = true; -+ private void campFireSettings() { -+ campFireLitWhenPlaced = getBoolean("blocks.campfire.lit-when-placed", campFireLitWhenPlaced); -+ } -+ -+ public boolean chestOpenWithBlockOnTop = false; -+ private void chestSettings() { -+ chestOpenWithBlockOnTop = getBoolean("blocks.chest.open-with-solid-block-on-top", chestOpenWithBlockOnTop); -+ } -+ -+ public boolean composterBulkProcess = false; -+ private void composterSettings() { -+ composterBulkProcess = getBoolean("blocks.composter.sneak-to-bulk-process", composterBulkProcess); -+ } -+ -+ public boolean coralDieOutsideWater = true; -+ private void coralSettings() { -+ coralDieOutsideWater = getBoolean("blocks.coral.die-outside-water", coralDieOutsideWater); -+ } -+ -+ public boolean dispenserApplyCursedArmor = true; -+ public boolean dispenserPlaceAnvils = false; -+ private void dispenserSettings() { -+ dispenserApplyCursedArmor = getBoolean("blocks.dispenser.apply-cursed-to-armor-slots", dispenserApplyCursedArmor); -+ dispenserPlaceAnvils = getBoolean("blocks.dispenser.place-anvils", dispenserPlaceAnvils); -+ } -+ -+ public List doorRequiresRedstone = new ArrayList<>(); -+ private void doorSettings() { -+ getList("blocks.door.requires-redstone", new ArrayList()).forEach(key -> { -+ Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(key.toString())); -+ if (!block.defaultBlockState().isAir()) { -+ doorRequiresRedstone.add(block); -+ } -+ }); -+ } -+ -+ public boolean dragonEggTeleport = true; -+ private void dragonEggSettings() { -+ dragonEggTeleport = getBoolean("blocks.dragon_egg.teleport", dragonEggTeleport); -+ } -+ -+ public boolean baselessEndCrystalExplode = true; -+ public double baselessEndCrystalExplosionPower = 6.0D; -+ public boolean baselessEndCrystalExplosionFire = false; -+ public net.minecraft.world.level.Level.ExplosionInteraction baselessEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK; -+ public boolean basedEndCrystalExplode = true; -+ public double basedEndCrystalExplosionPower = 6.0D; -+ public boolean basedEndCrystalExplosionFire = false; -+ public net.minecraft.world.level.Level.ExplosionInteraction basedEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK; -+ public int endCrystalCramming = 0; -+ private void endCrystalSettings() { -+ if (PurpurConfig.version < 31) { -+ if ("DESTROY".equals(getString("blocks.end-crystal.baseless.explosion-effect", baselessEndCrystalExplosionEffect.name()))) { -+ set("blocks.end-crystal.baseless.explosion-effect", "BLOCK"); -+ } -+ if ("DESTROY".equals(getString("blocks.end-crystal.base.explosion-effect", basedEndCrystalExplosionEffect.name()))) { -+ set("blocks.end-crystal.base.explosion-effect", "BLOCK"); -+ } -+ } -+ baselessEndCrystalExplode = getBoolean("blocks.end-crystal.baseless.explode", baselessEndCrystalExplode); -+ baselessEndCrystalExplosionPower = getDouble("blocks.end-crystal.baseless.explosion-power", baselessEndCrystalExplosionPower); -+ baselessEndCrystalExplosionFire = getBoolean("blocks.end-crystal.baseless.explosion-fire", baselessEndCrystalExplosionFire); -+ try { -+ baselessEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.valueOf(getString("blocks.end-crystal.baseless.explosion-effect", baselessEndCrystalExplosionEffect.name())); -+ } catch (IllegalArgumentException e) { -+ log(Level.SEVERE, "Unknown value for `blocks.end-crystal.baseless.explosion-effect`! Using default of `BLOCK`"); -+ baselessEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK; -+ } -+ basedEndCrystalExplode = getBoolean("blocks.end-crystal.base.explode", basedEndCrystalExplode); -+ basedEndCrystalExplosionPower = getDouble("blocks.end-crystal.base.explosion-power", basedEndCrystalExplosionPower); -+ basedEndCrystalExplosionFire = getBoolean("blocks.end-crystal.base.explosion-fire", basedEndCrystalExplosionFire); -+ try { -+ basedEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.valueOf(getString("blocks.end-crystal.base.explosion-effect", basedEndCrystalExplosionEffect.name())); -+ } catch (IllegalArgumentException e) { -+ log(Level.SEVERE, "Unknown value for `blocks.end-crystal.base.explosion-effect`! Using default of `BLOCK`"); -+ basedEndCrystalExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK; -+ } -+ endCrystalCramming = getInt("blocks.end-crystal.cramming-amount", endCrystalCramming); -+ } -+ -+ public boolean farmlandBypassMobGriefing = false; -+ public boolean farmlandGetsMoistFromBelow = false; -+ public boolean farmlandAlpha = false; -+ public boolean farmlandTramplingDisabled = false; -+ public boolean farmlandTramplingOnlyPlayers = false; -+ public boolean farmlandTramplingFeatherFalling = false; -+ public double farmlandTrampleHeight = -1D; -+ private void farmlandSettings() { -+ farmlandBypassMobGriefing = getBoolean("blocks.farmland.bypass-mob-griefing", farmlandBypassMobGriefing); -+ farmlandGetsMoistFromBelow = getBoolean("blocks.farmland.gets-moist-from-below", farmlandGetsMoistFromBelow); -+ farmlandAlpha = getBoolean("blocks.farmland.use-alpha-farmland", farmlandAlpha); -+ farmlandTramplingDisabled = getBoolean("blocks.farmland.disable-trampling", farmlandTramplingDisabled); -+ farmlandTramplingOnlyPlayers = getBoolean("blocks.farmland.only-players-trample", farmlandTramplingOnlyPlayers); -+ farmlandTramplingFeatherFalling = getBoolean("blocks.farmland.feather-fall-distance-affects-trampling", farmlandTramplingFeatherFalling); -+ farmlandTrampleHeight = getDouble("blocks.farmland.trample-height", farmlandTrampleHeight); -+ } -+ -+ public double floweringAzaleaGrowthChance = 0.0D; -+ private void floweringAzaleaSettings() { -+ floweringAzaleaGrowthChance = getDouble("blocks.flowering_azalea.growth-chance", floweringAzaleaGrowthChance); -+ } -+ -+ public boolean furnaceUseLavaFromUnderneath = false; -+ private void furnaceSettings() { -+ if (PurpurConfig.version < 17) { -+ furnaceUseLavaFromUnderneath = getBoolean("blocks.furnace.infinite-fuel", furnaceUseLavaFromUnderneath); -+ boolean oldValue = getBoolean("blocks.furnace.infinite-fuel", furnaceUseLavaFromUnderneath); -+ set("blocks.furnace.infinite-fuel", null); -+ set("blocks.furnace.use-lava-from-underneath", oldValue); -+ } -+ furnaceUseLavaFromUnderneath = getBoolean("blocks.furnace.use-lava-from-underneath", furnaceUseLavaFromUnderneath); -+ } -+ -+ public boolean endPortalSafeTeleporting = true; -+ private void endPortalSettings() { -+ endPortalSafeTeleporting = getBoolean("blocks.end_portal.safe-teleporting", endPortalSafeTeleporting); -+ } -+ -+ public boolean mobsSpawnOnPackedIce = true; -+ public boolean mobsSpawnOnBlueIce = true; -+ public boolean snowOnBlueIce = true; -+ private void iceSettings() { -+ mobsSpawnOnPackedIce = getBoolean("blocks.packed_ice.allow-mob-spawns", mobsSpawnOnPackedIce); -+ mobsSpawnOnBlueIce = getBoolean("blocks.blue_ice.allow-mob-spawns", mobsSpawnOnBlueIce); -+ snowOnBlueIce = getBoolean("blocks.blue_ice.allow-snow-formation", snowOnBlueIce); -+ } -+ -+ public int lavaInfiniteRequiredSources = 2; -+ public int lavaSpeedNether = 10; -+ public int lavaSpeedNotNether = 30; -+ private void lavaSettings() { -+ lavaInfiniteRequiredSources = getInt("blocks.lava.infinite-required-sources", lavaInfiniteRequiredSources); -+ lavaSpeedNether = getInt("blocks.lava.speed.nether", lavaSpeedNether); -+ lavaSpeedNotNether = getInt("blocks.lava.speed.not-nether", lavaSpeedNotNether); -+ } -+ -+ public int pistonBlockPushLimit = 12; -+ private void pistonSettings() { -+ pistonBlockPushLimit = getInt("blocks.piston.block-push-limit", pistonBlockPushLimit); -+ } -+ -+ public boolean magmaBlockDamageWhenSneaking = false; -+ public boolean magmaBlockDamageWithFrostWalker = false; -+ private void magmaBlockSettings() { -+ magmaBlockDamageWhenSneaking = getBoolean("blocks.magma-block.damage-when-sneaking", magmaBlockDamageWhenSneaking); -+ magmaBlockDamageWithFrostWalker = getBoolean("blocks.magma-block.damage-with-frost-walker", magmaBlockDamageWithFrostWalker); -+ } -+ -+ public boolean powderSnowBypassMobGriefing = false; -+ private void powderSnowSettings() { -+ powderSnowBypassMobGriefing = getBoolean("blocks.powder_snow.bypass-mob-griefing", powderSnowBypassMobGriefing); -+ } -+ -+ public int railActivationRange = 8; -+ private void railSettings() { -+ railActivationRange = getInt("blocks.powered-rail.activation-range", railActivationRange); -+ } -+ -+ public boolean respawnAnchorExplode = true; -+ public double respawnAnchorExplosionPower = 5.0D; -+ public boolean respawnAnchorExplosionFire = true; -+ public net.minecraft.world.level.Level.ExplosionInteraction respawnAnchorExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK; -+ private void respawnAnchorSettings() { -+ if (PurpurConfig.version < 31) { -+ if ("DESTROY".equals(getString("blocks.respawn_anchor.explosion-effect", respawnAnchorExplosionEffect.name()))) { -+ set("blocks.respawn_anchor.explosion-effect", "BLOCK"); -+ } -+ } -+ respawnAnchorExplode = getBoolean("blocks.respawn_anchor.explode", respawnAnchorExplode); -+ respawnAnchorExplosionPower = getDouble("blocks.respawn_anchor.explosion-power", respawnAnchorExplosionPower); -+ respawnAnchorExplosionFire = getBoolean("blocks.respawn_anchor.explosion-fire", respawnAnchorExplosionFire); -+ try { -+ respawnAnchorExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.valueOf(getString("blocks.respawn_anchor.explosion-effect", respawnAnchorExplosionEffect.name())); -+ } catch (IllegalArgumentException e) { -+ log(Level.SEVERE, "Unknown value for `blocks.respawn_anchor.explosion-effect`! Using default of `BLOCK`"); -+ respawnAnchorExplosionEffect = net.minecraft.world.level.Level.ExplosionInteraction.BLOCK; -+ } -+ } -+ -+ public boolean fixSandDuping = true; -+ private void sandSettings() { -+ fixSandDuping = getBoolean("blocks.sand.fix-duping", fixSandDuping); -+ } -+ -+ public boolean sculkShriekerCanSummonDefault = false; -+ private void sculkShriekerSettings() { -+ sculkShriekerCanSummonDefault = getBoolean("blocks.sculk_shrieker.can-summon-default", sculkShriekerCanSummonDefault); -+ } -+ -+ public boolean shulkerBoxAllowOversizedStacks = false; -+ private void shulkerBoxSettings() { -+ shulkerBoxAllowOversizedStacks = getBoolean("blocks.shulker_box.allow-oversized-stacks", shulkerBoxAllowOversizedStacks); -+ } -+ -+ public boolean signRightClickEdit = false; -+ public boolean signAllowColors = false; -+ private void signSettings() { -+ signRightClickEdit = getBoolean("blocks.sign.right-click-edit", signRightClickEdit); -+ signAllowColors = getBoolean("blocks.sign.allow-colors", signAllowColors); -+ } -+ -+ public boolean slabHalfBreak = false; -+ private void slabSettings() { -+ slabHalfBreak = getBoolean("blocks.slab.break-individual-slabs-when-sneaking", slabHalfBreak); -+ } -+ -+ public boolean spawnerDeactivateByRedstone = false; -+ public boolean spawnerFixMC238526 = false; -+ private void spawnerSettings() { -+ spawnerDeactivateByRedstone = getBoolean("blocks.spawner.deactivate-by-redstone", spawnerDeactivateByRedstone); -+ spawnerFixMC238526 = getBoolean("blocks.spawner.fix-mc-238526", spawnerFixMC238526); -+ } -+ -+ public int spongeAbsorptionArea = 64; -+ public int spongeAbsorptionRadius = 6; -+ public boolean spongeAbsorbsLava = false; -+ private void spongeSettings() { -+ spongeAbsorptionArea = getInt("blocks.sponge.absorption.area", spongeAbsorptionArea); -+ spongeAbsorptionRadius = getInt("blocks.sponge.absorption.radius", spongeAbsorptionRadius); -+ spongeAbsorbsLava = getBoolean("blocks.sponge.absorbs-lava", spongeAbsorbsLava); -+ } -+ -+ public float stonecutterDamage = 0.0F; -+ private void stonecutterSettings() { -+ stonecutterDamage = (float) getDouble("blocks.stonecutter.damage", stonecutterDamage); -+ } -+ -+ public boolean turtleEggsBreakFromExpOrbs = true; -+ public boolean turtleEggsBreakFromItems = true; -+ public boolean turtleEggsBreakFromMinecarts = true; -+ public boolean turtleEggsBypassMobGriefing = false; -+ public int turtleEggsRandomTickCrackChance = 500; -+ public boolean turtleEggsTramplingFeatherFalling = false; -+ private void turtleEggSettings() { -+ turtleEggsBreakFromExpOrbs = getBoolean("blocks.turtle_egg.break-from-exp-orbs", turtleEggsBreakFromExpOrbs); -+ turtleEggsBreakFromItems = getBoolean("blocks.turtle_egg.break-from-items", turtleEggsBreakFromItems); -+ turtleEggsBreakFromMinecarts = getBoolean("blocks.turtle_egg.break-from-minecarts", turtleEggsBreakFromMinecarts); -+ turtleEggsBypassMobGriefing = getBoolean("blocks.turtle_egg.bypass-mob-griefing", turtleEggsBypassMobGriefing); -+ turtleEggsRandomTickCrackChance = getInt("blocks.turtle_egg.random-tick-crack-chance", turtleEggsRandomTickCrackChance); -+ turtleEggsTramplingFeatherFalling = getBoolean("blocks.turtle_egg.feather-fall-distance-affects-trampling", turtleEggsTramplingFeatherFalling); -+ } -+ -+ public int waterInfiniteRequiredSources = 2; -+ private void waterSources() { -+ waterInfiniteRequiredSources = getInt("blocks.water.infinite-required-sources", waterInfiniteRequiredSources); -+ } -+ -+ public boolean babiesAreRidable = true; -+ public boolean untamedTamablesAreRidable = true; -+ public boolean useNightVisionWhenRiding = false; -+ public boolean useDismountsUnderwaterTag = true; -+ private void ridableSettings() { -+ babiesAreRidable = getBoolean("ridable-settings.babies-are-ridable", babiesAreRidable); -+ untamedTamablesAreRidable = getBoolean("ridable-settings.untamed-tamables-are-ridable", untamedTamablesAreRidable); -+ useNightVisionWhenRiding = getBoolean("ridable-settings.use-night-vision", useNightVisionWhenRiding); -+ useDismountsUnderwaterTag = getBoolean("ridable-settings.use-dismounts-underwater-tag", useDismountsUnderwaterTag); -+ } -+ -+ public boolean allayRidable = false; -+ public boolean allayRidableInWater = true; -+ public boolean allayControllable = true; -+ public List allayRespectNBT = new ArrayList<>(); -+ private void allaySettings() { -+ allayRidable = getBoolean("mobs.allay.ridable", allayRidable); -+ allayRidableInWater = getBoolean("mobs.allay.ridable-in-water", allayRidableInWater); -+ allayControllable = getBoolean("mobs.allay.controllable", allayControllable); -+ allayRespectNBT.clear(); -+ getList("mobs.allay.respect-nbt", new ArrayList<>()).forEach(key -> allayRespectNBT.add(key.toString())); -+ } -+ -+ public boolean axolotlRidable = false; -+ public boolean axolotlControllable = true; -+ public double axolotlMaxHealth = 14.0D; -+ public int axolotlBreedingTicks = 6000; -+ public boolean axolotlTakeDamageFromWater = false; -+ public boolean axolotlAlwaysDropExp = false; -+ private void axolotlSettings() { -+ axolotlRidable = getBoolean("mobs.axolotl.ridable", axolotlRidable); -+ axolotlControllable = getBoolean("mobs.axolotl.controllable", axolotlControllable); -+ axolotlMaxHealth = getDouble("mobs.axolotl.attributes.max_health", axolotlMaxHealth); -+ axolotlBreedingTicks = getInt("mobs.axolotl.breeding-delay-ticks", axolotlBreedingTicks); -+ axolotlTakeDamageFromWater = getBoolean("mobs.axolotl.takes-damage-from-water", axolotlTakeDamageFromWater); -+ axolotlAlwaysDropExp = getBoolean("mobs.axolotl.always-drop-exp", axolotlAlwaysDropExp); -+ } -+ -+ public boolean batRidable = false; -+ public boolean batRidableInWater = true; -+ public boolean batControllable = true; -+ public double batMaxY = 320D; -+ public double batMaxHealth = 6.0D; -+ public double batFollowRange = 16.0D; -+ public double batKnockbackResistance = 0.0D; -+ public double batMovementSpeed = 0.6D; -+ public double batFlyingSpeed = 0.6D; -+ public double batArmor = 0.0D; -+ public double batArmorToughness = 0.0D; -+ public double batAttackKnockback = 0.0D; -+ public boolean batTakeDamageFromWater = false; -+ public boolean batAlwaysDropExp = false; -+ private void batSettings() { -+ batRidable = getBoolean("mobs.bat.ridable", batRidable); -+ batRidableInWater = getBoolean("mobs.bat.ridable-in-water", batRidableInWater); -+ batControllable = getBoolean("mobs.bat.controllable", batControllable); -+ batMaxY = getDouble("mobs.bat.ridable-max-y", batMaxY); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.bat.attributes.max-health", batMaxHealth); -+ set("mobs.bat.attributes.max-health", null); -+ set("mobs.bat.attributes.max_health", oldValue); -+ } -+ batMaxHealth = getDouble("mobs.bat.attributes.max_health", batMaxHealth); -+ batTakeDamageFromWater = getBoolean("mobs.bat.takes-damage-from-water", batTakeDamageFromWater); -+ batAlwaysDropExp = getBoolean("mobs.bat.always-drop-exp", batAlwaysDropExp); -+ } -+ -+ public boolean beeRidable = false; -+ public boolean beeRidableInWater = true; -+ public boolean beeControllable = true; -+ public double beeMaxY = 320D; -+ public double beeMaxHealth = 10.0D; -+ public int beeBreedingTicks = 6000; -+ public boolean beeTakeDamageFromWater = false; -+ public boolean beeCanWorkAtNight = false; -+ public boolean beeCanWorkInRain = false; -+ public boolean beeAlwaysDropExp = false; -+ public boolean beeDiesAfterSting = true; -+ private void beeSettings() { -+ beeRidable = getBoolean("mobs.bee.ridable", beeRidable); -+ beeRidableInWater = getBoolean("mobs.bee.ridable-in-water", beeRidableInWater); -+ beeControllable = getBoolean("mobs.bee.controllable", beeControllable); -+ beeMaxY = getDouble("mobs.bee.ridable-max-y", beeMaxY); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.bee.attributes.max-health", beeMaxHealth); -+ set("mobs.bee.attributes.max-health", null); -+ set("mobs.bee.attributes.max_health", oldValue); -+ } -+ beeMaxHealth = getDouble("mobs.bee.attributes.max_health", beeMaxHealth); -+ beeBreedingTicks = getInt("mobs.bee.breeding-delay-ticks", beeBreedingTicks); -+ beeTakeDamageFromWater = getBoolean("mobs.bee.takes-damage-from-water", beeTakeDamageFromWater); -+ beeCanWorkAtNight = getBoolean("mobs.bee.can-work-at-night", beeCanWorkAtNight); -+ beeCanWorkInRain = getBoolean("mobs.bee.can-work-in-rain", beeCanWorkInRain); -+ beeAlwaysDropExp = getBoolean("mobs.bee.always-drop-exp", beeAlwaysDropExp); -+ beeDiesAfterSting = getBoolean("mobs.bee.dies-after-sting", beeDiesAfterSting); -+ } -+ -+ public boolean blazeRidable = false; -+ public boolean blazeRidableInWater = true; -+ public boolean blazeControllable = true; -+ public double blazeMaxY = 320D; -+ public double blazeMaxHealth = 20.0D; -+ public boolean blazeTakeDamageFromWater = true; -+ public boolean blazeAlwaysDropExp = false; -+ private void blazeSettings() { -+ blazeRidable = getBoolean("mobs.blaze.ridable", blazeRidable); -+ blazeRidableInWater = getBoolean("mobs.blaze.ridable-in-water", blazeRidableInWater); -+ blazeControllable = getBoolean("mobs.blaze.controllable", blazeControllable); -+ blazeMaxY = getDouble("mobs.blaze.ridable-max-y", blazeMaxY); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.blaze.attributes.max-health", blazeMaxHealth); -+ set("mobs.blaze.attributes.max-health", null); -+ set("mobs.blaze.attributes.max_health", oldValue); -+ } -+ blazeMaxHealth = getDouble("mobs.blaze.attributes.max_health", blazeMaxHealth); -+ blazeTakeDamageFromWater = getBoolean("mobs.blaze.takes-damage-from-water", blazeTakeDamageFromWater); -+ blazeAlwaysDropExp = getBoolean("mobs.blaze.always-drop-exp", blazeAlwaysDropExp); -+ } -+ -+ public int camelBreedingTicks = 6000; -+ public double camelMaxHealthMin = 32.0D; -+ public double camelMaxHealthMax = 32.0D; -+ public double camelJumpStrengthMin = 0.42D; -+ public double camelJumpStrengthMax = 0.42D; -+ public double camelMovementSpeedMin = 0.09D; -+ public double camelMovementSpeedMax = 0.09D; -+ private void camelSettings() { -+ camelMaxHealthMin = getDouble("mobs.camel.attributes.max_health.min", camelMaxHealthMin); -+ camelMaxHealthMax = getDouble("mobs.camel.attributes.max_health.max", camelMaxHealthMax); -+ camelJumpStrengthMin = getDouble("mobs.camel.attributes.jump_strength.min", camelJumpStrengthMin); -+ camelJumpStrengthMax = getDouble("mobs.camel.attributes.jump_strength.max", camelJumpStrengthMax); -+ camelMovementSpeedMin = getDouble("mobs.camel.attributes.movement_speed.min", camelMovementSpeedMin); -+ camelMovementSpeedMax = getDouble("mobs.camel.attributes.movement_speed.max", camelMovementSpeedMax); -+ camelBreedingTicks = getInt("mobs.camel.breeding-delay-ticks", camelBreedingTicks); -+ } -+ -+ public boolean catRidable = false; -+ public boolean catRidableInWater = true; -+ public boolean catControllable = true; -+ public double catMaxHealth = 10.0D; -+ public int catSpawnDelay = 1200; -+ public int catSpawnSwampHutScanRange = 16; -+ public int catSpawnVillageScanRange = 48; -+ public int catBreedingTicks = 6000; -+ public DyeColor catDefaultCollarColor = DyeColor.RED; -+ public boolean catTakeDamageFromWater = false; -+ public boolean catAlwaysDropExp = false; -+ private void catSettings() { -+ catRidable = getBoolean("mobs.cat.ridable", catRidable); -+ catRidableInWater = getBoolean("mobs.cat.ridable-in-water", catRidableInWater); -+ catControllable = getBoolean("mobs.cat.controllable", catControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.cat.attributes.max-health", catMaxHealth); -+ set("mobs.cat.attributes.max-health", null); -+ set("mobs.cat.attributes.max_health", oldValue); -+ } -+ catMaxHealth = getDouble("mobs.cat.attributes.max_health", catMaxHealth); -+ catSpawnDelay = getInt("mobs.cat.spawn-delay", catSpawnDelay); -+ catSpawnSwampHutScanRange = getInt("mobs.cat.scan-range-for-other-cats.swamp-hut", catSpawnSwampHutScanRange); -+ catSpawnVillageScanRange = getInt("mobs.cat.scan-range-for-other-cats.village", catSpawnVillageScanRange); -+ catBreedingTicks = getInt("mobs.cat.breeding-delay-ticks", catBreedingTicks); -+ try { -+ catDefaultCollarColor = DyeColor.valueOf(getString("mobs.cat.default-collar-color", wolfDefaultCollarColor.name())); -+ } catch (IllegalArgumentException ignore) { -+ catDefaultCollarColor = DyeColor.RED; -+ } -+ catTakeDamageFromWater = getBoolean("mobs.cat.takes-damage-from-water", catTakeDamageFromWater); -+ catAlwaysDropExp = getBoolean("mobs.cat.always-drop-exp", catAlwaysDropExp); -+ } -+ -+ public boolean caveSpiderRidable = false; -+ public boolean caveSpiderRidableInWater = true; -+ public boolean caveSpiderControllable = true; -+ public double caveSpiderMaxHealth = 12.0D; -+ public boolean caveSpiderTakeDamageFromWater = false; -+ public boolean caveSpiderAlwaysDropExp = false; -+ private void caveSpiderSettings() { -+ caveSpiderRidable = getBoolean("mobs.cave_spider.ridable", caveSpiderRidable); -+ caveSpiderRidableInWater = getBoolean("mobs.cave_spider.ridable-in-water", caveSpiderRidableInWater); -+ caveSpiderControllable = getBoolean("mobs.cave_spider.controllable", caveSpiderControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.cave_spider.attributes.max-health", caveSpiderMaxHealth); -+ set("mobs.cave_spider.attributes.max-health", null); -+ set("mobs.cave_spider.attributes.max_health", oldValue); -+ } -+ caveSpiderMaxHealth = getDouble("mobs.cave_spider.attributes.max_health", caveSpiderMaxHealth); -+ caveSpiderTakeDamageFromWater = getBoolean("mobs.cave_spider.takes-damage-from-water", caveSpiderTakeDamageFromWater); -+ caveSpiderAlwaysDropExp = getBoolean("mobs.cave_spider.always-drop-exp", caveSpiderAlwaysDropExp); -+ } -+ -+ public boolean chickenRidable = false; -+ public boolean chickenRidableInWater = false; -+ public boolean chickenControllable = true; -+ public double chickenMaxHealth = 4.0D; -+ public boolean chickenRetaliate = false; -+ public int chickenBreedingTicks = 6000; -+ public boolean chickenTakeDamageFromWater = false; -+ public boolean chickenAlwaysDropExp = false; -+ private void chickenSettings() { -+ chickenRidable = getBoolean("mobs.chicken.ridable", chickenRidable); -+ chickenRidableInWater = getBoolean("mobs.chicken.ridable-in-water", chickenRidableInWater); -+ chickenControllable = getBoolean("mobs.chicken.controllable", chickenControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.chicken.attributes.max-health", chickenMaxHealth); -+ set("mobs.chicken.attributes.max-health", null); -+ set("mobs.chicken.attributes.max_health", oldValue); -+ } -+ chickenMaxHealth = getDouble("mobs.chicken.attributes.max_health", chickenMaxHealth); -+ chickenRetaliate = getBoolean("mobs.chicken.retaliate", chickenRetaliate); -+ chickenBreedingTicks = getInt("mobs.chicken.breeding-delay-ticks", chickenBreedingTicks); -+ chickenTakeDamageFromWater = getBoolean("mobs.chicken.takes-damage-from-water", chickenTakeDamageFromWater); -+ chickenAlwaysDropExp = getBoolean("mobs.chicken.always-drop-exp", chickenAlwaysDropExp); -+ } -+ -+ public boolean codRidable = false; -+ public boolean codControllable = true; -+ public double codMaxHealth = 3.0D; -+ public boolean codTakeDamageFromWater = false; -+ public boolean codAlwaysDropExp = false; -+ private void codSettings() { -+ codRidable = getBoolean("mobs.cod.ridable", codRidable); -+ codControllable = getBoolean("mobs.cod.controllable", codControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.cod.attributes.max-health", codMaxHealth); -+ set("mobs.cod.attributes.max-health", null); -+ set("mobs.cod.attributes.max_health", oldValue); -+ } -+ codMaxHealth = getDouble("mobs.cod.attributes.max_health", codMaxHealth); -+ codTakeDamageFromWater = getBoolean("mobs.cod.takes-damage-from-water", codTakeDamageFromWater); -+ codAlwaysDropExp = getBoolean("mobs.cod.always-drop-exp", codAlwaysDropExp); -+ } -+ -+ public boolean cowRidable = false; -+ public boolean cowRidableInWater = true; -+ public boolean cowControllable = true; -+ public double cowMaxHealth = 10.0D; -+ public int cowFeedMushrooms = 0; -+ public int cowBreedingTicks = 6000; -+ public boolean cowTakeDamageFromWater = false; -+ public double cowNaturallyAggressiveToPlayersChance = 0.0D; -+ public double cowNaturallyAggressiveToPlayersDamage = 2.0D; -+ public boolean cowAlwaysDropExp = false; -+ private void cowSettings() { -+ if (PurpurConfig.version < 22) { -+ double oldValue = getDouble("mobs.cow.naturally-aggressive-to-players-chance", cowNaturallyAggressiveToPlayersChance); -+ set("mobs.cow.naturally-aggressive-to-players-chance", null); -+ set("mobs.cow.naturally-aggressive-to-players.chance", oldValue); -+ } -+ cowRidable = getBoolean("mobs.cow.ridable", cowRidable); -+ cowRidableInWater = getBoolean("mobs.cow.ridable-in-water", cowRidableInWater); -+ cowControllable = getBoolean("mobs.cow.controllable", cowControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.cow.attributes.max-health", cowMaxHealth); -+ set("mobs.cow.attributes.max-health", null); -+ set("mobs.cow.attributes.max_health", oldValue); -+ } -+ cowMaxHealth = getDouble("mobs.cow.attributes.max_health", cowMaxHealth); -+ cowFeedMushrooms = getInt("mobs.cow.feed-mushrooms-for-mooshroom", cowFeedMushrooms); -+ cowBreedingTicks = getInt("mobs.cow.breeding-delay-ticks", cowBreedingTicks); -+ cowTakeDamageFromWater = getBoolean("mobs.cow.takes-damage-from-water", cowTakeDamageFromWater); -+ cowNaturallyAggressiveToPlayersChance = getDouble("mobs.cow.naturally-aggressive-to-players.chance", cowNaturallyAggressiveToPlayersChance); -+ cowNaturallyAggressiveToPlayersDamage = getDouble("mobs.cow.naturally-aggressive-to-players.damage", cowNaturallyAggressiveToPlayersDamage); -+ cowAlwaysDropExp = getBoolean("mobs.cow.always-drop-exp", cowAlwaysDropExp); -+ } -+ -+ public boolean creeperRidable = false; -+ public boolean creeperRidableInWater = true; -+ public boolean creeperControllable = true; -+ public double creeperMaxHealth = 20.0D; -+ public double creeperChargedChance = 0.0D; -+ public boolean creeperAllowGriefing = true; -+ public boolean creeperBypassMobGriefing = false; -+ public boolean creeperTakeDamageFromWater = false; -+ public boolean creeperExplodeWhenKilled = false; -+ public boolean creeperHealthRadius = false; -+ public boolean creeperAlwaysDropExp = false; -+ public double creeperHeadVisibilityPercent = 0.5D; -+ public boolean creeperEncircleTarget = false; -+ private void creeperSettings() { -+ creeperRidable = getBoolean("mobs.creeper.ridable", creeperRidable); -+ creeperRidableInWater = getBoolean("mobs.creeper.ridable-in-water", creeperRidableInWater); -+ creeperControllable = getBoolean("mobs.creeper.controllable", creeperControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.creeper.attributes.max-health", creeperMaxHealth); -+ set("mobs.creeper.attributes.max-health", null); -+ set("mobs.creeper.attributes.max_health", oldValue); -+ } -+ creeperMaxHealth = getDouble("mobs.creeper.attributes.max_health", creeperMaxHealth); -+ creeperChargedChance = getDouble("mobs.creeper.naturally-charged-chance", creeperChargedChance); -+ creeperAllowGriefing = getBoolean("mobs.creeper.allow-griefing", creeperAllowGriefing); -+ creeperBypassMobGriefing = getBoolean("mobs.creeper.bypass-mob-griefing", creeperBypassMobGriefing); -+ creeperTakeDamageFromWater = getBoolean("mobs.creeper.takes-damage-from-water", creeperTakeDamageFromWater); -+ creeperExplodeWhenKilled = getBoolean("mobs.creeper.explode-when-killed", creeperExplodeWhenKilled); -+ creeperHealthRadius = getBoolean("mobs.creeper.health-impacts-explosion", creeperHealthRadius); -+ creeperAlwaysDropExp = getBoolean("mobs.creeper.always-drop-exp", creeperAlwaysDropExp); -+ creeperHeadVisibilityPercent = getDouble("mobs.creeper.head-visibility-percent", creeperHeadVisibilityPercent); -+ creeperEncircleTarget = getBoolean("mobs.creeper.encircle-target", creeperEncircleTarget); -+ } -+ -+ public boolean dolphinRidable = false; -+ public boolean dolphinControllable = true; -+ public int dolphinSpitCooldown = 20; -+ public float dolphinSpitSpeed = 1.0F; -+ public float dolphinSpitDamage = 2.0F; -+ public double dolphinMaxHealth = 10.0D; -+ public boolean dolphinDisableTreasureSearching = false; -+ public boolean dolphinTakeDamageFromWater = false; -+ public double dolphinNaturallyAggressiveToPlayersChance = 0.0D; -+ public boolean dolphinAlwaysDropExp = false; -+ private void dolphinSettings() { -+ dolphinRidable = getBoolean("mobs.dolphin.ridable", dolphinRidable); -+ dolphinControllable = getBoolean("mobs.dolphin.controllable", dolphinControllable); -+ dolphinSpitCooldown = getInt("mobs.dolphin.spit.cooldown", dolphinSpitCooldown); -+ dolphinSpitSpeed = (float) getDouble("mobs.dolphin.spit.speed", dolphinSpitSpeed); -+ dolphinSpitDamage = (float) getDouble("mobs.dolphin.spit.damage", dolphinSpitDamage); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.dolphin.attributes.max-health", dolphinMaxHealth); -+ set("mobs.dolphin.attributes.max-health", null); -+ set("mobs.dolphin.attributes.max_health", oldValue); -+ } -+ dolphinMaxHealth = getDouble("mobs.dolphin.attributes.max_health", dolphinMaxHealth); -+ dolphinDisableTreasureSearching = getBoolean("mobs.dolphin.disable-treasure-searching", dolphinDisableTreasureSearching); -+ dolphinTakeDamageFromWater = getBoolean("mobs.dolphin.takes-damage-from-water", dolphinTakeDamageFromWater); -+ dolphinNaturallyAggressiveToPlayersChance = getDouble("mobs.dolphin.naturally-aggressive-to-players-chance", dolphinNaturallyAggressiveToPlayersChance); -+ dolphinAlwaysDropExp = getBoolean("mobs.dolphin.always-drop-exp", dolphinAlwaysDropExp); -+ } -+ -+ public boolean donkeyRidableInWater = false; -+ public double donkeyMaxHealthMin = 15.0D; -+ public double donkeyMaxHealthMax = 30.0D; -+ public double donkeyJumpStrengthMin = 0.5D; -+ public double donkeyJumpStrengthMax = 0.5D; -+ public double donkeyMovementSpeedMin = 0.175D; -+ public double donkeyMovementSpeedMax = 0.175D; -+ public int donkeyBreedingTicks = 6000; -+ public boolean donkeyTakeDamageFromWater = false; -+ public boolean donkeyAlwaysDropExp = false; -+ private void donkeySettings() { -+ donkeyRidableInWater = getBoolean("mobs.donkey.ridable-in-water", donkeyRidableInWater); -+ if (PurpurConfig.version < 10) { -+ double oldMin = getDouble("mobs.donkey.attributes.max-health.min", donkeyMaxHealthMin); -+ double oldMax = getDouble("mobs.donkey.attributes.max-health.max", donkeyMaxHealthMax); -+ set("mobs.donkey.attributes.max-health", null); -+ set("mobs.donkey.attributes.max_health.min", oldMin); -+ set("mobs.donkey.attributes.max_health.max", oldMax); -+ } -+ donkeyMaxHealthMin = getDouble("mobs.donkey.attributes.max_health.min", donkeyMaxHealthMin); -+ donkeyMaxHealthMax = getDouble("mobs.donkey.attributes.max_health.max", donkeyMaxHealthMax); -+ donkeyJumpStrengthMin = getDouble("mobs.donkey.attributes.jump_strength.min", donkeyJumpStrengthMin); -+ donkeyJumpStrengthMax = getDouble("mobs.donkey.attributes.jump_strength.max", donkeyJumpStrengthMax); -+ donkeyMovementSpeedMin = getDouble("mobs.donkey.attributes.movement_speed.min", donkeyMovementSpeedMin); -+ donkeyMovementSpeedMax = getDouble("mobs.donkey.attributes.movement_speed.max", donkeyMovementSpeedMax); -+ donkeyBreedingTicks = getInt("mobs.donkey.breeding-delay-ticks", donkeyBreedingTicks); -+ donkeyTakeDamageFromWater = getBoolean("mobs.donkey.takes-damage-from-water", donkeyTakeDamageFromWater); -+ donkeyAlwaysDropExp = getBoolean("mobs.donkey.always-drop-exp", donkeyAlwaysDropExp); -+ } -+ -+ public boolean drownedRidable = false; -+ public boolean drownedRidableInWater = true; -+ public boolean drownedControllable = true; -+ public double drownedMaxHealth = 20.0D; -+ public double drownedSpawnReinforcements = 0.1D; -+ public boolean drownedJockeyOnlyBaby = true; -+ public double drownedJockeyChance = 0.05D; -+ public boolean drownedJockeyTryExistingChickens = true; -+ public boolean drownedTakeDamageFromWater = false; -+ public boolean drownedBreakDoors = false; -+ public boolean drownedAlwaysDropExp = false; -+ private void drownedSettings() { -+ drownedRidable = getBoolean("mobs.drowned.ridable", drownedRidable); -+ drownedRidableInWater = getBoolean("mobs.drowned.ridable-in-water", drownedRidableInWater); -+ drownedControllable = getBoolean("mobs.drowned.controllable", drownedControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.drowned.attributes.max-health", drownedMaxHealth); -+ set("mobs.drowned.attributes.max-health", null); -+ set("mobs.drowned.attributes.max_health", oldValue); -+ } -+ drownedMaxHealth = getDouble("mobs.drowned.attributes.max_health", drownedMaxHealth); -+ drownedSpawnReinforcements = getDouble("mobs.drowned.attributes.spawn_reinforcements", drownedSpawnReinforcements); -+ drownedJockeyOnlyBaby = getBoolean("mobs.drowned.jockey.only-babies", drownedJockeyOnlyBaby); -+ drownedJockeyChance = getDouble("mobs.drowned.jockey.chance", drownedJockeyChance); -+ drownedJockeyTryExistingChickens = getBoolean("mobs.drowned.jockey.try-existing-chickens", drownedJockeyTryExistingChickens); -+ drownedTakeDamageFromWater = getBoolean("mobs.drowned.takes-damage-from-water", drownedTakeDamageFromWater); -+ drownedBreakDoors = getBoolean("mobs.drowned.can-break-doors", drownedBreakDoors); -+ drownedAlwaysDropExp = getBoolean("mobs.drowned.always-drop-exp", drownedAlwaysDropExp); -+ } -+ -+ public boolean elderGuardianRidable = false; -+ public boolean elderGuardianControllable = true; -+ public double elderGuardianMaxHealth = 80.0D; -+ public boolean elderGuardianTakeDamageFromWater = false; -+ public boolean elderGuardianAlwaysDropExp = false; -+ private void elderGuardianSettings() { -+ elderGuardianRidable = getBoolean("mobs.elder_guardian.ridable", elderGuardianRidable); -+ elderGuardianControllable = getBoolean("mobs.elder_guardian.controllable", elderGuardianControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.elder_guardian.attributes.max-health", elderGuardianMaxHealth); -+ set("mobs.elder_guardian.attributes.max-health", null); -+ set("mobs.elder_guardian.attributes.max_health", oldValue); -+ } -+ elderGuardianMaxHealth = getDouble("mobs.elder_guardian.attributes.max_health", elderGuardianMaxHealth); -+ elderGuardianTakeDamageFromWater = getBoolean("mobs.elder_guardian.takes-damage-from-water", elderGuardianTakeDamageFromWater); -+ elderGuardianAlwaysDropExp = getBoolean("mobs.elder_guardian.always-drop-exp", elderGuardianAlwaysDropExp); -+ } -+ -+ public boolean enchantmentTableLapisPersists = false; -+ private void enchantmentTableSettings() { -+ enchantmentTableLapisPersists = getBoolean("blocks.enchantment-table.lapis-persists", enchantmentTableLapisPersists); -+ } -+ -+ public boolean enderDragonRidable = false; -+ public boolean enderDragonRidableInWater = true; -+ public boolean enderDragonControllable = true; -+ public double enderDragonMaxY = 320D; -+ public double enderDragonMaxHealth = 200.0D; -+ public boolean enderDragonAlwaysDropsFullExp = false; -+ public boolean enderDragonBypassMobGriefing = false; -+ public boolean enderDragonTakeDamageFromWater = false; -+ public boolean enderDragonCanRideVehicles = false; -+ private void enderDragonSettings() { -+ enderDragonRidable = getBoolean("mobs.ender_dragon.ridable", enderDragonRidable); -+ enderDragonRidableInWater = getBoolean("mobs.ender_dragon.ridable-in-water", enderDragonRidableInWater); -+ enderDragonControllable = getBoolean("mobs.ender_dragon.controllable", enderDragonControllable); -+ enderDragonMaxY = getDouble("mobs.ender_dragon.ridable-max-y", enderDragonMaxY); -+ if (PurpurConfig.version < 8) { -+ double oldValue = getDouble("mobs.ender_dragon.max-health", enderDragonMaxHealth); -+ set("mobs.ender_dragon.max-health", null); -+ set("mobs.ender_dragon.attributes.max_health", oldValue); -+ } else if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.ender_dragon.attributes.max-health", enderDragonMaxHealth); -+ set("mobs.ender_dragon.attributes.max-health", null); -+ set("mobs.ender_dragon.attributes.max_health", oldValue); -+ } -+ enderDragonMaxHealth = getDouble("mobs.ender_dragon.attributes.max_health", enderDragonMaxHealth); -+ enderDragonAlwaysDropsFullExp = getBoolean("mobs.ender_dragon.always-drop-full-exp", enderDragonAlwaysDropsFullExp); -+ enderDragonBypassMobGriefing = getBoolean("mobs.ender_dragon.bypass-mob-griefing", enderDragonBypassMobGriefing); -+ enderDragonTakeDamageFromWater = getBoolean("mobs.ender_dragon.takes-damage-from-water", enderDragonTakeDamageFromWater); -+ enderDragonCanRideVehicles = getBoolean("mobs.ender_dragon.can-ride-vehicles", enderDragonCanRideVehicles); -+ } -+ -+ public boolean endermanRidable = false; -+ public boolean endermanRidableInWater = true; -+ public boolean endermanControllable = true; -+ public double endermanMaxHealth = 40.0D; -+ public boolean endermanAllowGriefing = true; -+ public boolean endermanDespawnEvenWithBlock = false; -+ public boolean endermanBypassMobGriefing = false; -+ public boolean endermanTakeDamageFromWater = true; -+ public boolean endermanAggroEndermites = true; -+ public boolean endermanAggroEndermitesOnlyIfPlayerSpawned = false; -+ public boolean endermanIgnorePlayerDragonHead = false; -+ public boolean endermanDisableStareAggro = false; -+ public boolean endermanIgnoreProjectiles = false; -+ public boolean endermanAlwaysDropExp = false; -+ private void endermanSettings() { -+ endermanRidable = getBoolean("mobs.enderman.ridable", endermanRidable); -+ endermanRidableInWater = getBoolean("mobs.enderman.ridable-in-water", endermanRidableInWater); -+ endermanControllable = getBoolean("mobs.enderman.controllable", endermanControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.enderman.attributes.max-health", endermanMaxHealth); -+ set("mobs.enderman.attributes.max-health", null); -+ set("mobs.enderman.attributes.max_health", oldValue); -+ } -+ if (PurpurConfig.version < 15) { -+ // remove old option -+ set("mobs.enderman.aggressive-towards-spawned-endermites", null); -+ } -+ endermanMaxHealth = getDouble("mobs.enderman.attributes.max_health", endermanMaxHealth); -+ endermanAllowGriefing = getBoolean("mobs.enderman.allow-griefing", endermanAllowGriefing); -+ endermanDespawnEvenWithBlock = getBoolean("mobs.enderman.can-despawn-with-held-block", endermanDespawnEvenWithBlock); -+ endermanBypassMobGriefing = getBoolean("mobs.enderman.bypass-mob-griefing", endermanBypassMobGriefing); -+ endermanTakeDamageFromWater = getBoolean("mobs.enderman.takes-damage-from-water", endermanTakeDamageFromWater); -+ endermanAggroEndermites = getBoolean("mobs.enderman.aggressive-towards-endermites", endermanAggroEndermites); -+ endermanAggroEndermitesOnlyIfPlayerSpawned = getBoolean("mobs.enderman.aggressive-towards-endermites-only-spawned-by-player-thrown-ender-pearls", endermanAggroEndermitesOnlyIfPlayerSpawned); -+ endermanIgnorePlayerDragonHead = getBoolean("mobs.enderman.ignore-players-wearing-dragon-head", endermanIgnorePlayerDragonHead); -+ endermanDisableStareAggro = getBoolean("mobs.enderman.disable-player-stare-aggression", endermanDisableStareAggro); -+ endermanIgnoreProjectiles = getBoolean("mobs.enderman.ignore-projectiles", endermanIgnoreProjectiles); -+ endermanAlwaysDropExp = getBoolean("mobs.enderman.always-drop-exp", endermanAlwaysDropExp); -+ } -+ -+ public boolean endermiteRidable = false; -+ public boolean endermiteRidableInWater = true; -+ public boolean endermiteControllable = true; -+ public double endermiteMaxHealth = 8.0D; -+ public boolean endermiteTakeDamageFromWater = false; -+ public boolean endermiteAlwaysDropExp = false; -+ private void endermiteSettings() { -+ endermiteRidable = getBoolean("mobs.endermite.ridable", endermiteRidable); -+ endermiteRidableInWater = getBoolean("mobs.endermite.ridable-in-water", endermiteRidableInWater); -+ endermiteControllable = getBoolean("mobs.endermite.controllable", endermiteControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.endermite.attributes.max-health", endermiteMaxHealth); -+ set("mobs.endermite.attributes.max-health", null); -+ set("mobs.endermite.attributes.max_health", oldValue); -+ } -+ endermiteMaxHealth = getDouble("mobs.endermite.attributes.max_health", endermiteMaxHealth); -+ endermiteTakeDamageFromWater = getBoolean("mobs.endermite.takes-damage-from-water", endermiteTakeDamageFromWater); -+ endermiteAlwaysDropExp = getBoolean("mobs.endermite.always-drop-exp", endermiteAlwaysDropExp); -+ } -+ -+ public boolean evokerRidable = false; -+ public boolean evokerRidableInWater = true; -+ public boolean evokerControllable = true; -+ public double evokerMaxHealth = 24.0D; -+ public boolean evokerBypassMobGriefing = false; -+ public boolean evokerTakeDamageFromWater = false; -+ public boolean evokerAlwaysDropExp = false; -+ private void evokerSettings() { -+ evokerRidable = getBoolean("mobs.evoker.ridable", evokerRidable); -+ evokerRidableInWater = getBoolean("mobs.evoker.ridable-in-water", evokerRidableInWater); -+ evokerControllable = getBoolean("mobs.evoker.controllable", evokerControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.evoker.attributes.max-health", evokerMaxHealth); -+ set("mobs.evoker.attributes.max-health", null); -+ set("mobs.evoker.attributes.max_health", oldValue); -+ } -+ evokerMaxHealth = getDouble("mobs.evoker.attributes.max_health", evokerMaxHealth); -+ evokerBypassMobGriefing = getBoolean("mobs.evoker.bypass-mob-griefing", evokerBypassMobGriefing); -+ evokerTakeDamageFromWater = getBoolean("mobs.evoker.takes-damage-from-water", evokerTakeDamageFromWater); -+ evokerAlwaysDropExp = getBoolean("mobs.evoker.always-drop-exp", evokerAlwaysDropExp); -+ } -+ -+ public boolean foxRidable = false; -+ public boolean foxRidableInWater = true; -+ public boolean foxControllable = true; -+ public double foxMaxHealth = 10.0D; -+ public boolean foxTypeChangesWithTulips = false; -+ public int foxBreedingTicks = 6000; -+ public boolean foxBypassMobGriefing = false; -+ public boolean foxTakeDamageFromWater = false; -+ public boolean foxAlwaysDropExp = false; -+ private void foxSettings() { -+ foxRidable = getBoolean("mobs.fox.ridable", foxRidable); -+ foxRidableInWater = getBoolean("mobs.fox.ridable-in-water", foxRidableInWater); -+ foxControllable = getBoolean("mobs.fox.controllable", foxControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.fox.attributes.max-health", foxMaxHealth); -+ set("mobs.fox.attributes.max-health", null); -+ set("mobs.fox.attributes.max_health", oldValue); -+ } -+ foxMaxHealth = getDouble("mobs.fox.attributes.max_health", foxMaxHealth); -+ foxTypeChangesWithTulips = getBoolean("mobs.fox.tulips-change-type", foxTypeChangesWithTulips); -+ foxBreedingTicks = getInt("mobs.fox.breeding-delay-ticks", foxBreedingTicks); -+ foxBypassMobGriefing = getBoolean("mobs.fox.bypass-mob-griefing", foxBypassMobGriefing); -+ foxTakeDamageFromWater = getBoolean("mobs.fox.takes-damage-from-water", foxTakeDamageFromWater); -+ foxAlwaysDropExp = getBoolean("mobs.fox.always-drop-exp", foxAlwaysDropExp); -+ } -+ -+ public boolean frogRidable = false; -+ public boolean frogRidableInWater = true; -+ public boolean frogControllable = true; -+ public float frogRidableJumpHeight = 0.65F; -+ public int frogBreedingTicks = 6000; -+ private void frogSettings() { -+ frogRidable = getBoolean("mobs.frog.ridable", frogRidable); -+ frogRidableInWater = getBoolean("mobs.frog.ridable-in-water", frogRidableInWater); -+ frogControllable = getBoolean("mobs.frog.controllable", frogControllable); -+ frogRidableJumpHeight = (float) getDouble("mobs.frog.ridable-jump-height", frogRidableJumpHeight); -+ frogBreedingTicks = getInt("mobs.frog.breeding-delay-ticks", frogBreedingTicks); -+ } -+ -+ public boolean ghastRidable = false; -+ public boolean ghastRidableInWater = true; -+ public boolean ghastControllable = true; -+ public double ghastMaxY = 320D; -+ public double ghastMaxHealth = 10.0D; -+ public boolean ghastTakeDamageFromWater = false; -+ public boolean ghastAlwaysDropExp = false; -+ private void ghastSettings() { -+ ghastRidable = getBoolean("mobs.ghast.ridable", ghastRidable); -+ ghastRidableInWater = getBoolean("mobs.ghast.ridable-in-water", ghastRidableInWater); -+ ghastControllable = getBoolean("mobs.ghast.controllable", ghastControllable); -+ ghastMaxY = getDouble("mobs.ghast.ridable-max-y", ghastMaxY); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.ghast.attributes.max-health", ghastMaxHealth); -+ set("mobs.ghast.attributes.max-health", null); -+ set("mobs.ghast.attributes.max_health", oldValue); -+ } -+ ghastMaxHealth = getDouble("mobs.ghast.attributes.max_health", ghastMaxHealth); -+ ghastTakeDamageFromWater = getBoolean("mobs.ghast.takes-damage-from-water", ghastTakeDamageFromWater); -+ ghastAlwaysDropExp = getBoolean("mobs.ghast.always-drop-exp", ghastAlwaysDropExp); -+ } -+ -+ public boolean giantRidable = false; -+ public boolean giantRidableInWater = true; -+ public boolean giantControllable = true; -+ public double giantMovementSpeed = 0.5D; -+ public double giantAttackDamage = 50.0D; -+ public double giantMaxHealth = 100.0D; -+ public float giantStepHeight = 2.0F; -+ public float giantJumpHeight = 1.0F; -+ public boolean giantHaveAI = false; -+ public boolean giantHaveHostileAI = false; -+ public boolean giantTakeDamageFromWater = false; -+ public boolean giantAlwaysDropExp = false; -+ private void giantSettings() { -+ giantRidable = getBoolean("mobs.giant.ridable", giantRidable); -+ giantRidableInWater = getBoolean("mobs.giant.ridable-in-water", giantRidableInWater); -+ giantControllable = getBoolean("mobs.giant.controllable", giantControllable); -+ giantMovementSpeed = getDouble("mobs.giant.movement-speed", giantMovementSpeed); -+ giantAttackDamage = getDouble("mobs.giant.attack-damage", giantAttackDamage); -+ if (PurpurConfig.version < 8) { -+ double oldValue = getDouble("mobs.giant.max-health", giantMaxHealth); -+ set("mobs.giant.max-health", null); -+ set("mobs.giant.attributes.max_health", oldValue); -+ } else if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.giant.attributes.max-health", giantMaxHealth); -+ set("mobs.giant.attributes.max-health", null); -+ set("mobs.giant.attributes.max_health", oldValue); -+ } -+ giantMaxHealth = getDouble("mobs.giant.attributes.max_health", giantMaxHealth); -+ giantStepHeight = (float) getDouble("mobs.giant.step-height", giantStepHeight); -+ giantJumpHeight = (float) getDouble("mobs.giant.jump-height", giantJumpHeight); -+ giantHaveAI = getBoolean("mobs.giant.have-ai", giantHaveAI); -+ giantHaveHostileAI = getBoolean("mobs.giant.have-hostile-ai", giantHaveHostileAI); -+ giantTakeDamageFromWater = getBoolean("mobs.giant.takes-damage-from-water", giantTakeDamageFromWater); -+ giantAlwaysDropExp = getBoolean("mobs.giant.always-drop-exp", giantAlwaysDropExp); -+ } -+ -+ public boolean glowSquidRidable = false; -+ public boolean glowSquidControllable = true; -+ public double glowSquidMaxHealth = 10.0D; -+ public boolean glowSquidsCanFly = false; -+ public boolean glowSquidTakeDamageFromWater = false; -+ public boolean glowSquidAlwaysDropExp = false; -+ public GlowSquidColor.Mode glowSquidColorMode = GlowSquidColor.Mode.RAINBOW; -+ private void glowSquidSettings() { -+ glowSquidRidable = getBoolean("mobs.glow_squid.ridable", glowSquidRidable); -+ glowSquidControllable = getBoolean("mobs.glow_squid.controllable", glowSquidControllable); -+ glowSquidMaxHealth = getDouble("mobs.glow_squid.attributes.max_health", glowSquidMaxHealth); -+ glowSquidsCanFly = getBoolean("mobs.glow_squid.can-fly", glowSquidsCanFly); -+ glowSquidTakeDamageFromWater = getBoolean("mobs.glow_squid.takes-damage-from-water", glowSquidTakeDamageFromWater); -+ glowSquidAlwaysDropExp = getBoolean("mobs.glow_squid.always-drop-exp", glowSquidAlwaysDropExp); -+ glowSquidColorMode = GlowSquidColor.Mode.get(getString("mobs.glow_squid.rainglow-mode", glowSquidColorMode.toString())); -+ } -+ -+ public boolean goatRidable = false; -+ public boolean goatRidableInWater = true; -+ public boolean goatControllable = true; -+ public double goatMaxHealth = 10.0D; -+ public int goatBreedingTicks = 6000; -+ public boolean goatTakeDamageFromWater = false; -+ public boolean goatAlwaysDropExp = false; -+ private void goatSettings() { -+ goatRidable = getBoolean("mobs.goat.ridable", goatRidable); -+ goatRidableInWater = getBoolean("mobs.goat.ridable-in-water", goatRidableInWater); -+ goatControllable = getBoolean("mobs.goat.controllable", goatControllable); -+ goatMaxHealth = getDouble("mobs.goat.attributes.max_health", goatMaxHealth); -+ goatBreedingTicks = getInt("mobs.goat.breeding-delay-ticks", goatBreedingTicks); -+ goatTakeDamageFromWater = getBoolean("mobs.goat.takes-damage-from-water", goatTakeDamageFromWater); -+ goatAlwaysDropExp = getBoolean("mobs.goat.always-drop-exp", goatAlwaysDropExp); -+ } -+ -+ public boolean guardianRidable = false; -+ public boolean guardianControllable = true; -+ public double guardianMaxHealth = 30.0D; -+ public boolean guardianTakeDamageFromWater = false; -+ public boolean guardianAlwaysDropExp = false; -+ private void guardianSettings() { -+ guardianRidable = getBoolean("mobs.guardian.ridable", guardianRidable); -+ guardianControllable = getBoolean("mobs.guardian.controllable", guardianControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.guardian.attributes.max-health", guardianMaxHealth); -+ set("mobs.guardian.attributes.max-health", null); -+ set("mobs.guardian.attributes.max_health", oldValue); -+ } -+ guardianMaxHealth = getDouble("mobs.guardian.attributes.max_health", guardianMaxHealth); -+ guardianTakeDamageFromWater = getBoolean("mobs.guardian.takes-damage-from-water", guardianTakeDamageFromWater); -+ guardianAlwaysDropExp = getBoolean("mobs.guardian.always-drop-exp", guardianAlwaysDropExp); -+ } -+ -+ public boolean forceHalloweenSeason = false; -+ public float chanceHeadHalloweenOnEntity = 0.25F; -+ private void halloweenSetting() { -+ forceHalloweenSeason = getBoolean("gameplay-mechanics.halloween.force", forceHalloweenSeason); -+ chanceHeadHalloweenOnEntity = (float) getDouble("gameplay-mechanics.halloween.head-chance", chanceHeadHalloweenOnEntity); -+ } -+ -+ public boolean hoglinRidable = false; -+ public boolean hoglinRidableInWater = true; -+ public boolean hoglinControllable = true; -+ public double hoglinMaxHealth = 40.0D; -+ public int hoglinBreedingTicks = 6000; -+ public boolean hoglinTakeDamageFromWater = false; -+ public boolean hoglinAlwaysDropExp = false; -+ private void hoglinSettings() { -+ hoglinRidable = getBoolean("mobs.hoglin.ridable", hoglinRidable); -+ hoglinRidableInWater = getBoolean("mobs.hoglin.ridable-in-water", hoglinRidableInWater); -+ hoglinControllable = getBoolean("mobs.hoglin.controllable", hoglinControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.hoglin.attributes.max-health", hoglinMaxHealth); -+ set("mobs.hoglin.attributes.max-health", null); -+ set("mobs.hoglin.attributes.max_health", oldValue); -+ } -+ hoglinMaxHealth = getDouble("mobs.hoglin.attributes.max_health", hoglinMaxHealth); -+ hoglinBreedingTicks = getInt("mobs.hoglin.breeding-delay-ticks", hoglinBreedingTicks); -+ hoglinTakeDamageFromWater = getBoolean("mobs.hoglin.takes-damage-from-water", hoglinTakeDamageFromWater); -+ hoglinAlwaysDropExp = getBoolean("mobs.hoglin.always-drop-exp", hoglinAlwaysDropExp); -+ } -+ -+ public boolean horseRidableInWater = false; -+ public double horseMaxHealthMin = 15.0D; -+ public double horseMaxHealthMax = 30.0D; -+ public double horseJumpStrengthMin = 0.4D; -+ public double horseJumpStrengthMax = 1.0D; -+ public double horseMovementSpeedMin = 0.1125D; -+ public double horseMovementSpeedMax = 0.3375D; -+ public int horseBreedingTicks = 6000; -+ public boolean horseTakeDamageFromWater = false; -+ public boolean horseAlwaysDropExp = false; -+ private void horseSettings() { -+ horseRidableInWater = getBoolean("mobs.horse.ridable-in-water", horseRidableInWater); -+ if (PurpurConfig.version < 10) { -+ double oldMin = getDouble("mobs.horse.attributes.max-health.min", horseMaxHealthMin); -+ double oldMax = getDouble("mobs.horse.attributes.max-health.max", horseMaxHealthMax); -+ set("mobs.horse.attributes.max-health", null); -+ set("mobs.horse.attributes.max_health.min", oldMin); -+ set("mobs.horse.attributes.max_health.max", oldMax); -+ } -+ horseMaxHealthMin = getDouble("mobs.horse.attributes.max_health.min", horseMaxHealthMin); -+ horseMaxHealthMax = getDouble("mobs.horse.attributes.max_health.max", horseMaxHealthMax); -+ horseJumpStrengthMin = getDouble("mobs.horse.attributes.jump_strength.min", horseJumpStrengthMin); -+ horseJumpStrengthMax = getDouble("mobs.horse.attributes.jump_strength.max", horseJumpStrengthMax); -+ horseMovementSpeedMin = getDouble("mobs.horse.attributes.movement_speed.min", horseMovementSpeedMin); -+ horseMovementSpeedMax = getDouble("mobs.horse.attributes.movement_speed.max", horseMovementSpeedMax); -+ horseBreedingTicks = getInt("mobs.horse.breeding-delay-ticks", horseBreedingTicks); -+ horseTakeDamageFromWater = getBoolean("mobs.horse.takes-damage-from-water", horseTakeDamageFromWater); -+ horseAlwaysDropExp = getBoolean("mobs.horse.always-drop-exp", horseAlwaysDropExp); -+ } -+ -+ public boolean huskRidable = false; -+ public boolean huskRidableInWater = true; -+ public boolean huskControllable = true; -+ public double huskMaxHealth = 20.0D; -+ public double huskSpawnReinforcements = 0.1D; -+ public boolean huskJockeyOnlyBaby = true; -+ public double huskJockeyChance = 0.05D; -+ public boolean huskJockeyTryExistingChickens = true; -+ public boolean huskTakeDamageFromWater = false; -+ public boolean huskAlwaysDropExp = false; -+ private void huskSettings() { -+ huskRidable = getBoolean("mobs.husk.ridable", huskRidable); -+ huskRidableInWater = getBoolean("mobs.husk.ridable-in-water", huskRidableInWater); -+ huskControllable = getBoolean("mobs.husk.controllable", huskControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.husk.attributes.max-health", huskMaxHealth); -+ set("mobs.husk.attributes.max-health", null); -+ set("mobs.husk.attributes.max_health", oldValue); -+ } -+ huskMaxHealth = getDouble("mobs.husk.attributes.max_health", huskMaxHealth); -+ huskSpawnReinforcements = getDouble("mobs.husk.attributes.spawn_reinforcements", huskSpawnReinforcements); -+ huskJockeyOnlyBaby = getBoolean("mobs.husk.jockey.only-babies", huskJockeyOnlyBaby); -+ huskJockeyChance = getDouble("mobs.husk.jockey.chance", huskJockeyChance); -+ huskJockeyTryExistingChickens = getBoolean("mobs.husk.jockey.try-existing-chickens", huskJockeyTryExistingChickens); -+ huskTakeDamageFromWater = getBoolean("mobs.husk.takes-damage-from-water", huskTakeDamageFromWater); -+ huskAlwaysDropExp = getBoolean("mobs.husk.always-drop-exp", huskAlwaysDropExp); -+ } -+ -+ public boolean illusionerRidable = false; -+ public boolean illusionerRidableInWater = true; -+ public boolean illusionerControllable = true; -+ public double illusionerMovementSpeed = 0.5D; -+ public double illusionerFollowRange = 18.0D; -+ public double illusionerMaxHealth = 32.0D; -+ public boolean illusionerTakeDamageFromWater = false; -+ public boolean illusionerAlwaysDropExp = false; -+ private void illusionerSettings() { -+ illusionerRidable = getBoolean("mobs.illusioner.ridable", illusionerRidable); -+ illusionerRidableInWater = getBoolean("mobs.illusioner.ridable-in-water", illusionerRidableInWater); -+ illusionerControllable = getBoolean("mobs.illusioner.controllable", illusionerControllable); -+ illusionerMovementSpeed = getDouble("mobs.illusioner.movement-speed", illusionerMovementSpeed); -+ illusionerFollowRange = getDouble("mobs.illusioner.follow-range", illusionerFollowRange); -+ if (PurpurConfig.version < 8) { -+ double oldValue = getDouble("mobs.illusioner.max-health", illusionerMaxHealth); -+ set("mobs.illusioner.max-health", null); -+ set("mobs.illusioner.attributes.max_health", oldValue); -+ } else if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.illusioner.attributes.max-health", illusionerMaxHealth); -+ set("mobs.illusioner.attributes.max-health", null); -+ set("mobs.illusioner.attributes.max_health", oldValue); -+ } -+ illusionerMaxHealth = getDouble("mobs.illusioner.attributes.max_health", illusionerMaxHealth); -+ illusionerTakeDamageFromWater = getBoolean("mobs.illusioner.takes-damage-from-water", illusionerTakeDamageFromWater); -+ illusionerAlwaysDropExp = getBoolean("mobs.illusioner.always-drop-exp", illusionerAlwaysDropExp); -+ } -+ -+ public boolean ironGolemRidable = false; -+ public boolean ironGolemRidableInWater = true; -+ public boolean ironGolemControllable = true; -+ public boolean ironGolemCanSwim = false; -+ public double ironGolemMaxHealth = 100.0D; -+ public boolean ironGolemTakeDamageFromWater = false; -+ public boolean ironGolemPoppyCalm = false; -+ public boolean ironGolemHealCalm = false; -+ public boolean ironGolemAlwaysDropExp = false; -+ private void ironGolemSettings() { -+ ironGolemRidable = getBoolean("mobs.iron_golem.ridable", ironGolemRidable); -+ ironGolemRidableInWater = getBoolean("mobs.iron_golem.ridable-in-water", ironGolemRidableInWater); -+ ironGolemControllable = getBoolean("mobs.iron_golem.controllable", ironGolemControllable); -+ ironGolemCanSwim = getBoolean("mobs.iron_golem.can-swim", ironGolemCanSwim); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.iron_golem.attributes.max-health", ironGolemMaxHealth); -+ set("mobs.iron_golem.attributes.max-health", null); -+ set("mobs.iron_golem.attributes.max_health", oldValue); -+ } -+ ironGolemMaxHealth = getDouble("mobs.iron_golem.attributes.max_health", ironGolemMaxHealth); -+ ironGolemTakeDamageFromWater = getBoolean("mobs.iron_golem.takes-damage-from-water", ironGolemTakeDamageFromWater); -+ ironGolemPoppyCalm = getBoolean("mobs.iron_golem.poppy-calms-anger", ironGolemPoppyCalm); -+ ironGolemHealCalm = getBoolean("mobs.iron_golem.healing-calms-anger", ironGolemHealCalm); -+ ironGolemAlwaysDropExp = getBoolean("mobs.iron_golem.always-drop-exp", ironGolemAlwaysDropExp); -+ } -+ -+ public boolean llamaRidable = false; -+ public boolean llamaRidableInWater = false; -+ public boolean llamaControllable = true; -+ public double llamaMaxHealthMin = 15.0D; -+ public double llamaMaxHealthMax = 30.0D; -+ public double llamaJumpStrengthMin = 0.5D; -+ public double llamaJumpStrengthMax = 0.5D; -+ public double llamaMovementSpeedMin = 0.175D; -+ public double llamaMovementSpeedMax = 0.175D; -+ public int llamaBreedingTicks = 6000; -+ public boolean llamaTakeDamageFromWater = false; -+ public boolean llamaJoinCaravans = true; -+ public boolean llamaAlwaysDropExp = false; -+ private void llamaSettings() { -+ llamaRidable = getBoolean("mobs.llama.ridable", llamaRidable); -+ llamaRidableInWater = getBoolean("mobs.llama.ridable-in-water", llamaRidableInWater); -+ llamaControllable = getBoolean("mobs.llama.controllable", llamaControllable); -+ if (PurpurConfig.version < 10) { -+ double oldMin = getDouble("mobs.llama.attributes.max-health.min", llamaMaxHealthMin); -+ double oldMax = getDouble("mobs.llama.attributes.max-health.max", llamaMaxHealthMax); -+ set("mobs.llama.attributes.max-health", null); -+ set("mobs.llama.attributes.max_health.min", oldMin); -+ set("mobs.llama.attributes.max_health.max", oldMax); -+ } -+ llamaMaxHealthMin = getDouble("mobs.llama.attributes.max_health.min", llamaMaxHealthMin); -+ llamaMaxHealthMax = getDouble("mobs.llama.attributes.max_health.max", llamaMaxHealthMax); -+ llamaJumpStrengthMin = getDouble("mobs.llama.attributes.jump_strength.min", llamaJumpStrengthMin); -+ llamaJumpStrengthMax = getDouble("mobs.llama.attributes.jump_strength.max", llamaJumpStrengthMax); -+ llamaMovementSpeedMin = getDouble("mobs.llama.attributes.movement_speed.min", llamaMovementSpeedMin); -+ llamaMovementSpeedMax = getDouble("mobs.llama.attributes.movement_speed.max", llamaMovementSpeedMax); -+ llamaBreedingTicks = getInt("mobs.llama.breeding-delay-ticks", llamaBreedingTicks); -+ llamaTakeDamageFromWater = getBoolean("mobs.llama.takes-damage-from-water", llamaTakeDamageFromWater); -+ llamaJoinCaravans = getBoolean("mobs.llama.join-caravans", llamaJoinCaravans); -+ llamaAlwaysDropExp = getBoolean("mobs.llama.always-drop-exp", llamaAlwaysDropExp); -+ } -+ -+ public boolean magmaCubeRidable = false; -+ public boolean magmaCubeRidableInWater = true; -+ public boolean magmaCubeControllable = true; -+ public String magmaCubeMaxHealth = "size * size"; -+ public String magmaCubeAttackDamage = "size"; -+ public Map magmaCubeMaxHealthCache = new HashMap<>(); -+ public Map magmaCubeAttackDamageCache = new HashMap<>(); -+ public boolean magmaCubeTakeDamageFromWater = false; -+ public boolean magmaCubeAlwaysDropExp = false; -+ private void magmaCubeSettings() { -+ magmaCubeRidable = getBoolean("mobs.magma_cube.ridable", magmaCubeRidable); -+ magmaCubeRidableInWater = getBoolean("mobs.magma_cube.ridable-in-water", magmaCubeRidableInWater); -+ magmaCubeControllable = getBoolean("mobs.magma_cube.controllable", magmaCubeControllable); -+ if (PurpurConfig.version < 10) { -+ String oldValue = getString("mobs.magma_cube.attributes.max-health", magmaCubeMaxHealth); -+ set("mobs.magma_cube.attributes.max-health", null); -+ set("mobs.magma_cube.attributes.max_health", oldValue); -+ } -+ magmaCubeMaxHealth = getString("mobs.magma_cube.attributes.max_health", magmaCubeMaxHealth); -+ magmaCubeAttackDamage = getString("mobs.magma_cube.attributes.attack_damage", magmaCubeAttackDamage); -+ magmaCubeMaxHealthCache.clear(); -+ magmaCubeAttackDamageCache.clear(); -+ magmaCubeTakeDamageFromWater = getBoolean("mobs.magma_cube.takes-damage-from-water", magmaCubeTakeDamageFromWater); -+ magmaCubeAlwaysDropExp = getBoolean("mobs.magma_cube.always-drop-exp", magmaCubeAlwaysDropExp); -+ } -+ -+ public boolean mooshroomRidable = false; -+ public boolean mooshroomRidableInWater = true; -+ public boolean mooshroomControllable = true; -+ public double mooshroomMaxHealth = 10.0D; -+ public int mooshroomBreedingTicks = 6000; -+ public boolean mooshroomTakeDamageFromWater = false; -+ public boolean mooshroomAlwaysDropExp = false; -+ private void mooshroomSettings() { -+ mooshroomRidable = getBoolean("mobs.mooshroom.ridable", mooshroomRidable); -+ mooshroomRidableInWater = getBoolean("mobs.mooshroom.ridable-in-water", mooshroomRidableInWater); -+ mooshroomControllable = getBoolean("mobs.mooshroom.controllable", mooshroomControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.mooshroom.attributes.max-health", mooshroomMaxHealth); -+ set("mobs.mooshroom.attributes.max-health", null); -+ set("mobs.mooshroom.attributes.max_health", oldValue); -+ } -+ mooshroomMaxHealth = getDouble("mobs.mooshroom.attributes.max_health", mooshroomMaxHealth); -+ mooshroomBreedingTicks = getInt("mobs.mooshroom.breeding-delay-ticks", mooshroomBreedingTicks); -+ mooshroomTakeDamageFromWater = getBoolean("mobs.mooshroom.takes-damage-from-water", mooshroomTakeDamageFromWater); -+ mooshroomAlwaysDropExp = getBoolean("mobs.mooshroom.always-drop-exp", mooshroomAlwaysDropExp); -+ } -+ -+ public boolean muleRidableInWater = false; -+ public double muleMaxHealthMin = 15.0D; -+ public double muleMaxHealthMax = 30.0D; -+ public double muleJumpStrengthMin = 0.5D; -+ public double muleJumpStrengthMax = 0.5D; -+ public double muleMovementSpeedMin = 0.175D; -+ public double muleMovementSpeedMax = 0.175D; -+ public int muleBreedingTicks = 6000; -+ public boolean muleTakeDamageFromWater = false; -+ public boolean muleAlwaysDropExp = false; -+ private void muleSettings() { -+ muleRidableInWater = getBoolean("mobs.mule.ridable-in-water", muleRidableInWater); -+ if (PurpurConfig.version < 10) { -+ double oldMin = getDouble("mobs.mule.attributes.max-health.min", muleMaxHealthMin); -+ double oldMax = getDouble("mobs.mule.attributes.max-health.max", muleMaxHealthMax); -+ set("mobs.mule.attributes.max-health", null); -+ set("mobs.mule.attributes.max_health.min", oldMin); -+ set("mobs.mule.attributes.max_health.max", oldMax); -+ } -+ muleMaxHealthMin = getDouble("mobs.mule.attributes.max_health.min", muleMaxHealthMin); -+ muleMaxHealthMax = getDouble("mobs.mule.attributes.max_health.max", muleMaxHealthMax); -+ muleJumpStrengthMin = getDouble("mobs.mule.attributes.jump_strength.min", muleJumpStrengthMin); -+ muleJumpStrengthMax = getDouble("mobs.mule.attributes.jump_strength.max", muleJumpStrengthMax); -+ muleMovementSpeedMin = getDouble("mobs.mule.attributes.movement_speed.min", muleMovementSpeedMin); -+ muleMovementSpeedMax = getDouble("mobs.mule.attributes.movement_speed.max", muleMovementSpeedMax); -+ muleBreedingTicks = getInt("mobs.mule.breeding-delay-ticks", muleBreedingTicks); -+ muleTakeDamageFromWater = getBoolean("mobs.mule.takes-damage-from-water", muleTakeDamageFromWater); -+ muleAlwaysDropExp = getBoolean("mobs.mule.always-drop-exp", muleAlwaysDropExp); -+ } -+ -+ public boolean ocelotRidable = false; -+ public boolean ocelotRidableInWater = true; -+ public boolean ocelotControllable = true; -+ public double ocelotMaxHealth = 10.0D; -+ public int ocelotBreedingTicks = 6000; -+ public boolean ocelotTakeDamageFromWater = false; -+ public boolean ocelotAlwaysDropExp = false; -+ private void ocelotSettings() { -+ ocelotRidable = getBoolean("mobs.ocelot.ridable", ocelotRidable); -+ ocelotRidableInWater = getBoolean("mobs.ocelot.ridable-in-water", ocelotRidableInWater); -+ ocelotControllable = getBoolean("mobs.ocelot.controllable", ocelotControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.ocelot.attributes.max-health", ocelotMaxHealth); -+ set("mobs.ocelot.attributes.max-health", null); -+ set("mobs.ocelot.attributes.max_health", oldValue); -+ } -+ ocelotMaxHealth = getDouble("mobs.ocelot.attributes.max_health", ocelotMaxHealth); -+ ocelotBreedingTicks = getInt("mobs.ocelot.breeding-delay-ticks", ocelotBreedingTicks); -+ ocelotTakeDamageFromWater = getBoolean("mobs.ocelot.takes-damage-from-water", ocelotTakeDamageFromWater); -+ ocelotAlwaysDropExp = getBoolean("mobs.ocelot.always-drop-exp", ocelotAlwaysDropExp); -+ } -+ -+ public boolean pandaRidable = false; -+ public boolean pandaRidableInWater = true; -+ public boolean pandaControllable = true; -+ public double pandaMaxHealth = 20.0D; -+ public int pandaBreedingTicks = 6000; -+ public boolean pandaTakeDamageFromWater = false; -+ public boolean pandaAlwaysDropExp = false; -+ private void pandaSettings() { -+ pandaRidable = getBoolean("mobs.panda.ridable", pandaRidable); -+ pandaRidableInWater = getBoolean("mobs.panda.ridable-in-water", pandaRidableInWater); -+ pandaControllable = getBoolean("mobs.panda.controllable", pandaControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.panda.attributes.max-health", pandaMaxHealth); -+ set("mobs.panda.attributes.max-health", null); -+ set("mobs.panda.attributes.max_health", oldValue); -+ } -+ pandaMaxHealth = getDouble("mobs.panda.attributes.max_health", pandaMaxHealth); -+ pandaBreedingTicks = getInt("mobs.panda.breeding-delay-ticks", pandaBreedingTicks); -+ pandaTakeDamageFromWater = getBoolean("mobs.panda.takes-damage-from-water", pandaTakeDamageFromWater); -+ pandaAlwaysDropExp = getBoolean("mobs.panda.always-drop-exp", pandaAlwaysDropExp); -+ } -+ -+ public boolean parrotRidable = false; -+ public boolean parrotRidableInWater = true; -+ public boolean parrotControllable = true; -+ public double parrotMaxY = 320D; -+ public double parrotMaxHealth = 6.0D; -+ public boolean parrotTakeDamageFromWater = false; -+ public boolean parrotBreedable = false; -+ public boolean parrotAlwaysDropExp = false; -+ private void parrotSettings() { -+ parrotRidable = getBoolean("mobs.parrot.ridable", parrotRidable); -+ parrotRidableInWater = getBoolean("mobs.parrot.ridable-in-water", parrotRidableInWater); -+ parrotControllable = getBoolean("mobs.parrot.controllable", parrotControllable); -+ parrotMaxY = getDouble("mobs.parrot.ridable-max-y", parrotMaxY); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.parrot.attributes.max-health", parrotMaxHealth); -+ set("mobs.parrot.attributes.max-health", null); -+ set("mobs.parrot.attributes.max_health", oldValue); -+ } -+ parrotMaxHealth = getDouble("mobs.parrot.attributes.max_health", parrotMaxHealth); -+ parrotTakeDamageFromWater = getBoolean("mobs.parrot.takes-damage-from-water", parrotTakeDamageFromWater); -+ parrotBreedable = getBoolean("mobs.parrot.can-breed", parrotBreedable); -+ parrotAlwaysDropExp = getBoolean("mobs.parrot.always-drop-exp", parrotAlwaysDropExp); -+ } -+ -+ public boolean phantomRidable = false; -+ public boolean phantomRidableInWater = true; -+ public boolean phantomControllable = true; -+ public double phantomMaxY = 320D; -+ public float phantomFlameDamage = 1.0F; -+ public int phantomFlameFireTime = 8; -+ public boolean phantomAllowGriefing = false; -+ public String phantomMaxHealth = "20.0"; -+ public String phantomAttackDamage = "6 + size"; -+ public Map phantomMaxHealthCache = new HashMap<>(); -+ public Map phantomAttackDamageCache = new HashMap<>(); -+ public double phantomAttackedByCrystalRadius = 0.0D; -+ public float phantomAttackedByCrystalDamage = 1.0F; -+ public double phantomOrbitCrystalRadius = 0.0D; -+ public int phantomSpawnMinSkyDarkness = 5; -+ public boolean phantomSpawnOnlyAboveSeaLevel = true; -+ public boolean phantomSpawnOnlyWithVisibleSky = true; -+ public double phantomSpawnLocalDifficultyChance = 3.0D; -+ public int phantomSpawnMinPerAttempt = 1; -+ public int phantomSpawnMaxPerAttempt = -1; -+ public int phantomBurnInLight = 0; -+ public boolean phantomIgnorePlayersWithTorch = false; -+ public boolean phantomBurnInDaylight = true; -+ public boolean phantomFlamesOnSwoop = false; -+ public boolean phantomTakeDamageFromWater = false; -+ public boolean phantomAlwaysDropExp = false; -+ public int phantomMinSize = 0; -+ public int phantomMaxSize = 0; -+ private void phantomSettings() { -+ phantomRidable = getBoolean("mobs.phantom.ridable", phantomRidable); -+ phantomRidableInWater = getBoolean("mobs.phantom.ridable-in-water", phantomRidableInWater); -+ phantomControllable = getBoolean("mobs.phantom.controllable", phantomControllable); -+ phantomMaxY = getDouble("mobs.phantom.ridable-max-y", phantomMaxY); -+ phantomFlameDamage = (float) getDouble("mobs.phantom.flames.damage", phantomFlameDamage); -+ phantomFlameFireTime = getInt("mobs.phantom.flames.fire-time", phantomFlameFireTime); -+ phantomAllowGriefing = getBoolean("mobs.phantom.allow-griefing", phantomAllowGriefing); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.phantom.attributes.max-health", Double.parseDouble(phantomMaxHealth)); -+ set("mobs.phantom.attributes.max-health", null); -+ set("mobs.phantom.attributes.max_health", String.valueOf(oldValue)); -+ } -+ if (PurpurConfig.version < 25) { -+ double oldValue = getDouble("mobs.phantom.attributes.max_health", Double.parseDouble(phantomMaxHealth)); -+ set("mobs.phantom.attributes.max_health", String.valueOf(oldValue)); -+ } -+ phantomMaxHealth = getString("mobs.phantom.attributes.max_health", phantomMaxHealth); -+ phantomAttackDamage = getString("mobs.phantom.attributes.attack_damage", phantomAttackDamage); -+ phantomMaxHealthCache.clear(); -+ phantomAttackDamageCache.clear(); -+ phantomAttackedByCrystalRadius = getDouble("mobs.phantom.attacked-by-crystal-range", phantomAttackedByCrystalRadius); -+ phantomAttackedByCrystalDamage = (float) getDouble("mobs.phantom.attacked-by-crystal-damage", phantomAttackedByCrystalDamage); -+ phantomOrbitCrystalRadius = getDouble("mobs.phantom.orbit-crystal-radius", phantomOrbitCrystalRadius); -+ phantomSpawnMinSkyDarkness = getInt("mobs.phantom.spawn.min-sky-darkness", phantomSpawnMinSkyDarkness); -+ phantomSpawnOnlyAboveSeaLevel = getBoolean("mobs.phantom.spawn.only-above-sea-level", phantomSpawnOnlyAboveSeaLevel); -+ phantomSpawnOnlyWithVisibleSky = getBoolean("mobs.phantom.spawn.only-with-visible-sky", phantomSpawnOnlyWithVisibleSky); -+ phantomSpawnLocalDifficultyChance = getDouble("mobs.phantom.spawn.local-difficulty-chance", phantomSpawnLocalDifficultyChance); -+ phantomSpawnMinPerAttempt = getInt("mobs.phantom.spawn.per-attempt.min", phantomSpawnMinPerAttempt); -+ phantomSpawnMaxPerAttempt = getInt("mobs.phantom.spawn.per-attempt.max", phantomSpawnMaxPerAttempt); -+ phantomBurnInLight = getInt("mobs.phantom.burn-in-light", phantomBurnInLight); -+ phantomBurnInDaylight = getBoolean("mobs.phantom.burn-in-daylight", phantomBurnInDaylight); -+ phantomIgnorePlayersWithTorch = getBoolean("mobs.phantom.ignore-players-with-torch", phantomIgnorePlayersWithTorch); -+ phantomFlamesOnSwoop = getBoolean("mobs.phantom.flames-on-swoop", phantomFlamesOnSwoop); -+ phantomTakeDamageFromWater = getBoolean("mobs.phantom.takes-damage-from-water", phantomTakeDamageFromWater); -+ phantomAlwaysDropExp = getBoolean("mobs.phantom.always-drop-exp", phantomAlwaysDropExp); -+ phantomMinSize = Mth.clamp(getInt("mobs.phantom.size.min", phantomMinSize), 0, 64); -+ phantomMaxSize = Mth.clamp(getInt("mobs.phantom.size.max", phantomMaxSize), 0, 64); -+ if (phantomMinSize > phantomMaxSize) { -+ phantomMinSize = phantomMinSize ^ phantomMaxSize; -+ phantomMaxSize = phantomMinSize ^ phantomMaxSize; -+ phantomMinSize = phantomMinSize ^ phantomMaxSize; -+ } -+ } -+ -+ public boolean pigRidable = false; -+ public boolean pigRidableInWater = false; -+ public boolean pigControllable = true; -+ public double pigMaxHealth = 10.0D; -+ public boolean pigGiveSaddleBack = false; -+ public int pigBreedingTicks = 6000; -+ public boolean pigTakeDamageFromWater = false; -+ public boolean pigAlwaysDropExp = false; -+ private void pigSettings() { -+ pigRidable = getBoolean("mobs.pig.ridable", pigRidable); -+ pigRidableInWater = getBoolean("mobs.pig.ridable-in-water", pigRidableInWater); -+ pigControllable = getBoolean("mobs.pig.controllable", pigControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.pig.attributes.max-health", pigMaxHealth); -+ set("mobs.pig.attributes.max-health", null); -+ set("mobs.pig.attributes.max_health", oldValue); -+ } -+ pigMaxHealth = getDouble("mobs.pig.attributes.max_health", pigMaxHealth); -+ pigGiveSaddleBack = getBoolean("mobs.pig.give-saddle-back", pigGiveSaddleBack); -+ pigBreedingTicks = getInt("mobs.pig.breeding-delay-ticks", pigBreedingTicks); -+ pigTakeDamageFromWater = getBoolean("mobs.pig.takes-damage-from-water", pigTakeDamageFromWater); -+ pigAlwaysDropExp = getBoolean("mobs.pig.always-drop-exp", pigAlwaysDropExp); -+ } -+ -+ public boolean piglinRidable = false; -+ public boolean piglinRidableInWater = true; -+ public boolean piglinControllable = true; -+ public double piglinMaxHealth = 16.0D; -+ public boolean piglinBypassMobGriefing = false; -+ public boolean piglinTakeDamageFromWater = false; -+ public int piglinPortalSpawnModifier = 2000; -+ public boolean piglinAlwaysDropExp = false; -+ public double piglinHeadVisibilityPercent = 0.5D; -+ private void piglinSettings() { -+ piglinRidable = getBoolean("mobs.piglin.ridable", piglinRidable); -+ piglinRidableInWater = getBoolean("mobs.piglin.ridable-in-water", piglinRidableInWater); -+ piglinControllable = getBoolean("mobs.piglin.controllable", piglinControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.piglin.attributes.max-health", piglinMaxHealth); -+ set("mobs.piglin.attributes.max-health", null); -+ set("mobs.piglin.attributes.max_health", oldValue); -+ } -+ piglinMaxHealth = getDouble("mobs.piglin.attributes.max_health", piglinMaxHealth); -+ piglinBypassMobGriefing = getBoolean("mobs.piglin.bypass-mob-griefing", piglinBypassMobGriefing); -+ piglinTakeDamageFromWater = getBoolean("mobs.piglin.takes-damage-from-water", piglinTakeDamageFromWater); -+ piglinPortalSpawnModifier = getInt("mobs.piglin.portal-spawn-modifier", piglinPortalSpawnModifier); -+ piglinAlwaysDropExp = getBoolean("mobs.piglin.always-drop-exp", piglinAlwaysDropExp); -+ piglinHeadVisibilityPercent = getDouble("mobs.piglin.head-visibility-percent", piglinHeadVisibilityPercent); -+ } -+ -+ public boolean piglinBruteRidable = false; -+ public boolean piglinBruteRidableInWater = true; -+ public boolean piglinBruteControllable = true; -+ public double piglinBruteMaxHealth = 50.0D; -+ public boolean piglinBruteTakeDamageFromWater = false; -+ public boolean piglinBruteAlwaysDropExp = false; -+ private void piglinBruteSettings() { -+ piglinBruteRidable = getBoolean("mobs.piglin_brute.ridable", piglinBruteRidable); -+ piglinBruteRidableInWater = getBoolean("mobs.piglin_brute.ridable-in-water", piglinBruteRidableInWater); -+ piglinBruteControllable = getBoolean("mobs.piglin_brute.controllable", piglinBruteControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.piglin_brute.attributes.max-health", piglinBruteMaxHealth); -+ set("mobs.piglin_brute.attributes.max-health", null); -+ set("mobs.piglin_brute.attributes.max_health", oldValue); -+ } -+ piglinBruteMaxHealth = getDouble("mobs.piglin_brute.attributes.max_health", piglinBruteMaxHealth); -+ piglinBruteTakeDamageFromWater = getBoolean("mobs.piglin_brute.takes-damage-from-water", piglinBruteTakeDamageFromWater); -+ piglinBruteAlwaysDropExp = getBoolean("mobs.piglin_brute.always-drop-exp", piglinBruteAlwaysDropExp); -+ } -+ -+ public boolean pillagerRidable = false; -+ public boolean pillagerRidableInWater = true; -+ public boolean pillagerControllable = true; -+ public double pillagerMaxHealth = 24.0D; -+ public boolean pillagerBypassMobGriefing = false; -+ public boolean pillagerTakeDamageFromWater = false; -+ public boolean pillagerAlwaysDropExp = false; -+ private void pillagerSettings() { -+ pillagerRidable = getBoolean("mobs.pillager.ridable", pillagerRidable); -+ pillagerRidableInWater = getBoolean("mobs.pillager.ridable-in-water", pillagerRidableInWater); -+ pillagerControllable = getBoolean("mobs.pillager.controllable", pillagerControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.pillager.attributes.max-health", pillagerMaxHealth); -+ set("mobs.pillager.attributes.max-health", null); -+ set("mobs.pillager.attributes.max_health", oldValue); -+ } -+ pillagerMaxHealth = getDouble("mobs.pillager.attributes.max_health", pillagerMaxHealth); -+ pillagerBypassMobGriefing = getBoolean("mobs.pillager.bypass-mob-griefing", pillagerBypassMobGriefing); -+ pillagerTakeDamageFromWater = getBoolean("mobs.pillager.takes-damage-from-water", pillagerTakeDamageFromWater); -+ pillagerAlwaysDropExp = getBoolean("mobs.pillager.always-drop-exp", pillagerAlwaysDropExp); -+ } -+ -+ public boolean polarBearRidable = false; -+ public boolean polarBearRidableInWater = true; -+ public boolean polarBearControllable = true; -+ public double polarBearMaxHealth = 30.0D; -+ public String polarBearBreedableItemString = ""; -+ public Item polarBearBreedableItem = null; -+ public int polarBearBreedingTicks = 6000; -+ public boolean polarBearTakeDamageFromWater = false; -+ public boolean polarBearAlwaysDropExp = false; -+ private void polarBearSettings() { -+ polarBearRidable = getBoolean("mobs.polar_bear.ridable", polarBearRidable); -+ polarBearRidableInWater = getBoolean("mobs.polar_bear.ridable-in-water", polarBearRidableInWater); -+ polarBearControllable = getBoolean("mobs.polar_bear.controllable", polarBearControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.polar_bear.attributes.max-health", polarBearMaxHealth); -+ set("mobs.polar_bear.attributes.max-health", null); -+ set("mobs.polar_bear.attributes.max_health", oldValue); -+ } -+ polarBearMaxHealth = getDouble("mobs.polar_bear.attributes.max_health", polarBearMaxHealth); -+ polarBearBreedableItemString = getString("mobs.polar_bear.breedable-item", polarBearBreedableItemString); -+ Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(polarBearBreedableItemString)); -+ if (item != Items.AIR) polarBearBreedableItem = item; -+ polarBearBreedingTicks = getInt("mobs.polar_bear.breeding-delay-ticks", polarBearBreedingTicks); -+ polarBearTakeDamageFromWater = getBoolean("mobs.polar_bear.takes-damage-from-water", polarBearTakeDamageFromWater); -+ polarBearAlwaysDropExp = getBoolean("mobs.polar_bear.always-drop-exp", polarBearAlwaysDropExp); -+ } -+ -+ public boolean pufferfishRidable = false; -+ public boolean pufferfishControllable = true; -+ public double pufferfishMaxHealth = 3.0D; -+ public boolean pufferfishTakeDamageFromWater = false; -+ public boolean pufferfishAlwaysDropExp = false; -+ private void pufferfishSettings() { -+ pufferfishRidable = getBoolean("mobs.pufferfish.ridable", pufferfishRidable); -+ pufferfishControllable = getBoolean("mobs.pufferfish.controllable", pufferfishControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.pufferfish.attributes.max-health", pufferfishMaxHealth); -+ set("mobs.pufferfish.attributes.max-health", null); -+ set("mobs.pufferfish.attributes.max_health", oldValue); -+ } -+ pufferfishMaxHealth = getDouble("mobs.pufferfish.attributes.max_health", pufferfishMaxHealth); -+ pufferfishTakeDamageFromWater = getBoolean("mobs.pufferfish.takes-damage-from-water", pufferfishTakeDamageFromWater); -+ pufferfishAlwaysDropExp = getBoolean("mobs.pufferfish.always-drop-exp", pufferfishAlwaysDropExp); -+ } -+ -+ public boolean rabbitRidable = false; -+ public boolean rabbitRidableInWater = true; -+ public boolean rabbitControllable = true; -+ public double rabbitMaxHealth = 3.0D; -+ public double rabbitNaturalToast = 0.0D; -+ public double rabbitNaturalKiller = 0.0D; -+ public int rabbitBreedingTicks = 6000; -+ public boolean rabbitBypassMobGriefing = false; -+ public boolean rabbitTakeDamageFromWater = false; -+ public boolean rabbitAlwaysDropExp = false; -+ private void rabbitSettings() { -+ rabbitRidable = getBoolean("mobs.rabbit.ridable", rabbitRidable); -+ rabbitRidableInWater = getBoolean("mobs.rabbit.ridable-in-water", rabbitRidableInWater); -+ rabbitControllable = getBoolean("mobs.rabbit.controllable", rabbitControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.rabbit.attributes.max-health", rabbitMaxHealth); -+ set("mobs.rabbit.attributes.max-health", null); -+ set("mobs.rabbit.attributes.max_health", oldValue); -+ } -+ rabbitMaxHealth = getDouble("mobs.rabbit.attributes.max_health", rabbitMaxHealth); -+ rabbitNaturalToast = getDouble("mobs.rabbit.spawn-toast-chance", rabbitNaturalToast); -+ rabbitNaturalKiller = getDouble("mobs.rabbit.spawn-killer-rabbit-chance", rabbitNaturalKiller); -+ rabbitBreedingTicks = getInt("mobs.rabbit.breeding-delay-ticks", rabbitBreedingTicks); -+ rabbitBypassMobGriefing = getBoolean("mobs.rabbit.bypass-mob-griefing", rabbitBypassMobGriefing); -+ rabbitTakeDamageFromWater = getBoolean("mobs.rabbit.takes-damage-from-water", rabbitTakeDamageFromWater); -+ rabbitAlwaysDropExp = getBoolean("mobs.rabbit.always-drop-exp", rabbitAlwaysDropExp); -+ } -+ -+ public boolean ravagerRidable = false; -+ public boolean ravagerRidableInWater = false; -+ public boolean ravagerControllable = true; -+ public double ravagerMaxHealth = 100.0D; -+ public boolean ravagerBypassMobGriefing = false; -+ public boolean ravagerTakeDamageFromWater = false; -+ public List ravagerGriefableBlocks = new ArrayList<>(); -+ public boolean ravagerAlwaysDropExp = false; -+ private void ravagerSettings() { -+ ravagerRidable = getBoolean("mobs.ravager.ridable", ravagerRidable); -+ ravagerRidableInWater = getBoolean("mobs.ravager.ridable-in-water", ravagerRidableInWater); -+ ravagerControllable = getBoolean("mobs.ravager.controllable", ravagerControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.ravager.attributes.max-health", ravagerMaxHealth); -+ set("mobs.ravager.attributes.max-health", null); -+ set("mobs.ravager.attributes.max_health", oldValue); -+ } -+ ravagerMaxHealth = getDouble("mobs.ravager.attributes.max_health", ravagerMaxHealth); -+ ravagerBypassMobGriefing = getBoolean("mobs.ravager.bypass-mob-griefing", ravagerBypassMobGriefing); -+ ravagerTakeDamageFromWater = getBoolean("mobs.ravager.takes-damage-from-water", ravagerTakeDamageFromWater); -+ getList("mobs.ravager.griefable-blocks", new ArrayList(){{ -+ add("minecraft:oak_leaves"); -+ add("minecraft:spruce_leaves"); -+ add("minecraft:birch_leaves"); -+ add("minecraft:jungle_leaves"); -+ add("minecraft:acacia_leaves"); -+ add("minecraft:dark_oak_leaves"); -+ add("minecraft:beetroots"); -+ add("minecraft:carrots"); -+ add("minecraft:potatoes"); -+ add("minecraft:wheat"); -+ }}).forEach(key -> { -+ Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(key.toString())); -+ if (!block.defaultBlockState().isAir()) { -+ ravagerGriefableBlocks.add(block); -+ } -+ }); -+ ravagerAlwaysDropExp = getBoolean("mobs.ravager.always-drop-exp", ravagerAlwaysDropExp); -+ } -+ -+ public boolean salmonRidable = false; -+ public boolean salmonControllable = true; -+ public double salmonMaxHealth = 3.0D; -+ public boolean salmonTakeDamageFromWater = false; -+ public boolean salmonAlwaysDropExp = false; -+ private void salmonSettings() { -+ salmonRidable = getBoolean("mobs.salmon.ridable", salmonRidable); -+ salmonControllable = getBoolean("mobs.salmon.controllable", salmonControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.salmon.attributes.max-health", salmonMaxHealth); -+ set("mobs.salmon.attributes.max-health", null); -+ set("mobs.salmon.attributes.max_health", oldValue); -+ } -+ salmonMaxHealth = getDouble("mobs.salmon.attributes.max_health", salmonMaxHealth); -+ salmonTakeDamageFromWater = getBoolean("mobs.salmon.takes-damage-from-water", salmonTakeDamageFromWater); -+ salmonAlwaysDropExp = getBoolean("mobs.salmon.always-drop-exp", salmonAlwaysDropExp); -+ } -+ -+ public boolean sheepRidable = false; -+ public boolean sheepRidableInWater = true; -+ public boolean sheepControllable = true; -+ public double sheepMaxHealth = 8.0D; -+ public int sheepBreedingTicks = 6000; -+ public boolean sheepBypassMobGriefing = false; -+ public boolean sheepTakeDamageFromWater = false; -+ public boolean sheepAlwaysDropExp = false; -+ public boolean sheepShearJebRandomColor = false; -+ private void sheepSettings() { -+ sheepRidable = getBoolean("mobs.sheep.ridable", sheepRidable); -+ sheepRidableInWater = getBoolean("mobs.sheep.ridable-in-water", sheepRidableInWater); -+ sheepControllable = getBoolean("mobs.sheep.controllable", sheepControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.sheep.attributes.max-health", sheepMaxHealth); -+ set("mobs.sheep.attributes.max-health", null); -+ set("mobs.sheep.attributes.max_health", oldValue); -+ } -+ sheepMaxHealth = getDouble("mobs.sheep.attributes.max_health", sheepMaxHealth); -+ sheepBreedingTicks = getInt("mobs.sheep.breeding-delay-ticks", sheepBreedingTicks); -+ sheepBypassMobGriefing = getBoolean("mobs.sheep.bypass-mob-griefing", sheepBypassMobGriefing); -+ sheepTakeDamageFromWater = getBoolean("mobs.sheep.takes-damage-from-water", sheepTakeDamageFromWater); -+ sheepAlwaysDropExp = getBoolean("mobs.sheep.always-drop-exp", sheepAlwaysDropExp); -+ sheepShearJebRandomColor = getBoolean("mobs.sheep.jeb-shear-random-color", sheepShearJebRandomColor); -+ } -+ -+ public boolean shulkerRidable = false; -+ public boolean shulkerRidableInWater = true; -+ public boolean shulkerControllable = true; -+ public double shulkerMaxHealth = 30.0D; -+ public boolean shulkerTakeDamageFromWater = false; -+ public float shulkerSpawnFromBulletBaseChance = 1.0F; -+ public boolean shulkerSpawnFromBulletRequireOpenLid = true; -+ public double shulkerSpawnFromBulletNearbyRange = 8.0D; -+ public String shulkerSpawnFromBulletNearbyEquation = "(nearby - 1) / 5.0"; -+ public boolean shulkerSpawnFromBulletRandomColor = false; -+ public boolean shulkerChangeColorWithDye = false; -+ public boolean shulkerAlwaysDropExp = false; -+ private void shulkerSettings() { -+ shulkerRidable = getBoolean("mobs.shulker.ridable", shulkerRidable); -+ shulkerRidableInWater = getBoolean("mobs.shulker.ridable-in-water", shulkerRidableInWater); -+ shulkerControllable = getBoolean("mobs.shulker.controllable", shulkerControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.shulker.attributes.max-health", shulkerMaxHealth); -+ set("mobs.shulker.attributes.max-health", null); -+ set("mobs.shulker.attributes.max_health", oldValue); -+ } -+ shulkerMaxHealth = getDouble("mobs.shulker.attributes.max_health", shulkerMaxHealth); -+ shulkerTakeDamageFromWater = getBoolean("mobs.shulker.takes-damage-from-water", shulkerTakeDamageFromWater); -+ shulkerSpawnFromBulletBaseChance = (float) getDouble("mobs.shulker.spawn-from-bullet.base-chance", shulkerSpawnFromBulletBaseChance); -+ shulkerSpawnFromBulletRequireOpenLid = getBoolean("mobs.shulker.spawn-from-bullet.require-open-lid", shulkerSpawnFromBulletRequireOpenLid); -+ shulkerSpawnFromBulletNearbyRange = getDouble("mobs.shulker.spawn-from-bullet.nearby-range", shulkerSpawnFromBulletNearbyRange); -+ shulkerSpawnFromBulletNearbyEquation = getString("mobs.shulker.spawn-from-bullet.nearby-equation", shulkerSpawnFromBulletNearbyEquation); -+ shulkerSpawnFromBulletRandomColor = getBoolean("mobs.shulker.spawn-from-bullet.random-color", shulkerSpawnFromBulletRandomColor); -+ shulkerChangeColorWithDye = getBoolean("mobs.shulker.change-color-with-dye", shulkerChangeColorWithDye); -+ shulkerAlwaysDropExp = getBoolean("mobs.shulker.always-drop-exp", shulkerAlwaysDropExp); -+ } -+ -+ public boolean silverfishRidable = false; -+ public boolean silverfishRidableInWater = true; -+ public boolean silverfishControllable = true; -+ public double silverfishMaxHealth = 8.0D; -+ public boolean silverfishBypassMobGriefing = false; -+ public boolean silverfishTakeDamageFromWater = false; -+ public boolean silverfishAlwaysDropExp = false; -+ private void silverfishSettings() { -+ silverfishRidable = getBoolean("mobs.silverfish.ridable", silverfishRidable); -+ silverfishRidableInWater = getBoolean("mobs.silverfish.ridable-in-water", silverfishRidableInWater); -+ silverfishControllable = getBoolean("mobs.silverfish.controllable", silverfishControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.silverfish.attributes.max-health", silverfishMaxHealth); -+ set("mobs.silverfish.attributes.max-health", null); -+ set("mobs.silverfish.attributes.max_health", oldValue); -+ } -+ silverfishMaxHealth = getDouble("mobs.silverfish.attributes.max_health", silverfishMaxHealth); -+ silverfishBypassMobGriefing = getBoolean("mobs.silverfish.bypass-mob-griefing", silverfishBypassMobGriefing); -+ silverfishTakeDamageFromWater = getBoolean("mobs.silverfish.takes-damage-from-water", silverfishTakeDamageFromWater); -+ silverfishAlwaysDropExp = getBoolean("mobs.silverfish.always-drop-exp", silverfishAlwaysDropExp); -+ } -+ -+ public boolean skeletonRidable = false; -+ public boolean skeletonRidableInWater = true; -+ public boolean skeletonControllable = true; -+ public double skeletonMaxHealth = 20.0D; -+ public boolean skeletonTakeDamageFromWater = false; -+ public boolean skeletonAlwaysDropExp = false; -+ public double skeletonHeadVisibilityPercent = 0.5D; -+ public int skeletonFeedWitherRoses = 0; -+ public String skeletonBowAccuracy = "14 - difficulty * 4"; -+ public Map skeletonBowAccuracyMap = new HashMap<>(); -+ private void skeletonSettings() { -+ skeletonRidable = getBoolean("mobs.skeleton.ridable", skeletonRidable); -+ skeletonRidableInWater = getBoolean("mobs.skeleton.ridable-in-water", skeletonRidableInWater); -+ skeletonControllable = getBoolean("mobs.skeleton.controllable", skeletonControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.skeleton.attributes.max-health", skeletonMaxHealth); -+ set("mobs.skeleton.attributes.max-health", null); -+ set("mobs.skeleton.attributes.max_health", oldValue); -+ } -+ skeletonMaxHealth = getDouble("mobs.skeleton.attributes.max_health", skeletonMaxHealth); -+ skeletonTakeDamageFromWater = getBoolean("mobs.skeleton.takes-damage-from-water", skeletonTakeDamageFromWater); -+ skeletonAlwaysDropExp = getBoolean("mobs.skeleton.always-drop-exp", skeletonAlwaysDropExp); -+ skeletonHeadVisibilityPercent = getDouble("mobs.skeleton.head-visibility-percent", skeletonHeadVisibilityPercent); -+ skeletonFeedWitherRoses = getInt("mobs.skeleton.feed-wither-roses", skeletonFeedWitherRoses); -+ final String defaultSkeletonBowAccuracy = skeletonBowAccuracy; -+ skeletonBowAccuracy = getString("mobs.skeleton.bow-accuracy", skeletonBowAccuracy); -+ for (int i = 1; i < 4; i++) { -+ final float divergence; -+ try { -+ divergence = ((Number) Entity.scriptEngine.eval("let difficulty = " + i + "; " + skeletonBowAccuracy)).floatValue(); -+ } catch (javax.script.ScriptException e) { -+ e.printStackTrace(); -+ break; -+ } -+ skeletonBowAccuracyMap.put(i, divergence); -+ } -+ } -+ -+ public boolean skeletonHorseRidableInWater = true; -+ public boolean skeletonHorseCanSwim = false; -+ public double skeletonHorseMaxHealthMin = 15.0D; -+ public double skeletonHorseMaxHealthMax = 15.0D; -+ public double skeletonHorseJumpStrengthMin = 0.4D; -+ public double skeletonHorseJumpStrengthMax = 1.0D; -+ public double skeletonHorseMovementSpeedMin = 0.2D; -+ public double skeletonHorseMovementSpeedMax = 0.2D; -+ public boolean skeletonHorseTakeDamageFromWater = false; -+ public boolean skeletonHorseAlwaysDropExp = false; -+ private void skeletonHorseSettings() { -+ skeletonHorseRidableInWater = getBoolean("mobs.skeleton_horse.ridable-in-water", skeletonHorseRidableInWater); -+ skeletonHorseCanSwim = getBoolean("mobs.skeleton_horse.can-swim", skeletonHorseCanSwim); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.skeleton_horse.attributes.max-health", skeletonHorseMaxHealthMin); -+ set("mobs.skeleton_horse.attributes.max-health", null); -+ set("mobs.skeleton_horse.attributes.max_health.min", oldValue); -+ set("mobs.skeleton_horse.attributes.max_health.max", oldValue); -+ } -+ skeletonHorseMaxHealthMin = getDouble("mobs.skeleton_horse.attributes.max_health.min", skeletonHorseMaxHealthMin); -+ skeletonHorseMaxHealthMax = getDouble("mobs.skeleton_horse.attributes.max_health.max", skeletonHorseMaxHealthMax); -+ skeletonHorseJumpStrengthMin = getDouble("mobs.skeleton_horse.attributes.jump_strength.min", skeletonHorseJumpStrengthMin); -+ skeletonHorseJumpStrengthMax = getDouble("mobs.skeleton_horse.attributes.jump_strength.max", skeletonHorseJumpStrengthMax); -+ skeletonHorseMovementSpeedMin = getDouble("mobs.skeleton_horse.attributes.movement_speed.min", skeletonHorseMovementSpeedMin); -+ skeletonHorseMovementSpeedMax = getDouble("mobs.skeleton_horse.attributes.movement_speed.max", skeletonHorseMovementSpeedMax); -+ skeletonHorseTakeDamageFromWater = getBoolean("mobs.skeleton_horse.takes-damage-from-water", skeletonHorseTakeDamageFromWater); -+ skeletonHorseAlwaysDropExp = getBoolean("mobs.skeleton_horse.always-drop-exp", skeletonHorseAlwaysDropExp); -+ } -+ -+ public boolean slimeRidable = false; -+ public boolean slimeRidableInWater = true; -+ public boolean slimeControllable = true; -+ public String slimeMaxHealth = "size * size"; -+ public String slimeAttackDamage = "size"; -+ public Map slimeMaxHealthCache = new HashMap<>(); -+ public Map slimeAttackDamageCache = new HashMap<>(); -+ public boolean slimeTakeDamageFromWater = false; -+ public boolean slimeAlwaysDropExp = false; -+ private void slimeSettings() { -+ slimeRidable = getBoolean("mobs.slime.ridable", slimeRidable); -+ slimeRidableInWater = getBoolean("mobs.slime.ridable-in-water", slimeRidableInWater); -+ slimeControllable = getBoolean("mobs.slime.controllable", slimeControllable); -+ if (PurpurConfig.version < 10) { -+ String oldValue = getString("mobs.slime.attributes.max-health", slimeMaxHealth); -+ set("mobs.slime.attributes.max-health", null); -+ set("mobs.slime.attributes.max_health", oldValue); -+ } -+ slimeMaxHealth = getString("mobs.slime.attributes.max_health", slimeMaxHealth); -+ slimeAttackDamage = getString("mobs.slime.attributes.attack_damage", slimeAttackDamage); -+ slimeMaxHealthCache.clear(); -+ slimeAttackDamageCache.clear(); -+ slimeTakeDamageFromWater = getBoolean("mobs.slime.takes-damage-from-water", slimeTakeDamageFromWater); -+ slimeAlwaysDropExp = getBoolean("mobs.slime.always-drop-exp", slimeAlwaysDropExp); -+ } -+ -+ public boolean snowGolemRidable = false; -+ public boolean snowGolemRidableInWater = true; -+ public boolean snowGolemControllable = true; -+ public boolean snowGolemLeaveTrailWhenRidden = false; -+ public double snowGolemMaxHealth = 4.0D; -+ public boolean snowGolemDropsPumpkin = true; -+ public boolean snowGolemPutPumpkinBack = false; -+ public int snowGolemSnowBallMin = 20; -+ public int snowGolemSnowBallMax = 20; -+ public float snowGolemSnowBallModifier = 10.0F; -+ public double snowGolemAttackDistance = 1.25D; -+ public boolean snowGolemBypassMobGriefing = false; -+ public boolean snowGolemTakeDamageFromWater = true; -+ public boolean snowGolemAlwaysDropExp = false; -+ private void snowGolemSettings() { -+ snowGolemRidable = getBoolean("mobs.snow_golem.ridable", snowGolemRidable); -+ snowGolemRidableInWater = getBoolean("mobs.snow_golem.ridable-in-water", snowGolemRidableInWater); -+ snowGolemControllable = getBoolean("mobs.snow_golem.controllable", snowGolemControllable); -+ snowGolemLeaveTrailWhenRidden = getBoolean("mobs.snow_golem.leave-trail-when-ridden", snowGolemLeaveTrailWhenRidden); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.snow_golem.attributes.max-health", snowGolemMaxHealth); -+ set("mobs.snow_golem.attributes.max-health", null); -+ set("mobs.snow_golem.attributes.max_health", oldValue); -+ } -+ snowGolemMaxHealth = getDouble("mobs.snow_golem.attributes.max_health", snowGolemMaxHealth); -+ snowGolemDropsPumpkin = getBoolean("mobs.snow_golem.drop-pumpkin-when-sheared", snowGolemDropsPumpkin); -+ snowGolemPutPumpkinBack = getBoolean("mobs.snow_golem.pumpkin-can-be-added-back", snowGolemPutPumpkinBack); -+ snowGolemSnowBallMin = getInt("mobs.snow_golem.min-shoot-interval-ticks", snowGolemSnowBallMin); -+ snowGolemSnowBallMax = getInt("mobs.snow_golem.max-shoot-interval-ticks", snowGolemSnowBallMax); -+ snowGolemSnowBallModifier = (float) getDouble("mobs.snow_golem.snow-ball-modifier", snowGolemSnowBallModifier); -+ snowGolemAttackDistance = getDouble("mobs.snow_golem.attack-distance", snowGolemAttackDistance); -+ snowGolemBypassMobGriefing = getBoolean("mobs.snow_golem.bypass-mob-griefing", snowGolemBypassMobGriefing); -+ snowGolemTakeDamageFromWater = getBoolean("mobs.snow_golem.takes-damage-from-water", snowGolemTakeDamageFromWater); -+ snowGolemAlwaysDropExp = getBoolean("mobs.snow_golem.always-drop-exp", snowGolemAlwaysDropExp); -+ } -+ -+ public boolean snifferRidable = false; -+ public boolean snifferRidableInWater = true; -+ public boolean snifferControllable = true; -+ public double snifferMaxHealth = 14.0D; -+ public int snifferBreedingTicks = 6000; -+ private void snifferSettings() { -+ snifferRidable = getBoolean("mobs.sniffer.ridable", snifferRidable); -+ snifferRidableInWater = getBoolean("mobs.sniffer.ridable-in-water", snifferRidableInWater); -+ snifferControllable = getBoolean("mobs.sniffer.controllable", snifferControllable); -+ snifferMaxHealth = getDouble("mobs.sniffer.attributes.max_health", snifferMaxHealth); -+ snifferBreedingTicks = getInt("mobs.sniffer.breeding-delay-ticks", chickenBreedingTicks); -+ } -+ -+ public boolean squidRidable = false; -+ public boolean squidControllable = true; -+ public double squidMaxHealth = 10.0D; -+ public boolean squidImmuneToEAR = true; -+ public double squidOffsetWaterCheck = 0.0D; -+ public boolean squidsCanFly = false; -+ public boolean squidTakeDamageFromWater = false; -+ public boolean squidAlwaysDropExp = false; -+ private void squidSettings() { -+ squidRidable = getBoolean("mobs.squid.ridable", squidRidable); -+ squidControllable = getBoolean("mobs.squid.controllable", squidControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.squid.attributes.max-health", squidMaxHealth); -+ set("mobs.squid.attributes.max-health", null); -+ set("mobs.squid.attributes.max_health", oldValue); -+ } -+ squidMaxHealth = getDouble("mobs.squid.attributes.max_health", squidMaxHealth); -+ squidImmuneToEAR = getBoolean("mobs.squid.immune-to-EAR", squidImmuneToEAR); -+ squidOffsetWaterCheck = getDouble("mobs.squid.water-offset-check", squidOffsetWaterCheck); -+ squidsCanFly = getBoolean("mobs.squid.can-fly", squidsCanFly); -+ squidTakeDamageFromWater = getBoolean("mobs.squid.takes-damage-from-water", squidTakeDamageFromWater); -+ squidAlwaysDropExp = getBoolean("mobs.squid.always-drop-exp", squidAlwaysDropExp); -+ } -+ -+ public boolean spiderRidable = false; -+ public boolean spiderRidableInWater = false; -+ public boolean spiderControllable = true; -+ public double spiderMaxHealth = 16.0D; -+ public boolean spiderTakeDamageFromWater = false; -+ public boolean spiderAlwaysDropExp = false; -+ private void spiderSettings() { -+ spiderRidable = getBoolean("mobs.spider.ridable", spiderRidable); -+ spiderRidableInWater = getBoolean("mobs.spider.ridable-in-water", spiderRidableInWater); -+ spiderControllable = getBoolean("mobs.spider.controllable", spiderControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.spider.attributes.max-health", spiderMaxHealth); -+ set("mobs.spider.attributes.max-health", null); -+ set("mobs.spider.attributes.max_health", oldValue); -+ } -+ spiderMaxHealth = getDouble("mobs.spider.attributes.max_health", spiderMaxHealth); -+ spiderTakeDamageFromWater = getBoolean("mobs.spider.takes-damage-from-water", spiderTakeDamageFromWater); -+ spiderAlwaysDropExp = getBoolean("mobs.spider.always-drop-exp", spiderAlwaysDropExp); -+ } -+ -+ public boolean strayRidable = false; -+ public boolean strayRidableInWater = true; -+ public boolean strayControllable = true; -+ public double strayMaxHealth = 20.0D; -+ public boolean strayTakeDamageFromWater = false; -+ public boolean strayAlwaysDropExp = false; -+ private void straySettings() { -+ strayRidable = getBoolean("mobs.stray.ridable", strayRidable); -+ strayRidableInWater = getBoolean("mobs.stray.ridable-in-water", strayRidableInWater); -+ strayControllable = getBoolean("mobs.stray.controllable", strayControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.stray.attributes.max-health", strayMaxHealth); -+ set("mobs.stray.attributes.max-health", null); -+ set("mobs.stray.attributes.max_health", oldValue); -+ } -+ strayMaxHealth = getDouble("mobs.stray.attributes.max_health", strayMaxHealth); -+ strayTakeDamageFromWater = getBoolean("mobs.stray.takes-damage-from-water", strayTakeDamageFromWater); -+ strayAlwaysDropExp = getBoolean("mobs.stray.always-drop-exp", strayAlwaysDropExp); -+ } -+ -+ public boolean striderRidable = false; -+ public boolean striderRidableInWater = false; -+ public boolean striderControllable = true; -+ public double striderMaxHealth = 20.0D; -+ public int striderBreedingTicks = 6000; -+ public boolean striderGiveSaddleBack = false; -+ public boolean striderTakeDamageFromWater = true; -+ public boolean striderAlwaysDropExp = false; -+ private void striderSettings() { -+ striderRidable = getBoolean("mobs.strider.ridable", striderRidable); -+ striderRidableInWater = getBoolean("mobs.strider.ridable-in-water", striderRidableInWater); -+ striderControllable = getBoolean("mobs.strider.controllable", striderControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.strider.attributes.max-health", striderMaxHealth); -+ set("mobs.strider.attributes.max-health", null); -+ set("mobs.strider.attributes.max_health", oldValue); -+ } -+ striderMaxHealth = getDouble("mobs.strider.attributes.max_health", striderMaxHealth); -+ striderBreedingTicks = getInt("mobs.strider.breeding-delay-ticks", striderBreedingTicks); -+ striderGiveSaddleBack = getBoolean("mobs.strider.give-saddle-back", striderGiveSaddleBack); -+ striderTakeDamageFromWater = getBoolean("mobs.strider.takes-damage-from-water", striderTakeDamageFromWater); -+ striderAlwaysDropExp = getBoolean("mobs.strider.always-drop-exp", striderAlwaysDropExp); -+ } -+ -+ public boolean tadpoleRidable = false; -+ public boolean tadpoleRidableInWater = true; -+ public boolean tadpoleControllable = true; -+ private void tadpoleSettings() { -+ tadpoleRidable = getBoolean("mobs.tadpole.ridable", tadpoleRidable); -+ tadpoleRidableInWater = getBoolean("mobs.tadpole.ridable-in-water", tadpoleRidableInWater); -+ tadpoleControllable = getBoolean("mobs.tadpole.controllable", tadpoleControllable); -+ } -+ -+ public boolean traderLlamaRidable = false; -+ public boolean traderLlamaRidableInWater = false; -+ public boolean traderLlamaControllable = true; -+ public double traderLlamaMaxHealthMin = 15.0D; -+ public double traderLlamaMaxHealthMax = 30.0D; -+ public double traderLlamaJumpStrengthMin = 0.5D; -+ public double traderLlamaJumpStrengthMax = 0.5D; -+ public double traderLlamaMovementSpeedMin = 0.175D; -+ public double traderLlamaMovementSpeedMax = 0.175D; -+ public int traderLlamaBreedingTicks = 6000; -+ public boolean traderLlamaTakeDamageFromWater = false; -+ public boolean traderLlamaAlwaysDropExp = false; -+ private void traderLlamaSettings() { -+ traderLlamaRidable = getBoolean("mobs.trader_llama.ridable", traderLlamaRidable); -+ traderLlamaRidableInWater = getBoolean("mobs.trader_llama.ridable-in-water", traderLlamaRidableInWater); -+ traderLlamaControllable = getBoolean("mobs.trader_llama.controllable", traderLlamaControllable); -+ if (PurpurConfig.version < 10) { -+ double oldMin = getDouble("mobs.trader_llama.attributes.max-health.min", traderLlamaMaxHealthMin); -+ double oldMax = getDouble("mobs.trader_llama.attributes.max-health.max", traderLlamaMaxHealthMax); -+ set("mobs.trader_llama.attributes.max-health", null); -+ set("mobs.trader_llama.attributes.max_health.min", oldMin); -+ set("mobs.trader_llama.attributes.max_health.max", oldMax); -+ } -+ traderLlamaMaxHealthMin = getDouble("mobs.trader_llama.attributes.max_health.min", traderLlamaMaxHealthMin); -+ traderLlamaMaxHealthMax = getDouble("mobs.trader_llama.attributes.max_health.max", traderLlamaMaxHealthMax); -+ traderLlamaJumpStrengthMin = getDouble("mobs.trader_llama.attributes.jump_strength.min", traderLlamaJumpStrengthMin); -+ traderLlamaJumpStrengthMax = getDouble("mobs.trader_llama.attributes.jump_strength.max", traderLlamaJumpStrengthMax); -+ traderLlamaMovementSpeedMin = getDouble("mobs.trader_llama.attributes.movement_speed.min", traderLlamaMovementSpeedMin); -+ traderLlamaMovementSpeedMax = getDouble("mobs.trader_llama.attributes.movement_speed.max", traderLlamaMovementSpeedMax); -+ traderLlamaBreedingTicks = getInt("mobs.trader_llama.breeding-delay-ticks", traderLlamaBreedingTicks); -+ traderLlamaTakeDamageFromWater = getBoolean("mobs.trader_llama.takes-damage-from-water", traderLlamaTakeDamageFromWater); -+ traderLlamaAlwaysDropExp = getBoolean("mobs.trader_llama.always-drop-exp", traderLlamaAlwaysDropExp); -+ } -+ -+ public boolean tropicalFishRidable = false; -+ public boolean tropicalFishControllable = true; -+ public double tropicalFishMaxHealth = 3.0D; -+ public boolean tropicalFishTakeDamageFromWater = false; -+ public boolean tropicalFishAlwaysDropExp = false; -+ private void tropicalFishSettings() { -+ tropicalFishRidable = getBoolean("mobs.tropical_fish.ridable", tropicalFishRidable); -+ tropicalFishControllable = getBoolean("mobs.tropical_fish.controllable", tropicalFishControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.tropical_fish.attributes.max-health", tropicalFishMaxHealth); -+ set("mobs.tropical_fish.attributes.max-health", null); -+ set("mobs.tropical_fish.attributes.max_health", oldValue); -+ } -+ tropicalFishMaxHealth = getDouble("mobs.tropical_fish.attributes.max_health", tropicalFishMaxHealth); -+ tropicalFishTakeDamageFromWater = getBoolean("mobs.tropical_fish.takes-damage-from-water", tropicalFishTakeDamageFromWater); -+ tropicalFishAlwaysDropExp = getBoolean("mobs.tropical_fish.always-drop-exp", tropicalFishAlwaysDropExp); -+ } -+ -+ public boolean turtleRidable = false; -+ public boolean turtleRidableInWater = true; -+ public boolean turtleControllable = true; -+ public double turtleMaxHealth = 30.0D; -+ public int turtleBreedingTicks = 6000; -+ public boolean turtleTakeDamageFromWater = false; -+ public boolean turtleAlwaysDropExp = false; -+ private void turtleSettings() { -+ turtleRidable = getBoolean("mobs.turtle.ridable", turtleRidable); -+ turtleRidableInWater = getBoolean("mobs.turtle.ridable-in-water", turtleRidableInWater); -+ turtleControllable = getBoolean("mobs.turtle.controllable", turtleControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.turtle.attributes.max-health", turtleMaxHealth); -+ set("mobs.turtle.attributes.max-health", null); -+ set("mobs.turtle.attributes.max_health", oldValue); -+ } -+ turtleMaxHealth = getDouble("mobs.turtle.attributes.max_health", turtleMaxHealth); -+ turtleBreedingTicks = getInt("mobs.turtle.breeding-delay-ticks", turtleBreedingTicks); -+ turtleTakeDamageFromWater = getBoolean("mobs.turtle.takes-damage-from-water", turtleTakeDamageFromWater); -+ turtleAlwaysDropExp = getBoolean("mobs.turtle.always-drop-exp", turtleAlwaysDropExp); -+ } -+ -+ public boolean vexRidable = false; -+ public boolean vexRidableInWater = true; -+ public boolean vexControllable = true; -+ public double vexMaxY = 320D; -+ public double vexMaxHealth = 14.0D; -+ public boolean vexTakeDamageFromWater = false; -+ public boolean vexAlwaysDropExp = false; -+ private void vexSettings() { -+ vexRidable = getBoolean("mobs.vex.ridable", vexRidable); -+ vexRidableInWater = getBoolean("mobs.vex.ridable-in-water", vexRidableInWater); -+ vexControllable = getBoolean("mobs.vex.controllable", vexControllable); -+ vexMaxY = getDouble("mobs.vex.ridable-max-y", vexMaxY); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.vex.attributes.max-health", vexMaxHealth); -+ set("mobs.vex.attributes.max-health", null); -+ set("mobs.vex.attributes.max_health", oldValue); -+ } -+ vexMaxHealth = getDouble("mobs.vex.attributes.max_health", vexMaxHealth); -+ vexTakeDamageFromWater = getBoolean("mobs.vex.takes-damage-from-water", vexTakeDamageFromWater); -+ vexAlwaysDropExp = getBoolean("mobs.vex.always-drop-exp", vexAlwaysDropExp); -+ } -+ -+ public boolean villagerRidable = false; -+ public boolean villagerRidableInWater = true; -+ public boolean villagerControllable = true; -+ public double villagerMaxHealth = 20.0D; -+ public boolean villagerFollowEmeraldBlock = false; -+ public boolean villagerCanBeLeashed = false; -+ public boolean villagerCanBreed = true; -+ public int villagerBreedingTicks = 6000; -+ public boolean villagerClericsFarmWarts = false; -+ public boolean villagerClericFarmersThrowWarts = true; -+ public boolean villagerBypassMobGriefing = false; -+ public boolean villagerTakeDamageFromWater = false; -+ public boolean villagerAllowTrading = true; -+ public boolean villagerAlwaysDropExp = false; -+ public int villagerMinimumDemand = 0; -+ public boolean villagerLobotomizeEnabled = false; -+ public int villagerLobotomizeCheckInterval = 100; -+ public boolean villagerDisplayTradeItem = true; -+ public int villagerSpawnIronGolemRadius = 0; -+ public int villagerSpawnIronGolemLimit = 0; -+ private void villagerSettings() { -+ villagerRidable = getBoolean("mobs.villager.ridable", villagerRidable); -+ villagerRidableInWater = getBoolean("mobs.villager.ridable-in-water", villagerRidableInWater); -+ villagerControllable = getBoolean("mobs.villager.controllable", villagerControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.villager.attributes.max-health", villagerMaxHealth); -+ set("mobs.villager.attributes.max-health", null); -+ set("mobs.villager.attributes.max_health", oldValue); -+ } -+ villagerMaxHealth = getDouble("mobs.villager.attributes.max_health", villagerMaxHealth); -+ villagerFollowEmeraldBlock = getBoolean("mobs.villager.follow-emerald-blocks", villagerFollowEmeraldBlock); -+ villagerCanBeLeashed = getBoolean("mobs.villager.can-be-leashed", villagerCanBeLeashed); -+ villagerCanBreed = getBoolean("mobs.villager.can-breed", villagerCanBreed); -+ villagerBreedingTicks = getInt("mobs.villager.breeding-delay-ticks", villagerBreedingTicks); -+ villagerClericsFarmWarts = getBoolean("mobs.villager.clerics-farm-warts", villagerClericsFarmWarts); -+ villagerClericFarmersThrowWarts = getBoolean("mobs.villager.cleric-wart-farmers-throw-warts-at-villagers", villagerClericFarmersThrowWarts); -+ villagerBypassMobGriefing = getBoolean("mobs.villager.bypass-mob-griefing", villagerBypassMobGriefing); -+ villagerTakeDamageFromWater = getBoolean("mobs.villager.takes-damage-from-water", villagerTakeDamageFromWater); -+ villagerAllowTrading = getBoolean("mobs.villager.allow-trading", villagerAllowTrading); -+ villagerAlwaysDropExp = getBoolean("mobs.villager.always-drop-exp", villagerAlwaysDropExp); -+ villagerMinimumDemand = getInt("mobs.villager.minimum-demand", villagerMinimumDemand); -+ if (PurpurConfig.version < 9) { -+ boolean oldValue = getBoolean("mobs.villager.lobotomize-1x1", villagerLobotomizeEnabled); -+ set("mobs.villager.lobotomize.enabled", oldValue); -+ set("mobs.villager.lobotomize-1x1", null); -+ } -+ if (PurpurConfig.version < 27) { -+ int oldValue = getInt("mobs.villager.lobotomize.check-interval", villagerLobotomizeCheckInterval); -+ set("mobs.villager.lobotomize.check-interval", oldValue == 60 ? 100 : oldValue); -+ } -+ villagerLobotomizeEnabled = getBoolean("mobs.villager.lobotomize.enabled", villagerLobotomizeEnabled); -+ villagerLobotomizeCheckInterval = getInt("mobs.villager.lobotomize.check-interval", villagerLobotomizeCheckInterval); -+ villagerDisplayTradeItem = getBoolean("mobs.villager.display-trade-item", villagerDisplayTradeItem); -+ villagerSpawnIronGolemRadius = getInt("mobs.villager.spawn-iron-golem.radius", villagerSpawnIronGolemRadius); -+ villagerSpawnIronGolemLimit = getInt("mobs.villager.spawn-iron-golem.limit", villagerSpawnIronGolemLimit); -+ } -+ -+ public boolean vindicatorRidable = false; -+ public boolean vindicatorRidableInWater = true; -+ public boolean vindicatorControllable = true; -+ public double vindicatorMaxHealth = 24.0D; -+ public double vindicatorJohnnySpawnChance = 0D; -+ public boolean vindicatorTakeDamageFromWater = false; -+ public boolean vindicatorAlwaysDropExp = false; -+ private void vindicatorSettings() { -+ vindicatorRidable = getBoolean("mobs.vindicator.ridable", vindicatorRidable); -+ vindicatorRidableInWater = getBoolean("mobs.vindicator.ridable-in-water", vindicatorRidableInWater); -+ vindicatorControllable = getBoolean("mobs.vindicator.controllable", vindicatorControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.vindicator.attributes.max-health", vindicatorMaxHealth); -+ set("mobs.vindicator.attributes.max-health", null); -+ set("mobs.vindicator.attributes.max_health", oldValue); -+ } -+ vindicatorMaxHealth = getDouble("mobs.vindicator.attributes.max_health", vindicatorMaxHealth); -+ vindicatorJohnnySpawnChance = getDouble("mobs.vindicator.johnny.spawn-chance", vindicatorJohnnySpawnChance); -+ vindicatorTakeDamageFromWater = getBoolean("mobs.vindicator.takes-damage-from-water", vindicatorTakeDamageFromWater); -+ vindicatorAlwaysDropExp = getBoolean("mobs.vindicator.always-drop-exp", vindicatorAlwaysDropExp); -+ } -+ -+ public boolean wanderingTraderRidable = false; -+ public boolean wanderingTraderRidableInWater = true; -+ public boolean wanderingTraderControllable = true; -+ public double wanderingTraderMaxHealth = 20.0D; -+ public boolean wanderingTraderFollowEmeraldBlock = false; -+ public boolean wanderingTraderCanBeLeashed = false; -+ public boolean wanderingTraderTakeDamageFromWater = false; -+ public boolean wanderingTraderAllowTrading = true; -+ public boolean wanderingTraderAlwaysDropExp = false; -+ private void wanderingTraderSettings() { -+ wanderingTraderRidable = getBoolean("mobs.wandering_trader.ridable", wanderingTraderRidable); -+ wanderingTraderRidableInWater = getBoolean("mobs.wandering_trader.ridable-in-water", wanderingTraderRidableInWater); -+ wanderingTraderControllable = getBoolean("mobs.wandering_trader.controllable", wanderingTraderControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.wandering_trader.attributes.max-health", wanderingTraderMaxHealth); -+ set("mobs.wandering_trader.attributes.max-health", null); -+ set("mobs.wandering_trader.attributes.max_health", oldValue); -+ } -+ wanderingTraderMaxHealth = getDouble("mobs.wandering_trader.attributes.max_health", wanderingTraderMaxHealth); -+ wanderingTraderFollowEmeraldBlock = getBoolean("mobs.wandering_trader.follow-emerald-blocks", wanderingTraderFollowEmeraldBlock); -+ wanderingTraderCanBeLeashed = getBoolean("mobs.wandering_trader.can-be-leashed", wanderingTraderCanBeLeashed); -+ wanderingTraderTakeDamageFromWater = getBoolean("mobs.wandering_trader.takes-damage-from-water", wanderingTraderTakeDamageFromWater); -+ wanderingTraderAllowTrading = getBoolean("mobs.wandering_trader.allow-trading", wanderingTraderAllowTrading); -+ wanderingTraderAlwaysDropExp = getBoolean("mobs.wandering_trader.always-drop-exp", wanderingTraderAlwaysDropExp); -+ } -+ -+ public boolean wardenRidable = false; -+ public boolean wardenRidableInWater = true; -+ public boolean wardenControllable = true; -+ private void wardenSettings() { -+ wardenRidable = getBoolean("mobs.warden.ridable", wardenRidable); -+ wardenRidableInWater = getBoolean("mobs.warden.ridable-in-water", wardenRidableInWater); -+ wardenControllable = getBoolean("mobs.warden.controllable", wardenControllable); -+ } -+ -+ public boolean witchRidable = false; -+ public boolean witchRidableInWater = true; -+ public boolean witchControllable = true; -+ public double witchMaxHealth = 26.0D; -+ public boolean witchTakeDamageFromWater = false; -+ public boolean witchAlwaysDropExp = false; -+ private void witchSettings() { -+ witchRidable = getBoolean("mobs.witch.ridable", witchRidable); -+ witchRidableInWater = getBoolean("mobs.witch.ridable-in-water", witchRidableInWater); -+ witchControllable = getBoolean("mobs.witch.controllable", witchControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.witch.attributes.max-health", witchMaxHealth); -+ set("mobs.witch.attributes.max-health", null); -+ set("mobs.witch.attributes.max_health", oldValue); -+ } -+ witchMaxHealth = getDouble("mobs.witch.attributes.max_health", witchMaxHealth); -+ witchTakeDamageFromWater = getBoolean("mobs.witch.takes-damage-from-water", witchTakeDamageFromWater); -+ witchAlwaysDropExp = getBoolean("mobs.witch.always-drop-exp", witchAlwaysDropExp); -+ } -+ -+ public boolean witherRidable = false; -+ public boolean witherRidableInWater = true; -+ public boolean witherControllable = true; -+ public double witherMaxY = 320D; -+ public double witherMaxHealth = 300.0D; -+ public float witherHealthRegenAmount = 1.0f; -+ public int witherHealthRegenDelay = 20; -+ public boolean witherBypassMobGriefing = false; -+ public boolean witherTakeDamageFromWater = false; -+ public boolean witherCanRideVehicles = false; -+ public float witherExplosionRadius = 1.0F; -+ public boolean witherPlaySpawnSound = true; -+ public boolean witherAlwaysDropExp = false; -+ private void witherSettings() { -+ witherRidable = getBoolean("mobs.wither.ridable", witherRidable); -+ witherRidableInWater = getBoolean("mobs.wither.ridable-in-water", witherRidableInWater); -+ witherControllable = getBoolean("mobs.wither.controllable", witherControllable); -+ witherMaxY = getDouble("mobs.wither.ridable-max-y", witherMaxY); -+ if (PurpurConfig.version < 8) { -+ double oldValue = getDouble("mobs.wither.max-health", witherMaxHealth); -+ set("mobs.wither.max_health", null); -+ set("mobs.wither.attributes.max-health", oldValue); -+ } else if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.wither.attributes.max-health", witherMaxHealth); -+ set("mobs.wither.attributes.max-health", null); -+ set("mobs.wither.attributes.max_health", oldValue); -+ } -+ witherMaxHealth = getDouble("mobs.wither.attributes.max_health", witherMaxHealth); -+ witherHealthRegenAmount = (float) getDouble("mobs.wither.health-regen-amount", witherHealthRegenAmount); -+ witherHealthRegenDelay = getInt("mobs.wither.health-regen-delay", witherHealthRegenDelay); -+ witherBypassMobGriefing = getBoolean("mobs.wither.bypass-mob-griefing", witherBypassMobGriefing); -+ witherTakeDamageFromWater = getBoolean("mobs.wither.takes-damage-from-water", witherTakeDamageFromWater); -+ witherCanRideVehicles = getBoolean("mobs.wither.can-ride-vehicles", witherCanRideVehicles); -+ witherExplosionRadius = (float) getDouble("mobs.wither.explosion-radius", witherExplosionRadius); -+ witherPlaySpawnSound = getBoolean("mobs.wither.play-spawn-sound", witherPlaySpawnSound); -+ witherAlwaysDropExp = getBoolean("mobs.wither.always-drop-exp", witherAlwaysDropExp); -+ } -+ -+ public boolean witherSkeletonRidable = false; -+ public boolean witherSkeletonRidableInWater = true; -+ public boolean witherSkeletonControllable = true; -+ public double witherSkeletonMaxHealth = 20.0D; -+ public boolean witherSkeletonTakeDamageFromWater = false; -+ public boolean witherSkeletonAlwaysDropExp = false; -+ private void witherSkeletonSettings() { -+ witherSkeletonRidable = getBoolean("mobs.wither_skeleton.ridable", witherSkeletonRidable); -+ witherSkeletonRidableInWater = getBoolean("mobs.wither_skeleton.ridable-in-water", witherSkeletonRidableInWater); -+ witherSkeletonControllable = getBoolean("mobs.wither_skeleton.controllable", witherSkeletonControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.wither_skeleton.attributes.max-health", witherSkeletonMaxHealth); -+ set("mobs.wither_skeleton.attributes.max-health", null); -+ set("mobs.wither_skeleton.attributes.max_health", oldValue); -+ } -+ witherSkeletonMaxHealth = getDouble("mobs.wither_skeleton.attributes.max_health", witherSkeletonMaxHealth); -+ witherSkeletonTakeDamageFromWater = getBoolean("mobs.wither_skeleton.takes-damage-from-water", witherSkeletonTakeDamageFromWater); -+ witherSkeletonAlwaysDropExp = getBoolean("mobs.wither_skeleton.always-drop-exp", witherSkeletonAlwaysDropExp); -+ } -+ -+ public boolean wolfRidable = false; -+ public boolean wolfRidableInWater = true; -+ public boolean wolfControllable = true; -+ public double wolfMaxHealth = 8.0D; -+ public DyeColor wolfDefaultCollarColor = DyeColor.RED; -+ public boolean wolfMilkCuresRabies = true; -+ public double wolfNaturalRabid = 0.0D; -+ public int wolfBreedingTicks = 6000; -+ public boolean wolfTakeDamageFromWater = false; -+ public boolean wolfAlwaysDropExp = false; -+ private void wolfSettings() { -+ wolfRidable = getBoolean("mobs.wolf.ridable", wolfRidable); -+ wolfRidableInWater = getBoolean("mobs.wolf.ridable-in-water", wolfRidableInWater); -+ wolfControllable = getBoolean("mobs.wolf.controllable", wolfControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.wolf.attributes.max-health", wolfMaxHealth); -+ set("mobs.wolf.attributes.max-health", null); -+ set("mobs.wolf.attributes.max_health", oldValue); -+ } -+ wolfMaxHealth = getDouble("mobs.wolf.attributes.max_health", wolfMaxHealth); -+ try { -+ wolfDefaultCollarColor = DyeColor.valueOf(getString("mobs.wolf.default-collar-color", wolfDefaultCollarColor.name())); -+ } catch (IllegalArgumentException ignore) { -+ wolfDefaultCollarColor = DyeColor.RED; -+ } -+ wolfMilkCuresRabies = getBoolean("mobs.wolf.milk-cures-rabid-wolves", wolfMilkCuresRabies); -+ wolfNaturalRabid = getDouble("mobs.wolf.spawn-rabid-chance", wolfNaturalRabid); -+ wolfBreedingTicks = getInt("mobs.wolf.breeding-delay-ticks", wolfBreedingTicks); -+ wolfTakeDamageFromWater = getBoolean("mobs.wolf.takes-damage-from-water", wolfTakeDamageFromWater); -+ wolfAlwaysDropExp = getBoolean("mobs.wolf.always-drop-exp", wolfAlwaysDropExp); -+ } -+ -+ public boolean zoglinRidable = false; -+ public boolean zoglinRidableInWater = true; -+ public boolean zoglinControllable = true; -+ public double zoglinMaxHealth = 40.0D; -+ public boolean zoglinTakeDamageFromWater = false; -+ public boolean zoglinAlwaysDropExp = false; -+ private void zoglinSettings() { -+ zoglinRidable = getBoolean("mobs.zoglin.ridable", zoglinRidable); -+ zoglinRidableInWater = getBoolean("mobs.zoglin.ridable-in-water", zoglinRidableInWater); -+ zoglinControllable = getBoolean("mobs.zoglin.controllable", zoglinControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.zoglin.attributes.max-health", zoglinMaxHealth); -+ set("mobs.zoglin.attributes.max-health", null); -+ set("mobs.zoglin.attributes.max_health", oldValue); -+ } -+ zoglinMaxHealth = getDouble("mobs.zoglin.attributes.max_health", zoglinMaxHealth); -+ zoglinTakeDamageFromWater = getBoolean("mobs.zoglin.takes-damage-from-water", zoglinTakeDamageFromWater); -+ zoglinAlwaysDropExp = getBoolean("mobs.zoglin.always-drop-exp", zoglinAlwaysDropExp); -+ } -+ -+ public boolean zombieRidable = false; -+ public boolean zombieRidableInWater = true; -+ public boolean zombieControllable = true; -+ public double zombieMaxHealth = 20.0D; -+ public double zombieSpawnReinforcements = 0.1D; -+ public boolean zombieJockeyOnlyBaby = true; -+ public double zombieJockeyChance = 0.05D; -+ public boolean zombieJockeyTryExistingChickens = true; -+ public boolean zombieAggressiveTowardsVillagerWhenLagging = true; -+ public boolean zombieBypassMobGriefing = false; -+ public boolean zombieTakeDamageFromWater = false; -+ public boolean zombieAlwaysDropExp = false; -+ public double zombieHeadVisibilityPercent = 0.5D; -+ private void zombieSettings() { -+ zombieRidable = getBoolean("mobs.zombie.ridable", zombieRidable); -+ zombieRidableInWater = getBoolean("mobs.zombie.ridable-in-water", zombieRidableInWater); -+ zombieControllable = getBoolean("mobs.zombie.controllable", zombieControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.zombie.attributes.max-health", zombieMaxHealth); -+ set("mobs.zombie.attributes.max-health", null); -+ set("mobs.zombie.attributes.max_health", oldValue); -+ } -+ zombieMaxHealth = getDouble("mobs.zombie.attributes.max_health", zombieMaxHealth); -+ zombieSpawnReinforcements = getDouble("mobs.zombie.attributes.spawn_reinforcements", zombieSpawnReinforcements); -+ zombieJockeyOnlyBaby = getBoolean("mobs.zombie.jockey.only-babies", zombieJockeyOnlyBaby); -+ zombieJockeyChance = getDouble("mobs.zombie.jockey.chance", zombieJockeyChance); -+ zombieJockeyTryExistingChickens = getBoolean("mobs.zombie.jockey.try-existing-chickens", zombieJockeyTryExistingChickens); -+ zombieAggressiveTowardsVillagerWhenLagging = getBoolean("mobs.zombie.aggressive-towards-villager-when-lagging", zombieAggressiveTowardsVillagerWhenLagging); -+ zombieBypassMobGriefing = getBoolean("mobs.zombie.bypass-mob-griefing", zombieBypassMobGriefing); -+ zombieTakeDamageFromWater = getBoolean("mobs.zombie.takes-damage-from-water", zombieTakeDamageFromWater); -+ zombieAlwaysDropExp = getBoolean("mobs.zombie.always-drop-exp", zombieAlwaysDropExp); -+ zombieHeadVisibilityPercent = getDouble("mobs.zombie.head-visibility-percent", zombieHeadVisibilityPercent); -+ } -+ -+ public boolean zombieHorseRidableInWater = false; -+ public boolean zombieHorseCanSwim = false; -+ public double zombieHorseMaxHealthMin = 15.0D; -+ public double zombieHorseMaxHealthMax = 15.0D; -+ public double zombieHorseJumpStrengthMin = 0.4D; -+ public double zombieHorseJumpStrengthMax = 1.0D; -+ public double zombieHorseMovementSpeedMin = 0.2D; -+ public double zombieHorseMovementSpeedMax = 0.2D; -+ public double zombieHorseSpawnChance = 0.0D; -+ public boolean zombieHorseTakeDamageFromWater = false; -+ public boolean zombieHorseAlwaysDropExp = false; -+ private void zombieHorseSettings() { -+ zombieHorseRidableInWater = getBoolean("mobs.zombie_horse.ridable-in-water", zombieHorseRidableInWater); -+ zombieHorseCanSwim = getBoolean("mobs.zombie_horse.can-swim", zombieHorseCanSwim); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.zombie_horse.attributes.max-health", zombieHorseMaxHealthMin); -+ set("mobs.zombie_horse.attributes.max-health", null); -+ set("mobs.zombie_horse.attributes.max_health.min", oldValue); -+ set("mobs.zombie_horse.attributes.max_health.max", oldValue); -+ } -+ zombieHorseMaxHealthMin = getDouble("mobs.zombie_horse.attributes.max_health.min", zombieHorseMaxHealthMin); -+ zombieHorseMaxHealthMax = getDouble("mobs.zombie_horse.attributes.max_health.max", zombieHorseMaxHealthMax); -+ zombieHorseJumpStrengthMin = getDouble("mobs.zombie_horse.attributes.jump_strength.min", zombieHorseJumpStrengthMin); -+ zombieHorseJumpStrengthMax = getDouble("mobs.zombie_horse.attributes.jump_strength.max", zombieHorseJumpStrengthMax); -+ zombieHorseMovementSpeedMin = getDouble("mobs.zombie_horse.attributes.movement_speed.min", zombieHorseMovementSpeedMin); -+ zombieHorseMovementSpeedMax = getDouble("mobs.zombie_horse.attributes.movement_speed.max", zombieHorseMovementSpeedMax); -+ zombieHorseSpawnChance = getDouble("mobs.zombie_horse.spawn-chance", zombieHorseSpawnChance); -+ zombieHorseTakeDamageFromWater = getBoolean("mobs.zombie_horse.takes-damage-from-water", zombieHorseTakeDamageFromWater); -+ zombieHorseAlwaysDropExp = getBoolean("mobs.zombie_horse.always-drop-exp", zombieHorseAlwaysDropExp); -+ } -+ -+ public boolean zombieVillagerRidable = false; -+ public boolean zombieVillagerRidableInWater = true; -+ public boolean zombieVillagerControllable = true; -+ public double zombieVillagerMaxHealth = 20.0D; -+ public double zombieVillagerSpawnReinforcements = 0.1D; -+ public boolean zombieVillagerJockeyOnlyBaby = true; -+ public double zombieVillagerJockeyChance = 0.05D; -+ public boolean zombieVillagerJockeyTryExistingChickens = true; -+ public boolean zombieVillagerTakeDamageFromWater = false; -+ public int zombieVillagerCuringTimeMin = 3600; -+ public int zombieVillagerCuringTimeMax = 6000; -+ public boolean zombieVillagerCureEnabled = true; -+ public boolean zombieVillagerAlwaysDropExp = false; -+ private void zombieVillagerSettings() { -+ zombieVillagerRidable = getBoolean("mobs.zombie_villager.ridable", zombieVillagerRidable); -+ zombieVillagerRidableInWater = getBoolean("mobs.zombie_villager.ridable-in-water", zombieVillagerRidableInWater); -+ zombieVillagerControllable = getBoolean("mobs.zombie_villager.controllable", zombieVillagerControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.zombie_villager.attributes.max-health", zombieVillagerMaxHealth); -+ set("mobs.zombie_villager.attributes.max-health", null); -+ set("mobs.zombie_villager.attributes.max_health", oldValue); -+ } -+ zombieVillagerMaxHealth = getDouble("mobs.zombie_villager.attributes.max_health", zombieVillagerMaxHealth); -+ zombieVillagerSpawnReinforcements = getDouble("mobs.zombie_villager.attributes.spawn_reinforcements", zombieVillagerSpawnReinforcements); -+ zombieVillagerJockeyOnlyBaby = getBoolean("mobs.zombie_villager.jockey.only-babies", zombieVillagerJockeyOnlyBaby); -+ zombieVillagerJockeyChance = getDouble("mobs.zombie_villager.jockey.chance", zombieVillagerJockeyChance); -+ zombieVillagerJockeyTryExistingChickens = getBoolean("mobs.zombie_villager.jockey.try-existing-chickens", zombieVillagerJockeyTryExistingChickens); -+ zombieVillagerTakeDamageFromWater = getBoolean("mobs.zombie_villager.takes-damage-from-water", zombieVillagerTakeDamageFromWater); -+ zombieVillagerCuringTimeMin = getInt("mobs.zombie_villager.curing_time.min", zombieVillagerCuringTimeMin); -+ zombieVillagerCuringTimeMax = getInt("mobs.zombie_villager.curing_time.max", zombieVillagerCuringTimeMax); -+ zombieVillagerCureEnabled = getBoolean("mobs.zombie_villager.cure.enabled", zombieVillagerCureEnabled); -+ zombieVillagerAlwaysDropExp = getBoolean("mobs.zombie_villager.always-drop-exp", zombieVillagerAlwaysDropExp); -+ } -+ -+ public boolean zombifiedPiglinRidable = false; -+ public boolean zombifiedPiglinRidableInWater = true; -+ public boolean zombifiedPiglinControllable = true; -+ public double zombifiedPiglinMaxHealth = 20.0D; -+ public double zombifiedPiglinSpawnReinforcements = 0.0D; -+ public boolean zombifiedPiglinJockeyOnlyBaby = true; -+ public double zombifiedPiglinJockeyChance = 0.05D; -+ public boolean zombifiedPiglinJockeyTryExistingChickens = true; -+ public boolean zombifiedPiglinCountAsPlayerKillWhenAngry = true; -+ public boolean zombifiedPiglinTakeDamageFromWater = false; -+ public boolean zombifiedPiglinAlwaysDropExp = false; -+ private void zombifiedPiglinSettings() { -+ zombifiedPiglinRidable = getBoolean("mobs.zombified_piglin.ridable", zombifiedPiglinRidable); -+ zombifiedPiglinRidableInWater = getBoolean("mobs.zombified_piglin.ridable-in-water", zombifiedPiglinRidableInWater); -+ zombifiedPiglinControllable = getBoolean("mobs.zombified_piglin.controllable", zombifiedPiglinControllable); -+ if (PurpurConfig.version < 10) { -+ double oldValue = getDouble("mobs.zombified_piglin.attributes.max-health", zombifiedPiglinMaxHealth); -+ set("mobs.zombified_piglin.attributes.max-health", null); -+ set("mobs.zombified_piglin.attributes.max_health", oldValue); -+ } -+ zombifiedPiglinMaxHealth = getDouble("mobs.zombified_piglin.attributes.max_health", zombifiedPiglinMaxHealth); -+ zombifiedPiglinSpawnReinforcements = getDouble("mobs.zombified_piglin.attributes.spawn_reinforcements", zombifiedPiglinSpawnReinforcements); -+ zombifiedPiglinJockeyOnlyBaby = getBoolean("mobs.zombified_piglin.jockey.only-babies", zombifiedPiglinJockeyOnlyBaby); -+ zombifiedPiglinJockeyChance = getDouble("mobs.zombified_piglin.jockey.chance", zombifiedPiglinJockeyChance); -+ zombifiedPiglinJockeyTryExistingChickens = getBoolean("mobs.zombified_piglin.jockey.try-existing-chickens", zombifiedPiglinJockeyTryExistingChickens); -+ zombifiedPiglinCountAsPlayerKillWhenAngry = getBoolean("mobs.zombified_piglin.count-as-player-kill-when-angry", zombifiedPiglinCountAsPlayerKillWhenAngry); -+ zombifiedPiglinTakeDamageFromWater = getBoolean("mobs.zombified_piglin.takes-damage-from-water", zombifiedPiglinTakeDamageFromWater); -+ zombifiedPiglinAlwaysDropExp = getBoolean("mobs.zombified_piglin.always-drop-exp", zombifiedPiglinAlwaysDropExp); -+ } -+ -+ public float hungerStarvationDamage = 1.0F; -+ private void hungerSettings() { -+ hungerStarvationDamage = (float) getDouble("hunger.starvation-damage", hungerStarvationDamage); -+ } -+ -+ public int conduitDistance = 16; -+ public double conduitDamageDistance = 8; -+ public float conduitDamageAmount = 4; -+ public Block[] conduitBlocks; -+ private void conduitSettings() { -+ conduitDistance = getInt("blocks.conduit.effect-distance", conduitDistance); -+ conduitDamageDistance = getDouble("blocks.conduit.mob-damage.distance", conduitDamageDistance); -+ conduitDamageAmount = (float) getDouble("blocks.conduit.mob-damage.damage-amount", conduitDamageAmount); -+ List conduitBlockList = new ArrayList<>(); -+ getList("blocks.conduit.valid-ring-blocks", new ArrayList(){{ -+ add("minecraft:prismarine"); -+ add("minecraft:prismarine_bricks"); -+ add("minecraft:sea_lantern"); -+ add("minecraft:dark_prismarine"); -+ }}).forEach(key -> { -+ Block block = BuiltInRegistries.BLOCK.get(new ResourceLocation(key.toString())); -+ if (!block.defaultBlockState().isAir()) { -+ conduitBlockList.add(block); -+ } -+ }); -+ conduitBlocks = conduitBlockList.toArray(Block[]::new); -+ } -+ -+ public float cauldronRainChance = 0.05F; -+ public float cauldronPowderSnowChance = 0.1F; -+ public float cauldronDripstoneWaterFillChance = 0.17578125F; -+ public float cauldronDripstoneLavaFillChance = 0.05859375F; -+ private void cauldronSettings() { -+ cauldronRainChance = (float) getDouble("blocks.cauldron.fill-chances.rain", cauldronRainChance); -+ cauldronPowderSnowChance = (float) getDouble("blocks.cauldron.fill-chances.powder-snow", cauldronPowderSnowChance); -+ cauldronDripstoneWaterFillChance = (float) getDouble("blocks.cauldron.fill-chances.dripstone-water", cauldronDripstoneWaterFillChance); -+ cauldronDripstoneLavaFillChance = (float) getDouble("blocks.cauldron.fill-chances.dripstone-lava", cauldronDripstoneLavaFillChance); -+ } -+} -+ -diff --git a/src/main/java/org/purpurmc/purpur/command/CompassCommand.java b/src/main/java/org/purpurmc/purpur/command/CompassCommand.java -new file mode 100644 -index 0000000000000000000000000000000000000000..79b8490832d2a0cc7846ddcb091cb6bcac74ea45 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/command/CompassCommand.java -@@ -0,0 +1,27 @@ -+package org.purpurmc.purpur.command; -+ -+import com.mojang.brigadier.CommandDispatcher; -+import net.minecraft.commands.CommandSourceStack; -+import net.minecraft.commands.Commands; -+import net.minecraft.server.level.ServerPlayer; -+import org.purpurmc.purpur.task.CompassTask; -+ -+public class CompassCommand { -+ public static void register(CommandDispatcher dispatcher) { -+ dispatcher.register(Commands.literal("compass") -+ .requires(listener -> listener.hasPermission(2, "bukkit.command.compass")) -+ .executes(context -> { -+ ServerPlayer player = context.getSource().getPlayerOrException(); -+ CompassTask task = CompassTask.instance(); -+ if (player.compassBar()) { -+ task.removePlayer(player.getBukkitEntity()); -+ player.compassBar(false); -+ } else { -+ task.addPlayer(player.getBukkitEntity()); -+ player.compassBar(true); -+ } -+ return 1; -+ }) -+ ); -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/command/CreditsCommand.java b/src/main/java/org/purpurmc/purpur/command/CreditsCommand.java -new file mode 100644 -index 0000000000000000000000000000000000000000..40d2fab4a9728ac90c36e30c130f3116b7025d11 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/command/CreditsCommand.java -@@ -0,0 +1,35 @@ -+package org.purpurmc.purpur.command; -+ -+import com.mojang.brigadier.CommandDispatcher; -+import net.minecraft.commands.CommandSourceStack; -+import net.minecraft.commands.Commands; -+import net.minecraft.commands.arguments.EntityArgument; -+import net.minecraft.network.protocol.game.ClientboundGameEventPacket; -+import net.minecraft.server.level.ServerPlayer; -+import org.purpurmc.purpur.PurpurConfig; -+ -+import java.util.Collection; -+import java.util.Collections; -+ -+public class CreditsCommand { -+ public static void register(CommandDispatcher dispatcher) { -+ dispatcher.register(Commands.literal("credits") -+ .requires((listener) -> listener.hasPermission(2, "bukkit.command.credits")) -+ .executes((context) -> execute(context.getSource(), Collections.singleton(context.getSource().getPlayerOrException()))) -+ .then(Commands.argument("targets", EntityArgument.players()) -+ .requires(listener -> listener.hasPermission(2, "bukkit.command.credits.other")) -+ .executes((context) -> execute(context.getSource(), EntityArgument.getPlayers(context, "targets"))) -+ ) -+ ); -+ } -+ -+ private static int execute(CommandSourceStack sender, Collection targets) { -+ for (ServerPlayer player : targets) { -+ ClientboundGameEventPacket packet = new ClientboundGameEventPacket(ClientboundGameEventPacket.WIN_GAME, 1F); -+ player.connection.send(packet); -+ String output = String.format(PurpurConfig.creditsCommandOutput, player.getGameProfile().getName()); -+ sender.sendSuccess(output, false); -+ } -+ return targets.size(); -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/command/DemoCommand.java b/src/main/java/org/purpurmc/purpur/command/DemoCommand.java -new file mode 100644 -index 0000000000000000000000000000000000000000..235f3cd89f675b70a6152a00534608c0902f19fd ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/command/DemoCommand.java -@@ -0,0 +1,35 @@ -+package org.purpurmc.purpur.command; -+ -+import com.mojang.brigadier.CommandDispatcher; -+import net.minecraft.commands.CommandSourceStack; -+import net.minecraft.commands.Commands; -+import net.minecraft.commands.arguments.EntityArgument; -+import net.minecraft.network.protocol.game.ClientboundGameEventPacket; -+import net.minecraft.server.level.ServerPlayer; -+import org.purpurmc.purpur.PurpurConfig; -+ -+import java.util.Collection; -+import java.util.Collections; -+ -+public class DemoCommand { -+ public static void register(CommandDispatcher dispatcher) { -+ dispatcher.register(Commands.literal("demo") -+ .requires((listener) -> listener.hasPermission(2, "bukkit.command.demo")) -+ .executes((context) -> execute(context.getSource(), Collections.singleton(context.getSource().getPlayerOrException()))) -+ .then(Commands.argument("targets", EntityArgument.players()) -+ .requires(listener -> listener.hasPermission(2, "bukkit.command.demo.other")) -+ .executes((context) -> execute(context.getSource(), EntityArgument.getPlayers(context, "targets"))) -+ ) -+ ); -+ } -+ -+ private static int execute(CommandSourceStack sender, Collection targets) { -+ for (ServerPlayer player : targets) { -+ ClientboundGameEventPacket packet = new ClientboundGameEventPacket(ClientboundGameEventPacket.DEMO_EVENT, 0); -+ player.connection.send(packet); -+ String output = String.format(PurpurConfig.demoCommandOutput, player.getGameProfile().getName()); -+ sender.sendSuccess(output, false); -+ } -+ return targets.size(); -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/command/PingCommand.java b/src/main/java/org/purpurmc/purpur/command/PingCommand.java -new file mode 100644 -index 0000000000000000000000000000000000000000..179727c6b3171c040d1aaf069525f61a9a2d54d9 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/command/PingCommand.java -@@ -0,0 +1,33 @@ -+package org.purpurmc.purpur.command; -+ -+import com.mojang.brigadier.CommandDispatcher; -+import net.minecraft.commands.CommandSourceStack; -+import net.minecraft.commands.Commands; -+import net.minecraft.commands.arguments.EntityArgument; -+import net.minecraft.server.level.ServerPlayer; -+import org.purpurmc.purpur.PurpurConfig; -+import org.bukkit.craftbukkit.util.CraftChatMessage; -+ -+import java.util.Collection; -+import java.util.Collections; -+ -+public class PingCommand { -+ public static void register(CommandDispatcher dispatcher) { -+ dispatcher.register(Commands.literal("ping") -+ .requires((listener) -> listener.hasPermission(2, "bukkit.command.ping")) -+ .executes((context) -> execute(context.getSource(), Collections.singleton(context.getSource().getPlayerOrException()))) -+ .then(Commands.argument("targets", EntityArgument.players()) -+ .requires(listener -> listener.hasPermission(2, "bukkit.command.ping.other")) -+ .executes((context) -> execute(context.getSource(), EntityArgument.getPlayers(context, "targets"))) -+ ) -+ ); -+ } -+ -+ private static int execute(CommandSourceStack sender, Collection targets) { -+ for (ServerPlayer player : targets) { -+ String output = String.format(PurpurConfig.pingCommandOutput, player.getGameProfile().getName(), player.latency); -+ sender.sendSuccess(output, false); -+ } -+ return targets.size(); -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/command/PurpurCommand.java b/src/main/java/org/purpurmc/purpur/command/PurpurCommand.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2621e54879e9ab0029a875f1d09eee67878b90d5 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/command/PurpurCommand.java -@@ -0,0 +1,66 @@ -+package org.purpurmc.purpur.command; -+ -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ServerLevel; -+import org.purpurmc.purpur.PurpurConfig; -+import org.bukkit.ChatColor; -+import org.bukkit.Location; -+import org.bukkit.command.Command; -+import org.bukkit.command.CommandSender; -+ -+import java.io.File; -+import java.util.Collections; -+import java.util.List; -+import java.util.stream.Collectors; -+import java.util.stream.Stream; -+ -+public class PurpurCommand extends Command { -+ public PurpurCommand(String name) { -+ super(name); -+ this.description = "Purpur related commands"; -+ this.usageMessage = "/purpur [reload | version]"; -+ this.setPermission("bukkit.command.purpur"); -+ } -+ -+ @Override -+ public List tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException { -+ if (args.length == 1) { -+ return Stream.of("reload", "version") -+ .filter(arg -> arg.startsWith(args[0].toLowerCase())) -+ .collect(Collectors.toList()); -+ } -+ return Collections.emptyList(); -+ } -+ -+ @Override -+ public boolean execute(CommandSender sender, String commandLabel, String[] args) { -+ if (!testPermission(sender)) return true; -+ -+ if (args.length != 1) { -+ sender.sendMessage(ChatColor.RED + "Usage: " + usageMessage); -+ return false; -+ } -+ -+ if (args[0].equalsIgnoreCase("reload")) { -+ Command.broadcastCommandMessage(sender, ChatColor.RED + "Please note that this command is not supported and may cause issues."); -+ Command.broadcastCommandMessage(sender, ChatColor.RED + "If you encounter any issues please use the /stop command to restart your server."); -+ -+ MinecraftServer console = MinecraftServer.getServer(); -+ PurpurConfig.init((File) console.options.valueOf("purpur-settings")); -+ for (ServerLevel level : console.getAllLevels()) { -+ level.purpurConfig.init(); -+ level.resetBreedingCooldowns(); -+ } -+ console.server.reloadCount++; -+ -+ Command.broadcastCommandMessage(sender, ChatColor.GREEN + "Purpur config reload complete."); -+ } else if (args[0].equalsIgnoreCase("version")) { -+ Command verCmd = org.bukkit.Bukkit.getServer().getCommandMap().getCommand("version"); -+ if (verCmd != null) { -+ return verCmd.execute(sender, commandLabel, new String[0]); -+ } -+ } -+ -+ return true; -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/command/RamBarCommand.java b/src/main/java/org/purpurmc/purpur/command/RamBarCommand.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2852c07adb080c34905f5d1b19efed8ea47eecc6 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/command/RamBarCommand.java -@@ -0,0 +1,44 @@ -+package org.purpurmc.purpur.command; -+ -+import com.mojang.brigadier.CommandDispatcher; -+import net.kyori.adventure.text.Component; -+import net.kyori.adventure.text.format.NamedTextColor; -+import net.kyori.adventure.text.minimessage.MiniMessage; -+import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; -+import net.minecraft.commands.CommandSourceStack; -+import net.minecraft.commands.Commands; -+import net.minecraft.commands.arguments.EntityArgument; -+import net.minecraft.server.level.ServerPlayer; -+import org.purpurmc.purpur.PurpurConfig; -+import org.purpurmc.purpur.task.RamBarTask; -+ -+import java.util.Collection; -+import java.util.Collections; -+ -+public class RamBarCommand { -+ public static void register(CommandDispatcher dispatcher) { -+ dispatcher.register(Commands.literal("rambar") -+ .requires(listener -> listener.hasPermission(2, "bukkit.command.rambar")) -+ .executes(context -> execute(context.getSource(), Collections.singleton(context.getSource().getPlayerOrException()))) -+ .then(Commands.argument("targets", EntityArgument.players()) -+ .requires(listener -> listener.hasPermission(2, "bukkit.command.rambar.other")) -+ .executes((context) -> execute(context.getSource(), EntityArgument.getPlayers(context, "targets"))) -+ ) -+ ); -+ } -+ -+ private static int execute(CommandSourceStack sender, Collection targets) { -+ for (ServerPlayer player : targets) { -+ boolean result = RamBarTask.instance().togglePlayer(player.getBukkitEntity()); -+ player.ramBar(result); -+ -+ Component output = MiniMessage.miniMessage().deserialize(PurpurConfig.rambarCommandOutput, -+ Placeholder.component("onoff", Component.translatable(result ? "options.on" : "options.off") -+ .color(result ? NamedTextColor.GREEN : NamedTextColor.RED)), -+ Placeholder.parsed("target", player.getGameProfile().getName())); -+ -+ sender.sendSuccess(output, false); -+ } -+ return targets.size(); -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/command/RamCommand.java b/src/main/java/org/purpurmc/purpur/command/RamCommand.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4ea0877f92b6733035d83a186c3d02c101c9b6cd ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/command/RamCommand.java -@@ -0,0 +1,30 @@ -+package org.purpurmc.purpur.command; -+ -+import com.mojang.brigadier.CommandDispatcher; -+import io.papermc.paper.adventure.PaperAdventure; -+import net.kyori.adventure.text.minimessage.MiniMessage; -+import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; -+import net.minecraft.commands.CommandSourceStack; -+import net.minecraft.commands.Commands; -+import org.purpurmc.purpur.PurpurConfig; -+import org.purpurmc.purpur.task.RamBarTask; -+ -+public class RamCommand { -+ public static void register(CommandDispatcher dispatcher) { -+ dispatcher.register(Commands.literal("ram") -+ .requires(listener -> listener.hasPermission(2, "bukkit.command.ram")) -+ .executes(context -> { -+ CommandSourceStack sender = context.getSource(); -+ RamBarTask ramBar = RamBarTask.instance(); -+ sender.sendSuccess(PaperAdventure.asVanilla(MiniMessage.miniMessage().deserialize(PurpurConfig.ramCommandOutput, -+ Placeholder.component("allocated", ramBar.format(ramBar.getAllocated())), -+ Placeholder.component("used", ramBar.format(ramBar.getUsed())), -+ Placeholder.component("xmx", ramBar.format(ramBar.getXmx())), -+ Placeholder.component("xms", ramBar.format(ramBar.getXms())), -+ Placeholder.unparsed("percent", ((int) (ramBar.getPercent() * 100)) + "%") -+ )), false); -+ return 1; -+ }) -+ ); -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/command/TPSBarCommand.java b/src/main/java/org/purpurmc/purpur/command/TPSBarCommand.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d8f9b044107ff7c29a83eb5378aa9f5465ba1995 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/command/TPSBarCommand.java -@@ -0,0 +1,44 @@ -+package org.purpurmc.purpur.command; -+ -+import com.mojang.brigadier.CommandDispatcher; -+import net.kyori.adventure.text.Component; -+import net.kyori.adventure.text.format.NamedTextColor; -+import net.kyori.adventure.text.minimessage.MiniMessage; -+import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; -+import net.minecraft.commands.CommandSourceStack; -+import net.minecraft.commands.Commands; -+import net.minecraft.commands.arguments.EntityArgument; -+import net.minecraft.server.level.ServerPlayer; -+import org.purpurmc.purpur.PurpurConfig; -+import org.purpurmc.purpur.task.TPSBarTask; -+ -+import java.util.Collection; -+import java.util.Collections; -+ -+public class TPSBarCommand { -+ public static void register(CommandDispatcher dispatcher) { -+ dispatcher.register(Commands.literal("tpsbar") -+ .requires(listener -> listener.hasPermission(2, "bukkit.command.tpsbar")) -+ .executes(context -> execute(context.getSource(), Collections.singleton(context.getSource().getPlayerOrException()))) -+ .then(Commands.argument("targets", EntityArgument.players()) -+ .requires(listener -> listener.hasPermission(2, "bukkit.command.tpsbar.other")) -+ .executes((context) -> execute(context.getSource(), EntityArgument.getPlayers(context, "targets"))) -+ ) -+ ); -+ } -+ -+ private static int execute(CommandSourceStack sender, Collection targets) { -+ for (ServerPlayer player : targets) { -+ boolean result = TPSBarTask.instance().togglePlayer(player.getBukkitEntity()); -+ player.tpsBar(result); -+ -+ Component output = MiniMessage.miniMessage().deserialize(PurpurConfig.tpsbarCommandOutput, -+ Placeholder.component("onoff", Component.translatable(result ? "options.on" : "options.off") -+ .color(result ? NamedTextColor.GREEN : NamedTextColor.RED)), -+ Placeholder.parsed("target", player.getGameProfile().getName())); -+ -+ sender.sendSuccess(output, false); -+ } -+ return targets.size(); -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/command/UptimeCommand.java b/src/main/java/org/purpurmc/purpur/command/UptimeCommand.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4bb475099bcf8f05d5f1474e7fbf29c57c2c40cd ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/command/UptimeCommand.java -@@ -0,0 +1,55 @@ -+package org.purpurmc.purpur.command; -+ -+import com.mojang.brigadier.CommandDispatcher; -+import net.kyori.adventure.text.Component; -+import net.kyori.adventure.text.minimessage.MiniMessage; -+import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; -+import net.minecraft.commands.CommandSourceStack; -+import net.minecraft.commands.Commands; -+import net.minecraft.server.MinecraftServer; -+import org.purpurmc.purpur.PurpurConfig; -+ -+import java.util.concurrent.TimeUnit; -+import java.util.function.Function; -+ -+public class UptimeCommand { -+ public static void register(CommandDispatcher dispatcher) { -+ dispatcher.register(Commands.literal("uptime") -+ .requires((listener) -> listener.hasPermission(2, "bukkit.command.uptime")) -+ .executes((context) -> execute(context.getSource())) -+ ); -+ } -+ -+ private static int execute(CommandSourceStack sender) { -+ Data data = new Data(); -+ -+ data.format = PurpurConfig.uptimeFormat; -+ data.hide = true; -+ data.millis = System.currentTimeMillis() - MinecraftServer.startTimeMillis; -+ -+ process(data, "", PurpurConfig.uptimeDay, PurpurConfig.uptimeDays, TimeUnit.DAYS, TimeUnit.MILLISECONDS::toDays); -+ process(data, "", PurpurConfig.uptimeHour, PurpurConfig.uptimeHours, TimeUnit.HOURS, TimeUnit.MILLISECONDS::toHours); -+ process(data, "", PurpurConfig.uptimeMinute, PurpurConfig.uptimeMinutes, TimeUnit.MINUTES, TimeUnit.MILLISECONDS::toMinutes); -+ data.hide = false; // never hide seconds -+ process(data, "", PurpurConfig.uptimeSecond, PurpurConfig.uptimeSeconds, TimeUnit.SECONDS, TimeUnit.MILLISECONDS::toSeconds); -+ -+ Component output = MiniMessage.miniMessage().deserialize(PurpurConfig.uptimeCommandOutput, Placeholder.unparsed("uptime", data.format)); -+ sender.sendSuccess(output, false); -+ return 1; -+ } -+ -+ private static void process(Data data, String replace, String singular, String plural, TimeUnit unit, Function func) { -+ if (data.format.contains(replace)) { -+ long val = func.apply(data.millis); -+ if (data.hide) data.hide = val == 0; -+ if (!data.hide) data.millis -= unit.toMillis(val); -+ data.format = data.format.replace(replace, data.hide ? "" : String.format(val == 1 ? singular : plural, val)); -+ } -+ } -+ -+ private static class Data { -+ String format; -+ boolean hide; -+ long millis; -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/controller/FlyingMoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/FlyingMoveControllerWASD.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ed494e0ad278813a0eb261101447b84cca3ad7aa ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/controller/FlyingMoveControllerWASD.java -@@ -0,0 +1,71 @@ -+package org.purpurmc.purpur.controller; -+ -+import net.minecraft.world.entity.Mob; -+import net.minecraft.world.entity.ai.attributes.Attributes; -+import net.minecraft.world.entity.player.Player; -+ -+public class FlyingMoveControllerWASD extends MoveControllerWASD { -+ protected final float groundSpeedModifier; -+ protected final float flyingSpeedModifier; -+ protected int tooHighCooldown = 0; -+ protected boolean setNoGravityFlag; -+ -+ public FlyingMoveControllerWASD(Mob entity) { -+ this(entity, 1.0F); -+ } -+ -+ public FlyingMoveControllerWASD(Mob entity, float groundSpeedModifier) { -+ this(entity, groundSpeedModifier, 1.0F, true); -+ } -+ -+ public FlyingMoveControllerWASD(Mob entity, float groundSpeedModifier, float flyingSpeedModifier) { -+ this(entity, groundSpeedModifier, flyingSpeedModifier, true); -+ } -+ -+ public FlyingMoveControllerWASD(Mob entity, float groundSpeedModifier, float flyingSpeedModifier, boolean setNoGravityFlag) { -+ super(entity); -+ this.groundSpeedModifier = groundSpeedModifier; -+ this.flyingSpeedModifier = flyingSpeedModifier; -+ this.setNoGravityFlag = setNoGravityFlag; -+ } -+ -+ @Override -+ public void purpurTick(Player rider) { -+ float forward = Math.max(0.0F, rider.getForwardMot()); -+ float vertical = forward == 0.0F ? 0.0F : -(rider.xRotO / 45.0F); -+ float strafe = rider.getStrafeMot(); -+ -+ if (rider.jumping && spacebarEvent(entity)) { -+ entity.onSpacebar(); -+ } -+ -+ if (entity.getY() >= entity.getMaxY() || --tooHighCooldown > 0) { -+ if (tooHighCooldown <= 0) { -+ tooHighCooldown = 20; -+ } -+ entity.setDeltaMovement(entity.getDeltaMovement().add(0.0D, -0.05D, 0.0D)); -+ vertical = 0.0F; -+ } -+ -+ setSpeedModifier(entity.getAttributeValue(Attributes.MOVEMENT_SPEED)); -+ float speed = (float) getSpeedModifier(); -+ -+ if (entity.onGround) { -+ speed *= groundSpeedModifier; // TODO = fix this! -+ } else { -+ speed *= flyingSpeedModifier; -+ } -+ -+ if (setNoGravityFlag) { -+ entity.setNoGravity(forward > 0); -+ } -+ -+ entity.setSpeed(speed); -+ entity.setVerticalMot(vertical); -+ entity.setStrafeMot(strafe); -+ entity.setForwardMot(forward); -+ -+ setForward(entity.getForwardMot()); -+ setStrafe(entity.getStrafeMot()); -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/controller/FlyingWithSpacebarMoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/FlyingWithSpacebarMoveControllerWASD.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9383c07fa53141127106a1f289366a040960d52e ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/controller/FlyingWithSpacebarMoveControllerWASD.java -@@ -0,0 +1,63 @@ -+package org.purpurmc.purpur.controller; -+ -+import net.minecraft.world.entity.Mob; -+import net.minecraft.world.entity.ai.attributes.Attributes; -+import net.minecraft.world.entity.player.Player; -+import net.minecraft.world.phys.Vec3; -+ -+public class FlyingWithSpacebarMoveControllerWASD extends FlyingMoveControllerWASD { -+ public FlyingWithSpacebarMoveControllerWASD(Mob entity) { -+ super(entity); -+ } -+ -+ public FlyingWithSpacebarMoveControllerWASD(Mob entity, float groundSpeedModifier) { -+ super(entity, groundSpeedModifier); -+ } -+ -+ @Override -+ public void purpurTick(Player rider) { -+ float forward = rider.getForwardMot(); -+ float strafe = rider.getStrafeMot() * 0.5F; -+ float vertical = 0; -+ -+ if (forward < 0.0F) { -+ forward *= 0.5F; -+ strafe *= 0.5F; -+ } -+ -+ float speed = (float) entity.getAttributeValue(Attributes.MOVEMENT_SPEED); -+ -+ if (entity.onGround) { -+ speed *= groundSpeedModifier; -+ } -+ -+ if (rider.jumping && spacebarEvent(entity) && !entity.onSpacebar()) { -+ entity.setNoGravity(true); -+ vertical = 1.0F; -+ } else { -+ entity.setNoGravity(false); -+ } -+ -+ if (entity.getY() >= entity.getMaxY() || --tooHighCooldown > 0) { -+ if (tooHighCooldown <= 0) { -+ tooHighCooldown = 20; -+ } -+ entity.setDeltaMovement(entity.getDeltaMovement().add(0.0D, -0.2D, 0.0D)); -+ vertical = 0.0F; -+ } -+ -+ setSpeedModifier(speed); -+ entity.setSpeed((float) getSpeedModifier()); -+ entity.setVerticalMot(vertical); -+ entity.setStrafeMot(strafe); -+ entity.setForwardMot(forward); -+ -+ setForward(entity.getForwardMot()); -+ setStrafe(entity.getStrafeMot()); -+ -+ Vec3 mot = entity.getDeltaMovement(); -+ if (mot.y > 0.2D) { -+ entity.setDeltaMovement(mot.x, 0.2D, mot.z); -+ } -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/controller/LookControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/LookControllerWASD.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b8c25c96e95dd5ec3ad9fa4c41bd6c08e144832d ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/controller/LookControllerWASD.java -@@ -0,0 +1,76 @@ -+package org.purpurmc.purpur.controller; -+ -+ -+import net.minecraft.network.protocol.game.ClientboundMoveEntityPacket; -+import net.minecraft.util.Mth; -+import net.minecraft.world.entity.Mob; -+import net.minecraft.world.entity.ai.control.LookControl; -+import net.minecraft.world.entity.player.Player; -+ -+public class LookControllerWASD extends LookControl { -+ protected final Mob entity; -+ private float yOffset = 0; -+ private float xOffset = 0; -+ -+ public LookControllerWASD(Mob entity) { -+ super(entity); -+ this.entity = entity; -+ } -+ -+ // tick -+ @Override -+ public void tick() { -+ if (entity.getRider() != null && entity.isControllable()) { -+ purpurTick(entity.getRider()); -+ } else { -+ vanillaTick(); -+ } -+ } -+ -+ protected void purpurTick(Player rider) { -+ setYawPitch(rider.getYRot(), rider.getXRot()); -+ } -+ -+ public void vanillaTick() { -+ super.tick(); -+ } -+ -+ public void setYawPitch(float yRot, float xRot) { -+ entity.setXRot(normalizePitch(xRot + xOffset)); -+ entity.setYRot(normalizeYaw(yRot + yOffset)); -+ entity.setYHeadRot(entity.getYRot()); -+ entity.xRotO = entity.getXRot(); -+ entity.yRotO = entity.getYRot(); -+ -+ entity.tracker.broadcast(new ClientboundMoveEntityPacket -+ .PosRot(entity.getId(), -+ (short) 0, (short) 0, (short) 0, -+ (byte) Mth.floor(entity.getYRot() * 256.0F / 360.0F), -+ (byte) Mth.floor(entity.getXRot() * 256.0F / 360.0F), -+ entity.onGround)); -+ } -+ -+ public void setOffsets(float yaw, float pitch) { -+ yOffset = yaw; -+ xOffset = pitch; -+ } -+ -+ public float normalizeYaw(float yaw) { -+ yaw %= 360.0f; -+ if (yaw >= 180.0f) { -+ yaw -= 360.0f; -+ } else if (yaw < -180.0f) { -+ yaw += 360.0f; -+ } -+ return yaw; -+ } -+ -+ public float normalizePitch(float pitch) { -+ if (pitch > 90.0f) { -+ pitch = 90.0f; -+ } else if (pitch < -90.0f) { -+ pitch = -90.0f; -+ } -+ return pitch; -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/controller/MoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/MoveControllerWASD.java -new file mode 100644 -index 0000000000000000000000000000000000000000..21fd6ea2a482758a3016e3bc2cdebe2d89267481 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/controller/MoveControllerWASD.java -@@ -0,0 +1,89 @@ -+package org.purpurmc.purpur.controller; -+ -+import net.minecraft.world.entity.Mob; -+import net.minecraft.world.entity.ai.attributes.Attributes; -+import net.minecraft.world.entity.ai.control.MoveControl; -+import net.minecraft.world.entity.player.Player; -+import org.purpurmc.purpur.event.entity.RidableSpacebarEvent; -+ -+public class MoveControllerWASD extends MoveControl { -+ protected final Mob entity; -+ private final double speedModifier; -+ -+ public MoveControllerWASD(Mob entity) { -+ this(entity, 1.0D); -+ } -+ -+ public MoveControllerWASD(Mob entity, double speedModifier) { -+ super(entity); -+ this.entity = entity; -+ this.speedModifier = speedModifier; -+ } -+ -+ @Override -+ public boolean hasWanted() { -+ return entity.getRider() != null ? strafeForwards != 0 || strafeRight != 0 : super.hasWanted(); -+ } -+ -+ @Override -+ public void tick() { -+ if (entity.getRider() != null && entity.isControllable()) { -+ purpurTick(entity.getRider()); -+ } else { -+ vanillaTick(); -+ } -+ } -+ -+ public void vanillaTick() { -+ super.tick(); -+ } -+ -+ public void purpurTick(Player rider) { -+ float forward = rider.getForwardMot() * 0.5F; -+ float strafe = rider.getStrafeMot() * 0.25F; -+ -+ if (forward <= 0.0F) { -+ forward *= 0.5F; -+ } -+ -+ float yawOffset = 0; -+ if (strafe != 0) { -+ if (forward == 0) { -+ yawOffset += strafe > 0 ? -90 : 90; -+ forward = Math.abs(strafe * 2); -+ } else { -+ yawOffset += strafe > 0 ? -30 : 30; -+ strafe /= 2; -+ if (forward < 0) { -+ yawOffset += strafe > 0 ? -110 : 110; -+ forward *= -1; -+ } -+ } -+ } else if (forward < 0) { -+ yawOffset -= 180; -+ forward *= -1; -+ } -+ -+ ((LookControllerWASD) entity.getLookControl()).setOffsets(yawOffset, 0); -+ -+ if (rider.jumping && spacebarEvent(entity) && !entity.onSpacebar() && entity.onGround) { -+ entity.jumpFromGround(); -+ } -+ -+ setSpeedModifier(entity.getAttributeValue(Attributes.MOVEMENT_SPEED) * speedModifier); -+ -+ entity.setSpeed((float) getSpeedModifier()); -+ entity.setForwardMot(forward); -+ -+ setForward(entity.getForwardMot()); -+ setStrafe(entity.getStrafeMot()); -+ } -+ -+ public static boolean spacebarEvent(Mob entity) { -+ if (RidableSpacebarEvent.getHandlerList().getRegisteredListeners().length > 0) { -+ return new RidableSpacebarEvent(entity.getBukkitEntity()).callEvent(); -+ } else { -+ return true; -+ } -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/controller/WaterMoveControllerWASD.java b/src/main/java/org/purpurmc/purpur/controller/WaterMoveControllerWASD.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ba2a37dad43e238e54632975abea8ee6fafaa9e0 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/controller/WaterMoveControllerWASD.java -@@ -0,0 +1,50 @@ -+package org.purpurmc.purpur.controller; -+ -+import net.minecraft.world.entity.Mob; -+import net.minecraft.world.entity.ai.attributes.Attributes; -+import net.minecraft.world.entity.player.Player; -+ -+public class WaterMoveControllerWASD extends MoveControllerWASD { -+ private final double speedModifier; -+ -+ public WaterMoveControllerWASD(Mob entity) { -+ this(entity, 1.0D); -+ } -+ -+ public WaterMoveControllerWASD(Mob entity, double speedModifier) { -+ super(entity); -+ this.speedModifier = speedModifier; -+ } -+ -+ @Override -+ public void purpurTick(Player rider) { -+ float forward = rider.getForwardMot(); -+ float strafe = rider.getStrafeMot() * 0.5F; // strafe slower by default -+ float vertical = -(rider.xRotO / 90); -+ -+ if (forward == 0.0F) { -+ // strafe slower if not moving forward -+ strafe *= 0.5F; -+ // do not move vertically if not moving forward -+ vertical = 0.0F; -+ } else if (forward < 0.0F) { -+ // water animals can't swim backwards -+ forward = 0.0F; -+ vertical = 0.0F; -+ } -+ -+ if (rider.jumping && spacebarEvent(entity)) { -+ entity.onSpacebar(); -+ } -+ -+ setSpeedModifier(entity.getAttributeValue(Attributes.MOVEMENT_SPEED) * speedModifier); -+ entity.setSpeed((float) getSpeedModifier() * 0.1F); -+ -+ entity.setForwardMot(forward * (float) speedModifier); -+ entity.setStrafeMot(strafe * (float) speedModifier); -+ entity.setVerticalMot(vertical * (float) speedModifier); -+ -+ setForward(entity.getForwardMot()); -+ setStrafe(entity.getStrafeMot()); -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/entity/DolphinSpit.java b/src/main/java/org/purpurmc/purpur/entity/DolphinSpit.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f0279d6cdc93f524f321c3c40967fdeeb8d2c46b ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/entity/DolphinSpit.java -@@ -0,0 +1,104 @@ -+package org.purpurmc.purpur.entity; -+ -+import net.minecraft.core.particles.ParticleTypes; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.util.Mth; -+import net.minecraft.world.damagesource.DamageSource; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.EntityType; -+import net.minecraft.world.entity.LivingEntity; -+import net.minecraft.world.entity.animal.Dolphin; -+import net.minecraft.world.entity.projectile.LlamaSpit; -+import net.minecraft.world.entity.projectile.ProjectileUtil; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.phys.BlockHitResult; -+import net.minecraft.world.phys.EntityHitResult; -+import net.minecraft.world.phys.HitResult; -+import net.minecraft.world.phys.Vec3; -+ -+public class DolphinSpit extends LlamaSpit { -+ public LivingEntity dolphin; -+ public int ticksLived; -+ -+ public DolphinSpit(EntityType type, Level world) { -+ super(type, world); -+ } -+ -+ public DolphinSpit(Level world, Dolphin dolphin) { -+ this(EntityType.LLAMA_SPIT, world); -+ setOwner(dolphin.getRider() != null ? dolphin.getRider() : dolphin); -+ this.dolphin = dolphin; -+ this.setPos( -+ dolphin.getX() - (double) (dolphin.getBbWidth() + 1.0F) * 0.5D * (double) Mth.sin(dolphin.yBodyRot * 0.017453292F), -+ dolphin.getEyeY() - 0.10000000149011612D, -+ dolphin.getZ() + (double) (dolphin.getBbWidth() + 1.0F) * 0.5D * (double) Mth.cos(dolphin.yBodyRot * 0.017453292F)); -+ } -+ -+ @Override -+ public boolean canSaveToDisk() { -+ return false; -+ } -+ -+ public void tick() { -+ super_tick(); -+ -+ Vec3 mot = this.getDeltaMovement(); -+ HitResult hitResult = ProjectileUtil.getHitResult(this, this::canHitEntity); -+ -+ this.preOnHit(hitResult); -+ -+ double x = this.getX() + mot.x; -+ double y = this.getY() + mot.y; -+ double z = this.getZ() + mot.z; -+ -+ this.updateRotation(); -+ -+ Vec3 motDouble = mot.scale(2.0); -+ for (int i = 0; i < 5; i++) { -+ ((ServerLevel) level).sendParticles(null, ParticleTypes.BUBBLE, -+ getX() + random.nextFloat() / 2 - 0.25F, -+ getY() + random.nextFloat() / 2 - 0.25F, -+ getZ() + random.nextFloat() / 2 - 0.25F, -+ 0, motDouble.x(), motDouble.y(), motDouble.z(), 0.1D, true); -+ } -+ -+ if (++ticksLived > 20) { -+ this.discard(); -+ } else { -+ this.setDeltaMovement(mot.scale(0.99D)); -+ if (!this.isNoGravity()) { -+ this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -0.06D, 0.0D)); -+ } -+ -+ this.setPos(x, y, z); -+ } -+ } -+ -+ @Override -+ public void shoot(double x, double y, double z, float speed, float inaccuracy) { -+ setDeltaMovement(new Vec3(x, y, z).normalize().add( -+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, -+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, -+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy) -+ .scale(speed)); -+ } -+ -+ @Override -+ protected void onHitEntity(EntityHitResult entityHitResult) { -+ Entity shooter = this.getOwner(); -+ if (shooter instanceof LivingEntity) { -+ entityHitResult.getEntity().hurt(entityHitResult.getEntity().damageSources().mobProjectile(this, (LivingEntity) shooter), level.purpurConfig.dolphinSpitDamage); -+ } -+ } -+ -+ @Override -+ protected void onHitBlock(BlockHitResult blockHitResult) { -+ if (this.hitCancelled) { -+ return; -+ } -+ BlockState state = this.level.getBlockState(blockHitResult.getBlockPos()); -+ state.onProjectileHit(this.level, state, blockHitResult, this); -+ this.discard(); -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/entity/GlowSquidColor.java b/src/main/java/org/purpurmc/purpur/entity/GlowSquidColor.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c90256f4c16ffdb2d8e767e837ea36ac7a6613be ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/entity/GlowSquidColor.java -@@ -0,0 +1,61 @@ -+package org.purpurmc.purpur.entity; -+ -+import net.minecraft.util.RandomSource; -+ -+import java.util.ArrayList; -+import java.util.Arrays; -+import java.util.HashMap; -+import java.util.List; -+import java.util.Locale; -+import java.util.Map; -+ -+public enum GlowSquidColor { -+ BLUE, RED, GREEN, PINK, YELLOW, ORANGE, INDIGO, PURPLE, WHITE, GRAY, BLACK; -+ -+ @Override -+ public String toString() { -+ return this.name().toLowerCase(Locale.ROOT); -+ } -+ -+ public enum Mode { -+ RAINBOW(RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, PURPLE), -+ ALL_COLORS(BLUE, RED, GREEN, PINK, YELLOW, ORANGE, INDIGO, PURPLE, WHITE, GRAY, BLACK), -+ TRANS_PRIDE(BLUE, WHITE, PINK), -+ LESBIAN_PRIDE(RED, ORANGE, WHITE, PINK, PURPLE), -+ BI_PRIDE(BLUE, PINK, PURPLE), -+ GAY_PRIDE(BLUE, GREEN, WHITE), -+ PAN_PRIDE(PINK, YELLOW, BLUE), -+ ACE_PRIDE(BLACK, GRAY, WHITE, PURPLE), -+ ARO_PRIDE(BLACK, GRAY, WHITE, GREEN), -+ ENBY_PRIDE(YELLOW, WHITE, BLACK, PURPLE), -+ GENDERFLUID(PURPLE, WHITE, BLACK, PINK, BLUE), -+ MONOCHROME(BLACK, GRAY, WHITE), -+ VANILLA(BLUE); -+ -+ private static final Map BY_NAME = new HashMap<>(); -+ -+ static { -+ Arrays.stream(values()).forEach(mode -> BY_NAME.put(mode.name(), mode)); -+ } -+ -+ private final List colors = new ArrayList<>(); -+ -+ Mode(GlowSquidColor... colors) { -+ this.colors.addAll(Arrays.stream(colors).toList()); -+ } -+ -+ public static Mode get(String string) { -+ Mode mode = BY_NAME.get(string.toUpperCase(Locale.ROOT)); -+ return mode == null ? RAINBOW : mode; -+ } -+ -+ public GlowSquidColor getRandom(RandomSource random) { -+ return this.colors.get(random.nextInt(this.colors.size())); -+ } -+ -+ @Override -+ public String toString() { -+ return this.name().toLowerCase(Locale.ROOT); -+ } -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/entity/PhantomFlames.java b/src/main/java/org/purpurmc/purpur/entity/PhantomFlames.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1542f038621b97a298a0fb31ab3be912e2bcd0d6 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/entity/PhantomFlames.java -@@ -0,0 +1,119 @@ -+package org.purpurmc.purpur.entity; -+ -+import net.minecraft.core.particles.ParticleTypes; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.util.Mth; -+import net.minecraft.world.damagesource.DamageSource; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.EntityType; -+import net.minecraft.world.entity.LivingEntity; -+import net.minecraft.world.entity.decoration.ArmorStand; -+import net.minecraft.world.entity.monster.Phantom; -+import net.minecraft.world.entity.projectile.LlamaSpit; -+import net.minecraft.world.entity.projectile.ProjectileUtil; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.state.BlockBehaviour; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.phys.BlockHitResult; -+import net.minecraft.world.phys.EntityHitResult; -+import net.minecraft.world.phys.HitResult; -+import net.minecraft.world.phys.Vec3; -+ -+public class PhantomFlames extends LlamaSpit { -+ public Phantom phantom; -+ public int ticksLived; -+ public boolean canGrief = false; -+ -+ public PhantomFlames(EntityType type, Level world) { -+ super(type, world); -+ } -+ -+ public PhantomFlames(Level world, Phantom phantom) { -+ this(EntityType.LLAMA_SPIT, world); -+ setOwner(phantom.getRider() != null ? phantom.getRider() : phantom); -+ this.phantom = phantom; -+ this.setPos( -+ phantom.getX() - (double) (phantom.getBbWidth() + 1.0F) * 0.5D * (double) Mth.sin(phantom.yBodyRot * 0.017453292F), -+ phantom.getEyeY() - 0.10000000149011612D, -+ phantom.getZ() + (double) (phantom.getBbWidth() + 1.0F) * 0.5D * (double) Mth.cos(phantom.yBodyRot * 0.017453292F)); -+ } -+ -+ @Override -+ public boolean canSaveToDisk() { -+ return false; -+ } -+ -+ public void tick() { -+ super_tick(); -+ -+ Vec3 mot = this.getDeltaMovement(); -+ HitResult hitResult = ProjectileUtil.getHitResult(this, this::canHitEntity); -+ -+ this.preOnHit(hitResult); -+ -+ double x = this.getX() + mot.x; -+ double y = this.getY() + mot.y; -+ double z = this.getZ() + mot.z; -+ -+ this.updateRotation(); -+ -+ Vec3 motDouble = mot.scale(2.0); -+ for (int i = 0; i < 5; i++) { -+ ((ServerLevel) level).sendParticles(null, ParticleTypes.FLAME, -+ getX() + random.nextFloat() / 2 - 0.25F, -+ getY() + random.nextFloat() / 2 - 0.25F, -+ getZ() + random.nextFloat() / 2 - 0.25F, -+ 0, motDouble.x(), motDouble.y(), motDouble.z(), 0.1D, true); -+ } -+ -+ if (++ticksLived > 20) { -+ this.discard(); -+ } else if (this.level.getBlockStates(this.getBoundingBox()).noneMatch(BlockBehaviour.BlockStateBase::isAir)) { -+ this.discard(); -+ } else if (this.isInWaterOrBubble()) { -+ this.discard(); -+ } else { -+ this.setDeltaMovement(mot.scale(0.99D)); -+ if (!this.isNoGravity()) { -+ this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -0.06D, 0.0D)); -+ } -+ -+ this.setPos(x, y, z); -+ } -+ } -+ -+ @Override -+ public void shoot(double x, double y, double z, float speed, float inaccuracy) { -+ setDeltaMovement(new Vec3(x, y, z).normalize().add( -+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, -+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy, -+ random.nextGaussian() * (double) 0.0075F * (double) inaccuracy) -+ .scale(speed)); -+ } -+ -+ @Override -+ protected void onHitEntity(EntityHitResult entityHitResult) { -+ Entity shooter = this.getOwner(); -+ if (shooter instanceof LivingEntity) { -+ Entity target = entityHitResult.getEntity(); -+ if (canGrief || (target instanceof LivingEntity && !(target instanceof ArmorStand))) { -+ boolean hurt = target.hurt(target.damageSources().mobProjectile(this, (LivingEntity) shooter), level.purpurConfig.phantomFlameDamage); -+ if (hurt && level.purpurConfig.phantomFlameFireTime > 0) { -+ target.setSecondsOnFire(level.purpurConfig.phantomFlameFireTime); -+ } -+ } -+ } -+ } -+ -+ @Override -+ protected void onHitBlock(BlockHitResult blockHitResult) { -+ if (this.hitCancelled) { -+ return; -+ } -+ if (this.canGrief) { -+ BlockState state = this.level.getBlockState(blockHitResult.getBlockPos()); -+ state.onProjectileHit(this.level, state, blockHitResult, this); -+ } -+ this.discard(); -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/entity/ai/HasRider.java b/src/main/java/org/purpurmc/purpur/entity/ai/HasRider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8babdaddd8b33278aea0369dbbeeb445abe45016 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/entity/ai/HasRider.java -@@ -0,0 +1,20 @@ -+package org.purpurmc.purpur.entity.ai; -+ -+import net.minecraft.world.entity.Mob; -+import net.minecraft.world.entity.ai.goal.Goal; -+ -+import java.util.EnumSet; -+ -+public class HasRider extends Goal { -+ public final Mob entity; -+ -+ public HasRider(Mob entity) { -+ this.entity = entity; -+ setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK, Flag.TARGET, Flag.UNKNOWN_BEHAVIOR)); -+ } -+ -+ @Override -+ public boolean canUse() { -+ return entity.getRider() != null && entity.isControllable(); -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/entity/ai/HorseHasRider.java b/src/main/java/org/purpurmc/purpur/entity/ai/HorseHasRider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..432f4f3d82af2f19820890b68d33189a9f2c69f9 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/entity/ai/HorseHasRider.java -@@ -0,0 +1,17 @@ -+package org.purpurmc.purpur.entity.ai; -+ -+import net.minecraft.world.entity.animal.horse.AbstractHorse; -+ -+public class HorseHasRider extends HasRider { -+ public final AbstractHorse horse; -+ -+ public HorseHasRider(AbstractHorse entity) { -+ super(entity); -+ this.horse = entity; -+ } -+ -+ @Override -+ public boolean canUse() { -+ return super.canUse() && horse.isSaddled(); -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/entity/ai/LlamaHasRider.java b/src/main/java/org/purpurmc/purpur/entity/ai/LlamaHasRider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..18a95e043cbffa65eeaaf65ff7695e5dc939820c ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/entity/ai/LlamaHasRider.java -@@ -0,0 +1,17 @@ -+package org.purpurmc.purpur.entity.ai; -+ -+import net.minecraft.world.entity.animal.horse.Llama; -+ -+public class LlamaHasRider extends HasRider { -+ public final Llama llama; -+ -+ public LlamaHasRider(Llama entity) { -+ super(entity); -+ this.llama = entity; -+ } -+ -+ @Override -+ public boolean canUse() { -+ return super.canUse() && llama.isSaddled() && llama.isControllable(); -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/entity/ai/ReceiveFlower.java b/src/main/java/org/purpurmc/purpur/entity/ai/ReceiveFlower.java -new file mode 100644 -index 0000000000000000000000000000000000000000..115a3b36cbb7716b28ef940a29ca97ac42a8a521 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/entity/ai/ReceiveFlower.java -@@ -0,0 +1,91 @@ -+package org.purpurmc.purpur.entity.ai; -+ -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.world.InteractionHand; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.ai.goal.Goal; -+import net.minecraft.world.entity.animal.IronGolem; -+import net.minecraft.world.item.ItemStack; -+import net.minecraft.world.level.block.Blocks; -+ -+import java.util.EnumSet; -+import java.util.UUID; -+ -+public class ReceiveFlower extends Goal { -+ private final IronGolem irongolem; -+ private ServerPlayer target; -+ private int cooldown; -+ -+ public ReceiveFlower(IronGolem entity) { -+ this.irongolem = entity; -+ setFlags(EnumSet.of(Flag.MOVE, Flag.LOOK)); -+ } -+ -+ @Override -+ public boolean canUse() { -+ if (this.irongolem.getOfferFlowerTick() > 0) { -+ return false; -+ } -+ if (!this.irongolem.isAngry()) { -+ return false; -+ } -+ UUID uuid = this.irongolem.getPersistentAngerTarget(); -+ if (uuid == null) { -+ return false; -+ } -+ Entity target = ((ServerLevel) this.irongolem.level).getEntity(uuid); -+ if (!(target instanceof ServerPlayer player)) { -+ return false; -+ } -+ InteractionHand hand = getPoppyHand(player); -+ if (hand == null) { -+ return false; -+ } -+ removeFlower(player, hand); -+ this.target = player; -+ return true; -+ } -+ -+ @Override -+ public boolean canContinueToUse() { -+ return this.cooldown > 0; -+ } -+ -+ @Override -+ public void start() { -+ this.cooldown = 100; -+ this.irongolem.stopBeingAngry(); -+ this.irongolem.offerFlower(true); -+ } -+ -+ @Override -+ public void stop() { -+ this.irongolem.offerFlower(false); -+ this.target = null; -+ } -+ -+ @Override -+ public void tick() { -+ this.irongolem.getLookControl().setLookAt(this.target, 30.0F, 30.0F); -+ --this.cooldown; -+ } -+ -+ private InteractionHand getPoppyHand(ServerPlayer player) { -+ if (isPoppy(player.getMainHandItem())) { -+ return InteractionHand.MAIN_HAND; -+ } -+ if (isPoppy(player.getOffhandItem())) { -+ return InteractionHand.OFF_HAND; -+ } -+ return null; -+ } -+ -+ private void removeFlower(ServerPlayer player, InteractionHand hand) { -+ player.setItemInHand(hand, ItemStack.EMPTY); -+ } -+ -+ private boolean isPoppy(ItemStack item) { -+ return item.getItem() == Blocks.POPPY.asItem(); -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/gui/GUIColor.java b/src/main/java/org/purpurmc/purpur/gui/GUIColor.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0f2e7e0b81620c8581949bd5f0bdb567cd93c17e ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/gui/GUIColor.java -@@ -0,0 +1,54 @@ -+package org.purpurmc.purpur.gui; -+ -+import net.md_5.bungee.api.ChatColor; -+ -+import java.awt.Color; -+import java.util.HashMap; -+import java.util.Map; -+ -+public enum GUIColor { -+ BLACK(ChatColor.BLACK, new Color(0x000000)), -+ DARK_BLUE(ChatColor.DARK_BLUE, new Color(0x0000AA)), -+ DARK_GREEN(ChatColor.DARK_GREEN, new Color(0x00AA00)), -+ DARK_AQUA(ChatColor.DARK_AQUA, new Color(0x009999)), -+ DARK_RED(ChatColor.DARK_RED, new Color(0xAA0000)), -+ DARK_PURPLE(ChatColor.DARK_PURPLE, new Color(0xAA00AA)), -+ GOLD(ChatColor.GOLD, new Color(0xBB8800)), -+ GRAY(ChatColor.GRAY, new Color(0x888888)), -+ DARK_GRAY(ChatColor.DARK_GRAY, new Color(0x444444)), -+ BLUE(ChatColor.BLUE, new Color(0x5555FF)), -+ GREEN(ChatColor.GREEN, new Color(0x55FF55)), -+ AQUA(ChatColor.AQUA, new Color(0x55DDDD)), -+ RED(ChatColor.RED, new Color(0xFF5555)), -+ LIGHT_PURPLE(ChatColor.LIGHT_PURPLE, new Color(0xFF55FF)), -+ YELLOW(ChatColor.YELLOW, new Color(0xFFBB00)), -+ WHITE(ChatColor.WHITE, new Color(0xBBBBBB)); -+ -+ private final ChatColor chat; -+ private final Color color; -+ -+ private static final Map BY_CHAT = new HashMap<>(); -+ -+ GUIColor(ChatColor chat, Color color) { -+ this.chat = chat; -+ this.color = color; -+ } -+ -+ public Color getColor() { -+ return color; -+ } -+ -+ public String getCode() { -+ return chat.toString(); -+ } -+ -+ public static GUIColor getColor(ChatColor chat) { -+ return BY_CHAT.get(chat); -+ } -+ -+ static { -+ for (GUIColor color : values()) { -+ BY_CHAT.put(color.chat, color); -+ } -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/gui/JColorTextPane.java b/src/main/java/org/purpurmc/purpur/gui/JColorTextPane.java -new file mode 100644 -index 0000000000000000000000000000000000000000..33e89b4c00fa8318506b36cbe49fe4e412e0a9a1 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/gui/JColorTextPane.java -@@ -0,0 +1,78 @@ -+package org.purpurmc.purpur.gui; -+ -+import com.google.common.collect.Sets; -+import net.md_5.bungee.api.ChatColor; -+import net.md_5.bungee.api.chat.BaseComponent; -+import net.md_5.bungee.api.chat.TextComponent; -+ -+import javax.swing.JTextPane; -+import javax.swing.Timer; -+import javax.swing.text.AttributeSet; -+import javax.swing.text.BadLocationException; -+import javax.swing.text.SimpleAttributeSet; -+import javax.swing.text.StyleConstants; -+import javax.swing.text.StyleContext; -+import java.util.Set; -+ -+public class JColorTextPane extends JTextPane { -+ private static final GUIColor DEFAULT_COLOR = GUIColor.BLACK; -+ -+ public void append(String msg) { -+ // TODO: update to use adventure instead -+ BaseComponent[] components = TextComponent.fromLegacyText(DEFAULT_COLOR.getCode() + msg, ChatColor.BLACK); -+ for (BaseComponent component : components) { -+ String text = component.toPlainText(); -+ if (text == null || text.isEmpty()) { -+ continue; -+ } -+ -+ GUIColor guiColor = GUIColor.getColor(component.getColor()); -+ -+ StyleContext context = StyleContext.getDefaultStyleContext(); -+ AttributeSet attr = context.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.Foreground, guiColor.getColor()); -+ attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Bold, component.isBold() || guiColor != DEFAULT_COLOR); -+ attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Italic, component.isItalic()); -+ attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Underline, component.isUnderlined()); -+ attr = context.addAttribute(attr, StyleConstants.CharacterConstants.StrikeThrough, component.isStrikethrough()); -+ //attr = context.addAttribute(attr, StyleConstants.CharacterConstants.Blink, component.isObfuscated()); // no such thing as Blink, sadly -+ -+ try { -+ int pos = getDocument().getLength(); -+ getDocument().insertString(pos, text, attr); -+ -+ if (component.isObfuscated()) { -+ // dirty hack to blink some text -+ Blink blink = new Blink(pos, text.length(), attr, context.addAttribute(attr, StyleConstants.Foreground, getBackground())); -+ BLINKS.add(blink); -+ } -+ } catch (BadLocationException ignore) { -+ } -+ } -+ } -+ -+ private static final Set BLINKS = Sets.newHashSet(); -+ private static boolean SYNC_BLINK; -+ -+ static { -+ new Timer(500, e -> { -+ SYNC_BLINK = !SYNC_BLINK; -+ BLINKS.forEach(Blink::blink); -+ }).start(); -+ } -+ -+ public class Blink { -+ private final int start, length; -+ private final AttributeSet attr1, attr2; -+ -+ private Blink(int start, int length, AttributeSet attr1, AttributeSet attr2) { -+ this.start = start; -+ this.length = length; -+ this.attr1 = attr1; -+ this.attr2 = attr2; -+ } -+ -+ private void blink() { -+ getStyledDocument().setCharacterAttributes(start, length, SYNC_BLINK ? attr1 : attr2, true); -+ } -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/gui/util/HighlightErrorConverter.java b/src/main/java/org/purpurmc/purpur/gui/util/HighlightErrorConverter.java -new file mode 100644 -index 0000000000000000000000000000000000000000..15a226e3854d731f7724025ea3459c8ace07630c ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/gui/util/HighlightErrorConverter.java -@@ -0,0 +1,85 @@ -+package org.purpurmc.purpur.gui.util; -+ -+import org.apache.logging.log4j.Level; -+import org.apache.logging.log4j.core.LogEvent; -+import org.apache.logging.log4j.core.config.Configuration; -+import org.apache.logging.log4j.core.config.plugins.Plugin; -+import org.apache.logging.log4j.core.layout.PatternLayout; -+import org.apache.logging.log4j.core.pattern.ConverterKeys; -+import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; -+import org.apache.logging.log4j.core.pattern.PatternConverter; -+import org.apache.logging.log4j.core.pattern.PatternFormatter; -+import org.apache.logging.log4j.core.pattern.PatternParser; -+import org.apache.logging.log4j.util.PerformanceSensitive; -+ -+import java.util.List; -+ -+@Plugin(name = "highlightGUIError", category = PatternConverter.CATEGORY) -+@ConverterKeys({"highlightGUIError"}) -+@PerformanceSensitive("allocation") -+public final class HighlightErrorConverter extends LogEventPatternConverter { -+ private static final String ERROR = "\u00A74\u00A7l"; // Bold Red -+ private static final String WARN = "\u00A7e\u00A7l"; // Bold Yellow -+ -+ private final List formatters; -+ -+ private HighlightErrorConverter(List formatters) { -+ super("highlightGUIError", null); -+ this.formatters = formatters; -+ } -+ -+ @Override -+ public void format(LogEvent event, StringBuilder toAppendTo) { -+ Level level = event.getLevel(); -+ if (level.isMoreSpecificThan(Level.ERROR)) { -+ format(ERROR, event, toAppendTo); -+ return; -+ } else if (level.isMoreSpecificThan(Level.WARN)) { -+ format(WARN, event, toAppendTo); -+ return; -+ } -+ for (PatternFormatter formatter : formatters) { -+ formatter.format(event, toAppendTo); -+ } -+ } -+ -+ private void format(String style, LogEvent event, StringBuilder toAppendTo) { -+ int start = toAppendTo.length(); -+ toAppendTo.append(style); -+ int end = toAppendTo.length(); -+ -+ for (PatternFormatter formatter : formatters) { -+ formatter.format(event, toAppendTo); -+ } -+ -+ if (toAppendTo.length() == end) { -+ toAppendTo.setLength(start); -+ } -+ } -+ -+ @Override -+ public boolean handlesThrowable() { -+ for (final PatternFormatter formatter : formatters) { -+ if (formatter.handlesThrowable()) { -+ return true; -+ } -+ } -+ return false; -+ } -+ -+ public static HighlightErrorConverter newInstance(Configuration config, String[] options) { -+ if (options.length != 1) { -+ LOGGER.error("Incorrect number of options on highlightGUIError. Expected 1 received " + options.length); -+ return null; -+ } -+ -+ if (options[0] == null) { -+ LOGGER.error("No pattern supplied on highlightGUIError"); -+ return null; -+ } -+ -+ PatternParser parser = PatternLayout.createPatternParser(config); -+ List formatters = parser.parse(options[0]); -+ return new HighlightErrorConverter(formatters); -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/item/GlowBerryItem.java b/src/main/java/org/purpurmc/purpur/item/GlowBerryItem.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7f526883495b3222746de3d0442e9e4fb5107036 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/item/GlowBerryItem.java -@@ -0,0 +1,26 @@ -+package org.purpurmc.purpur.item; -+ -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.world.effect.MobEffectInstance; -+import net.minecraft.world.effect.MobEffects; -+import net.minecraft.world.entity.LivingEntity; -+import net.minecraft.world.item.ItemNameBlockItem; -+import net.minecraft.world.item.ItemStack; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.Block; -+import org.bukkit.event.entity.EntityPotionEffectEvent; -+ -+public class GlowBerryItem extends ItemNameBlockItem { -+ public GlowBerryItem(Block block, Properties settings) { -+ super(block, settings); -+ } -+ -+ @Override -+ public ItemStack finishUsingItem(ItemStack stack, Level world, LivingEntity user) { -+ ItemStack result = super.finishUsingItem(stack, world, user); -+ if (world.purpurConfig.glowBerriesEatGlowDuration > 0 && user instanceof ServerPlayer player) { -+ player.addEffect(new MobEffectInstance(MobEffects.GLOWING, world.purpurConfig.glowBerriesEatGlowDuration), EntityPotionEffectEvent.Cause.FOOD); -+ } -+ return result; -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/item/SpawnerItem.java b/src/main/java/org/purpurmc/purpur/item/SpawnerItem.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c038fb2bbb0f0e78380bc24bbd6348b869669a90 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/item/SpawnerItem.java -@@ -0,0 +1,36 @@ -+package org.purpurmc.purpur.item; -+ -+import net.minecraft.core.BlockPos; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.world.entity.EntityType; -+import net.minecraft.world.entity.player.Player; -+import net.minecraft.world.item.BlockItem; -+import net.minecraft.world.item.ItemStack; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.Block; -+import net.minecraft.world.level.block.entity.BlockEntity; -+import net.minecraft.world.level.block.entity.SpawnerBlockEntity; -+import net.minecraft.world.level.block.state.BlockState; -+ -+public class SpawnerItem extends BlockItem { -+ -+ public SpawnerItem(Block block, Properties settings) { -+ super(block, settings); -+ } -+ -+ @Override -+ protected boolean updateCustomBlockEntityTag(BlockPos pos, Level level, Player player, ItemStack stack, BlockState state) { -+ boolean handled = super.updateCustomBlockEntityTag(pos, level, player, stack, state); -+ if (level.purpurConfig.silkTouchEnabled && player.getBukkitEntity().hasPermission("purpur.place.spawners")) { -+ BlockEntity spawner = level.getBlockEntity(pos); -+ if (spawner instanceof SpawnerBlockEntity && stack.hasTag()) { -+ CompoundTag tag = stack.getTag(); -+ if (tag.contains("Purpur.mob_type")) { -+ EntityType.byString(tag.getString("Purpur.mob_type")).ifPresent(type -> -+ ((SpawnerBlockEntity) spawner).getSpawner().setEntityId(type, level, level.random, pos)); -+ } -+ } -+ } -+ return handled; -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/task/BeehiveTask.java b/src/main/java/org/purpurmc/purpur/task/BeehiveTask.java -new file mode 100644 -index 0000000000000000000000000000000000000000..055dd307e9d5ac0d4623c961164c84bab1edd3bd ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/task/BeehiveTask.java -@@ -0,0 +1,81 @@ -+package org.purpurmc.purpur.task; -+ -+import com.google.common.io.ByteArrayDataInput; -+import com.google.common.io.ByteArrayDataOutput; -+import com.google.common.io.ByteStreams; -+import io.netty.buffer.Unpooled; -+import net.minecraft.core.BlockPos; -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.network.protocol.game.ClientboundCustomPayloadPacket; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.world.level.block.entity.BeehiveBlockEntity; -+import net.minecraft.world.level.block.entity.BlockEntity; -+import org.bukkit.Bukkit; -+import org.bukkit.craftbukkit.entity.CraftPlayer; -+import org.bukkit.craftbukkit.scheduler.MinecraftInternalPlugin; -+import org.bukkit.entity.Player; -+import org.bukkit.plugin.PluginBase; -+import org.bukkit.plugin.messaging.PluginMessageListener; -+import org.jetbrains.annotations.NotNull; -+ -+public class BeehiveTask implements PluginMessageListener { -+ public static final ResourceLocation BEEHIVE_C2S = new ResourceLocation("purpur", "beehive_c2s"); -+ public static final ResourceLocation BEEHIVE_S2C = new ResourceLocation("purpur", "beehive_s2c"); -+ -+ private static BeehiveTask instance; -+ -+ public static BeehiveTask instance() { -+ if (instance == null) { -+ instance = new BeehiveTask(); -+ } -+ return instance; -+ } -+ -+ private final PluginBase plugin = new MinecraftInternalPlugin(); -+ -+ private BeehiveTask() { -+ } -+ -+ public void register() { -+ Bukkit.getMessenger().registerOutgoingPluginChannel(this.plugin, BEEHIVE_S2C.toString()); -+ Bukkit.getMessenger().registerIncomingPluginChannel(this.plugin, BEEHIVE_C2S.toString(), this); -+ } -+ -+ public void unregister() { -+ Bukkit.getMessenger().unregisterOutgoingPluginChannel(this.plugin, BEEHIVE_S2C.toString()); -+ Bukkit.getMessenger().unregisterIncomingPluginChannel(this.plugin, BEEHIVE_C2S.toString()); -+ } -+ -+ @Override -+ public void onPluginMessageReceived(@NotNull String channel, Player player, byte[] bytes) { -+ ByteArrayDataInput in = in(bytes); -+ long packedPos = in.readLong(); -+ BlockPos pos = BlockPos.of(packedPos); -+ -+ ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); -+ -+ BlockEntity blockEntity = serverPlayer.level.getBlockEntity(pos); -+ if (!(blockEntity instanceof BeehiveBlockEntity beehive)) { -+ return; -+ } -+ -+ ByteArrayDataOutput out = out(); -+ -+ out.writeInt(beehive.getOccupantCount()); -+ out.writeLong(packedPos); -+ -+ FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.wrappedBuffer(out.toByteArray())); -+ serverPlayer.connection.send(new ClientboundCustomPayloadPacket(BEEHIVE_S2C, buf)); -+ } -+ -+ @SuppressWarnings("UnstableApiUsage") -+ private static ByteArrayDataOutput out() { -+ return ByteStreams.newDataOutput(); -+ } -+ -+ @SuppressWarnings("UnstableApiUsage") -+ private static ByteArrayDataInput in(byte[] bytes) { -+ return ByteStreams.newDataInput(bytes); -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/task/BossBarTask.java b/src/main/java/org/purpurmc/purpur/task/BossBarTask.java -new file mode 100644 -index 0000000000000000000000000000000000000000..114f273dd7f8b8a3c02f0651f6944859b33a65d4 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/task/BossBarTask.java -@@ -0,0 +1,121 @@ -+package org.purpurmc.purpur.task; -+ -+import net.kyori.adventure.bossbar.BossBar; -+import net.minecraft.server.level.ServerPlayer; -+import org.bukkit.Bukkit; -+import org.bukkit.craftbukkit.scheduler.MinecraftInternalPlugin; -+import org.bukkit.entity.Player; -+import org.bukkit.scheduler.BukkitRunnable; -+ -+import java.util.HashMap; -+import java.util.HashSet; -+import java.util.Iterator; -+import java.util.Map; -+import java.util.UUID; -+ -+public abstract class BossBarTask extends BukkitRunnable { -+ private final Map bossbars = new HashMap<>(); -+ private boolean started; -+ -+ abstract BossBar createBossBar(); -+ -+ abstract void updateBossBar(BossBar bossbar, Player player); -+ -+ @Override -+ public void run() { -+ Iterator> iter = bossbars.entrySet().iterator(); -+ while (iter.hasNext()) { -+ Map.Entry entry = iter.next(); -+ Player player = Bukkit.getPlayer(entry.getKey()); -+ if (player == null) { -+ iter.remove(); -+ continue; -+ } -+ updateBossBar(entry.getValue(), player); -+ } -+ } -+ -+ @Override -+ public void cancel() { -+ super.cancel(); -+ new HashSet<>(this.bossbars.keySet()).forEach(uuid -> { -+ Player player = Bukkit.getPlayer(uuid); -+ if (player != null) { -+ removePlayer(player); -+ } -+ }); -+ this.bossbars.clear(); -+ } -+ -+ public boolean removePlayer(Player player) { -+ BossBar bossbar = this.bossbars.remove(player.getUniqueId()); -+ if (bossbar != null) { -+ player.hideBossBar(bossbar); -+ return true; -+ } -+ return false; -+ } -+ -+ public void addPlayer(Player player) { -+ removePlayer(player); -+ BossBar bossbar = createBossBar(); -+ this.bossbars.put(player.getUniqueId(), bossbar); -+ this.updateBossBar(bossbar, player); -+ player.showBossBar(bossbar); -+ } -+ -+ public boolean hasPlayer(UUID uuid) { -+ return this.bossbars.containsKey(uuid); -+ } -+ -+ public boolean togglePlayer(Player player) { -+ if (removePlayer(player)) { -+ return false; -+ } -+ addPlayer(player); -+ return true; -+ } -+ -+ public void start() { -+ stop(); -+ this.runTaskTimerAsynchronously(new MinecraftInternalPlugin(), 1, 1); -+ started = true; -+ } -+ -+ public void stop() { -+ if (started) { -+ cancel(); -+ } -+ } -+ -+ public static void startAll() { -+ RamBarTask.instance().start(); -+ TPSBarTask.instance().start(); -+ CompassTask.instance().start(); -+ } -+ -+ public static void stopAll() { -+ RamBarTask.instance().stop(); -+ TPSBarTask.instance().stop(); -+ CompassTask.instance().stop(); -+ } -+ -+ public static void addToAll(ServerPlayer player) { -+ Player bukkit = player.getBukkitEntity(); -+ if (player.ramBar()) { -+ RamBarTask.instance().addPlayer(bukkit); -+ } -+ if (player.tpsBar()) { -+ TPSBarTask.instance().addPlayer(bukkit); -+ } -+ if (player.compassBar()) { -+ CompassTask.instance().addPlayer(bukkit); -+ } -+ } -+ -+ public static void removeFromAll(Player player) { -+ RamBarTask.instance().removePlayer(player); -+ TPSBarTask.instance().removePlayer(player); -+ CompassTask.instance().removePlayer(player); -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/task/CompassTask.java b/src/main/java/org/purpurmc/purpur/task/CompassTask.java -new file mode 100644 -index 0000000000000000000000000000000000000000..bece7eefc8ba8822b433835526251d2fb916c025 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/task/CompassTask.java -@@ -0,0 +1,68 @@ -+package org.purpurmc.purpur.task; -+ -+import net.kyori.adventure.bossbar.BossBar; -+import net.kyori.adventure.text.Component; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.world.item.Items; -+import org.bukkit.entity.Player; -+import org.purpurmc.purpur.PurpurConfig; -+ -+public class CompassTask extends BossBarTask { -+ private static CompassTask instance; -+ -+ private int tick = 0; -+ -+ public static CompassTask instance() { -+ if (instance == null) { -+ instance = new CompassTask(); -+ } -+ return instance; -+ } -+ -+ @Override -+ public void run() { -+ if (++tick < PurpurConfig.commandCompassBarTickInterval) { -+ return; -+ } -+ tick = 0; -+ -+ MinecraftServer.getServer().getAllLevels().forEach((level) -> { -+ if (level.purpurConfig.compassItemShowsBossBar) { -+ level.players().forEach(player -> { -+ if (!player.compassBar()) { -+ if (player.getMainHandItem().getItem() != Items.COMPASS && player.getOffhandItem().getItem() != Items.COMPASS) { -+ removePlayer(player.getBukkitEntity()); -+ } else if (!hasPlayer(player.getUUID())) { -+ addPlayer(player.getBukkitEntity()); -+ } -+ } -+ }); -+ } -+ }); -+ -+ super.run(); -+ } -+ -+ @Override -+ BossBar createBossBar() { -+ return BossBar.bossBar(Component.text(""), PurpurConfig.commandCompassBarProgressPercent, PurpurConfig.commandCompassBarProgressColor, PurpurConfig.commandCompassBarProgressOverlay); -+ } -+ -+ @Override -+ void updateBossBar(BossBar bossbar, Player player) { -+ float yaw = player.getLocation().getYaw(); -+ int length = PurpurConfig.commandCompassBarTitle.length(); -+ int pos = (int) ((normalize(yaw) * (length / 720F)) + (length / 2F)); -+ bossbar.name(Component.text(PurpurConfig.commandCompassBarTitle.substring(pos - 25, pos + 25))); -+ } -+ -+ private float normalize(float yaw) { -+ while (yaw < -180.0F) { -+ yaw += 360.0F; -+ } -+ while (yaw > 180.0F) { -+ yaw -= 360.0F; -+ } -+ return yaw; -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/task/RamBarTask.java b/src/main/java/org/purpurmc/purpur/task/RamBarTask.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8e98c0ae73e2c40002a72b5d0d246ffa0c3ab38f ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/task/RamBarTask.java -@@ -0,0 +1,117 @@ -+package org.purpurmc.purpur.task; -+ -+import net.kyori.adventure.bossbar.BossBar; -+import net.kyori.adventure.text.Component; -+import net.kyori.adventure.text.minimessage.MiniMessage; -+import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; -+import org.bukkit.entity.Player; -+import org.purpurmc.purpur.PurpurConfig; -+ -+import java.lang.management.ManagementFactory; -+import java.lang.management.MemoryUsage; -+ -+public class RamBarTask extends BossBarTask { -+ private static RamBarTask instance; -+ private long allocated = 0L; -+ private long used = 0L; -+ private long xmx = 0L; -+ private long xms = 0L; -+ private float percent = 0F; -+ private int tick = 0; -+ -+ public static RamBarTask instance() { -+ if (instance == null) { -+ instance = new RamBarTask(); -+ } -+ return instance; -+ } -+ -+ @Override -+ BossBar createBossBar() { -+ return BossBar.bossBar(Component.text(""), 0.0F, instance().getBossBarColor(), PurpurConfig.commandRamBarProgressOverlay); -+ } -+ -+ @Override -+ void updateBossBar(BossBar bossbar, Player player) { -+ bossbar.progress(getBossBarProgress()); -+ bossbar.color(getBossBarColor()); -+ bossbar.name(MiniMessage.miniMessage().deserialize(PurpurConfig.commandRamBarTitle, -+ Placeholder.component("allocated", format(this.allocated)), -+ Placeholder.component("used", format(this.used)), -+ Placeholder.component("xmx", format(this.xmx)), -+ Placeholder.component("xms", format(this.xms)), -+ Placeholder.unparsed("percent", ((int) (this.percent * 100)) + "%") -+ )); -+ } -+ -+ @Override -+ public void run() { -+ if (++this.tick < PurpurConfig.commandRamBarTickInterval) { -+ return; -+ } -+ this.tick = 0; -+ -+ MemoryUsage heap = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage(); -+ -+ this.allocated = heap.getCommitted(); -+ this.used = heap.getUsed(); -+ this.xmx = heap.getMax(); -+ this.xms = heap.getInit(); -+ this.percent = Math.max(Math.min((float) this.used / this.xmx, 1.0F), 0.0F); -+ -+ super.run(); -+ } -+ -+ private float getBossBarProgress() { -+ return this.percent; -+ } -+ -+ private BossBar.Color getBossBarColor() { -+ if (this.percent < 0.5F) { -+ return PurpurConfig.commandRamBarProgressColorGood; -+ } else if (this.percent < 0.75F) { -+ return PurpurConfig.commandRamBarProgressColorMedium; -+ } else { -+ return PurpurConfig.commandRamBarProgressColorLow; -+ } -+ } -+ -+ public Component format(long v) { -+ String color; -+ if (this.percent < 0.60F) { -+ color = PurpurConfig.commandRamBarTextColorGood; -+ } else if (this.percent < 0.85F) { -+ color = PurpurConfig.commandRamBarTextColorMedium; -+ } else { -+ color = PurpurConfig.commandRamBarTextColorLow; -+ } -+ String value; -+ if (v < 1024) { -+ value = v + "B"; -+ } else { -+ int z = (63 - Long.numberOfLeadingZeros(v)) / 10; -+ value = String.format("%.1f%s", (double) v / (1L << (z * 10)), "BKMGTPE".charAt(z)); -+ } -+ return MiniMessage.miniMessage().deserialize(color, Placeholder.unparsed("text", value)); -+ } -+ -+ public long getAllocated() { -+ return this.allocated; -+ } -+ -+ public long getUsed() { -+ return this.used; -+ } -+ -+ public long getXmx() { -+ return this.xmx; -+ } -+ -+ public long getXms() { -+ return this.xms; -+ } -+ -+ public float getPercent() { -+ return this.percent; -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/task/TPSBarTask.java b/src/main/java/org/purpurmc/purpur/task/TPSBarTask.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8769993e7ca59da309087051a3cd38fc562c15d1 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/task/TPSBarTask.java -@@ -0,0 +1,142 @@ -+package org.purpurmc.purpur.task; -+ -+import net.kyori.adventure.bossbar.BossBar; -+import net.kyori.adventure.text.Component; -+import net.kyori.adventure.text.minimessage.MiniMessage; -+import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; -+import org.purpurmc.purpur.PurpurConfig; -+import org.bukkit.Bukkit; -+import org.bukkit.entity.Player; -+ -+public class TPSBarTask extends BossBarTask { -+ private static TPSBarTask instance; -+ private double tps = 20.0D; -+ private double mspt = 0.0D; -+ private int tick = 0; -+ -+ public static TPSBarTask instance() { -+ if (instance == null) { -+ instance = new TPSBarTask(); -+ } -+ return instance; -+ } -+ -+ @Override -+ BossBar createBossBar() { -+ return BossBar.bossBar(Component.text(""), 0.0F, instance().getBossBarColor(), PurpurConfig.commandTPSBarProgressOverlay); -+ } -+ -+ @Override -+ void updateBossBar(BossBar bossbar, Player player) { -+ bossbar.progress(getBossBarProgress()); -+ bossbar.color(getBossBarColor()); -+ bossbar.name(MiniMessage.miniMessage().deserialize(PurpurConfig.commandTPSBarTitle, -+ Placeholder.component("tps", getTPSColor()), -+ Placeholder.component("mspt", getMSPTColor()), -+ Placeholder.component("ping", getPingColor(player.getPing())) -+ )); -+ } -+ -+ @Override -+ public void run() { -+ if (++tick < PurpurConfig.commandTPSBarTickInterval) { -+ return; -+ } -+ tick = 0; -+ -+ this.tps = Math.max(Math.min(Bukkit.getTPS()[0], 20.0D), 0.0D); -+ this.mspt = Bukkit.getAverageTickTime(); -+ -+ super.run(); -+ } -+ -+ private float getBossBarProgress() { -+ if (PurpurConfig.commandTPSBarProgressFillMode == FillMode.MSPT) { -+ return Math.max(Math.min((float) mspt / 50.0F, 1.0F), 0.0F); -+ } else { -+ return Math.max(Math.min((float) tps / 20.0F, 1.0F), 0.0F); -+ } -+ } -+ -+ private BossBar.Color getBossBarColor() { -+ if (isGood(PurpurConfig.commandTPSBarProgressFillMode)) { -+ return PurpurConfig.commandTPSBarProgressColorGood; -+ } else if (isMedium(PurpurConfig.commandTPSBarProgressFillMode)) { -+ return PurpurConfig.commandTPSBarProgressColorMedium; -+ } else { -+ return PurpurConfig.commandTPSBarProgressColorLow; -+ } -+ } -+ -+ private boolean isGood(FillMode mode) { -+ return isGood(mode, 0); -+ } -+ -+ private boolean isGood(FillMode mode, int ping) { -+ if (mode == FillMode.MSPT) { -+ return mspt < 40; -+ } else if (mode == FillMode.TPS) { -+ return tps >= 19; -+ } else if (mode == FillMode.PING) { -+ return ping < 100; -+ } else { -+ return false; -+ } -+ } -+ -+ private boolean isMedium(FillMode mode) { -+ return isMedium(mode, 0); -+ } -+ -+ private boolean isMedium(FillMode mode, int ping) { -+ if (mode == FillMode.MSPT) { -+ return mspt < 50; -+ } else if (mode == FillMode.TPS) { -+ return tps >= 15; -+ } else if (mode == FillMode.PING) { -+ return ping < 200; -+ } else { -+ return false; -+ } -+ } -+ -+ private Component getTPSColor() { -+ String color; -+ if (isGood(FillMode.TPS)) { -+ color = PurpurConfig.commandTPSBarTextColorGood; -+ } else if (isMedium(FillMode.TPS)) { -+ color = PurpurConfig.commandTPSBarTextColorMedium; -+ } else { -+ color = PurpurConfig.commandTPSBarTextColorLow; -+ } -+ return MiniMessage.miniMessage().deserialize(color, Placeholder.parsed("text", String.format("%.2f", tps))); -+ } -+ -+ private Component getMSPTColor() { -+ String color; -+ if (isGood(FillMode.MSPT)) { -+ color = PurpurConfig.commandTPSBarTextColorGood; -+ } else if (isMedium(FillMode.MSPT)) { -+ color = PurpurConfig.commandTPSBarTextColorMedium; -+ } else { -+ color = PurpurConfig.commandTPSBarTextColorLow; -+ } -+ return MiniMessage.miniMessage().deserialize(color, Placeholder.parsed("text", String.format("%.2f", mspt))); -+ } -+ -+ private Component getPingColor(int ping) { -+ String color; -+ if (isGood(FillMode.PING, ping)) { -+ color = PurpurConfig.commandTPSBarTextColorGood; -+ } else if (isMedium(FillMode.PING, ping)) { -+ color = PurpurConfig.commandTPSBarTextColorMedium; -+ } else { -+ color = PurpurConfig.commandTPSBarTextColorLow; -+ } -+ return MiniMessage.miniMessage().deserialize(color, Placeholder.parsed("text", String.format("%s", ping))); -+ } -+ -+ public enum FillMode { -+ TPS, MSPT, PING -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/tool/Actionable.java b/src/main/java/org/purpurmc/purpur/tool/Actionable.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e18c37f06730da9d3055d5215e813b1477c1e70e ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/tool/Actionable.java -@@ -0,0 +1,24 @@ -+package org.purpurmc.purpur.tool; -+ -+import net.minecraft.world.item.Item; -+import net.minecraft.world.level.block.Block; -+ -+import java.util.Map; -+ -+public abstract class Actionable { -+ private final Block into; -+ private final Map drops; -+ -+ public Actionable(Block into, Map drops) { -+ this.into = into; -+ this.drops = drops; -+ } -+ -+ public Block into() { -+ return into; -+ } -+ -+ public Map drops() { -+ return drops; -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/tool/Strippable.java b/src/main/java/org/purpurmc/purpur/tool/Strippable.java -new file mode 100644 -index 0000000000000000000000000000000000000000..bf5402214f41af9c09bd6c5c4f45d330516d742e ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/tool/Strippable.java -@@ -0,0 +1,12 @@ -+package org.purpurmc.purpur.tool; -+ -+import net.minecraft.world.item.Item; -+import net.minecraft.world.level.block.Block; -+ -+import java.util.Map; -+ -+public class Strippable extends Actionable { -+ public Strippable(Block into, Map drops) { -+ super(into, drops); -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/tool/Tillable.java b/src/main/java/org/purpurmc/purpur/tool/Tillable.java -new file mode 100644 -index 0000000000000000000000000000000000000000..715f6dd44480347eebced43c11bc364e05727498 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/tool/Tillable.java -@@ -0,0 +1,50 @@ -+package org.purpurmc.purpur.tool; -+ -+import net.minecraft.world.item.HoeItem; -+import net.minecraft.world.item.Item; -+import net.minecraft.world.item.context.UseOnContext; -+import net.minecraft.world.level.block.Block; -+ -+import java.util.HashMap; -+import java.util.Map; -+import java.util.function.Predicate; -+ -+public class Tillable extends Actionable { -+ private final Condition condition; -+ -+ public Tillable(Condition condition, Block into, Map drops) { -+ super(into, drops); -+ this.condition = condition; -+ } -+ -+ public Condition condition() { -+ return condition; -+ } -+ -+ public enum Condition { -+ AIR_ABOVE(HoeItem::onlyIfAirAbove), -+ ALWAYS((useOnContext) -> true); -+ -+ private final Predicate predicate; -+ -+ Condition(Predicate predicate) { -+ this.predicate = predicate; -+ } -+ -+ public Predicate predicate() { -+ return predicate; -+ } -+ -+ private static final Map BY_NAME = new HashMap<>(); -+ -+ static { -+ for (Condition condition : values()) { -+ BY_NAME.put(condition.name(), condition); -+ } -+ } -+ -+ public static Condition get(String name) { -+ return BY_NAME.get(name.toUpperCase(java.util.Locale.ROOT)); -+ } -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/tool/Waxable.java b/src/main/java/org/purpurmc/purpur/tool/Waxable.java -new file mode 100644 -index 0000000000000000000000000000000000000000..64adb13b29b6757dcf227a55588da70ecabe083f ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/tool/Waxable.java -@@ -0,0 +1,12 @@ -+package org.purpurmc.purpur.tool; -+ -+import net.minecraft.world.item.Item; -+import net.minecraft.world.level.block.Block; -+ -+import java.util.Map; -+ -+public class Waxable extends Actionable { -+ public Waxable(Block into, Map drops) { -+ super(into, drops); -+ } -+} -diff --git a/src/main/java/org/purpurmc/purpur/tool/Weatherable.java b/src/main/java/org/purpurmc/purpur/tool/Weatherable.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b7586f494528f30eb0da82420d3bcf5b83a1a902 ---- /dev/null -+++ b/src/main/java/org/purpurmc/purpur/tool/Weatherable.java -@@ -0,0 +1,12 @@ -+package org.purpurmc.purpur.tool; -+ -+import net.minecraft.world.item.Item; -+import net.minecraft.world.level.block.Block; -+ -+import java.util.Map; -+ -+public class Weatherable extends Actionable { -+ public Weatherable(Block into, Map drops) { -+ super(into, drops); -+ } -+} -diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java -index ff247e28fef12d7cd18175b65811fb97a2828dad..a564fefb3ecf49e130247a4decddbec89ae41ee7 100644 ---- a/src/main/java/org/spigotmc/ActivationRange.java -+++ b/src/main/java/org/spigotmc/ActivationRange.java -@@ -15,6 +15,7 @@ import net.minecraft.world.entity.ambient.AmbientCreature; - import net.minecraft.world.entity.animal.Animal; - import net.minecraft.world.entity.animal.Bee; - import net.minecraft.world.entity.animal.Sheep; -+import net.minecraft.world.entity.animal.Squid; - import net.minecraft.world.entity.animal.WaterAnimal; - import net.minecraft.world.entity.animal.horse.Llama; - import net.minecraft.world.entity.boss.EnderDragonPart; -@@ -217,6 +218,7 @@ public class ActivationRange - continue; - } - -+ if (!player.level.purpurConfig.idleTimeoutTickNearbyEntities && player.isAfk()) continue; // Purpur - // Paper start - int worldHeight = world.getHeight(); - ActivationRange.maxBB = player.getBoundingBox().inflate( maxRange, worldHeight, maxRange ); -@@ -410,6 +412,7 @@ public class ActivationRange - */ - public static boolean checkIfActive(Entity entity) - { -+ if (entity.level.purpurConfig.squidImmuneToEAR && entity instanceof Squid) return true; // Purpur - // Never safe to skip fireworks or entities not yet added to chunk - if ( entity instanceof FireworkRocketEntity ) { - return true; -diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml -index 74ccc67e3c12dc5182602fb691ef3ddeb5b53280..52af11926a1f7973d70a1dae191d2e8138ec5c94 100644 ---- a/src/main/resources/log4j2.xml -+++ b/src/main/resources/log4j2.xml -@@ -2,7 +2,16 @@ - - - -- -+ -+ -+ -+ -+ -+ -+ -+ -+ - - - -diff --git a/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java b/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java -index b2d510459bcf90a3611f3d91dae4ccc3d29b4079..7a052f6deaa30f8a177a2aaf172f9da6c308a22b 100644 ---- a/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java -+++ b/src/test/java/com/destroystokyo/paper/entity/ai/VanillaMobGoalTest.java -@@ -37,7 +37,7 @@ public class VanillaMobGoalTest { - } - - List> classes; -- try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("net.minecraft").scan()) { -+ try (ScanResult scanResult = new ClassGraph().enableAllInfo().whitelistPackages("net.minecraft", "org.purpurmc.purpur.entity.ai").scan()) { // Purpur - classes = scanResult.getSubclasses(net.minecraft.world.entity.ai.goal.Goal.class.getName()).loadClasses(); - } - -diff --git a/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java b/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java -index 8665e2740aedcc2895b0e2c44ebaba53d2a40568..b7e2227116ee0a08826674d8681fdaac97efb0ea 100644 ---- a/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java -+++ b/src/test/java/io/papermc/paper/permissions/MinecraftCommandPermissionsTest.java -@@ -45,6 +45,7 @@ public class MinecraftCommandPermissionsTest extends AbstractTestingBase { - Set foundPerms = new HashSet<>(); - for (CommandNode child : root.getChildren()) { - final String vanillaPerm = VanillaCommandWrapper.getPermission(child); -+ if (TO_SKIP.contains(vanillaPerm)) continue; // Purpur - if (!perms.contains(vanillaPerm)) { - missing.add("Missing permission for " + child.getName() + " (" + vanillaPerm + ") command"); - } else { -@@ -57,6 +58,25 @@ public class MinecraftCommandPermissionsTest extends AbstractTestingBase { - } - - private static final List TO_SKIP = List.of( -+ // Purpur start -+ "minecraft.command.compass", -+ "minecraft.command.credits", -+ "minecraft.command.demo", -+ "minecraft.command.ping", -+ "minecraft.command.ram", -+ "minecraft.command.rambar", -+ "minecraft.command.tpsbar", -+ "minecraft.command.uptime", -+ "minecraft.command.debug", -+ "minecraft.command.gamemode.adventure", -+ "minecraft.command.gamemode.adventure.other", -+ "minecraft.command.gamemode.creative", -+ "minecraft.command.gamemode.creative.other", -+ "minecraft.command.gamemode.spectator", -+ "minecraft.command.gamemode.spectator.other", -+ "minecraft.command.gamemode.survival", -+ "minecraft.command.gamemode.survival.other", -+ // Purpur end - "minecraft.command.selector" - ); - -diff --git a/src/test/java/org/bukkit/potion/PotionTest.java b/src/test/java/org/bukkit/potion/PotionTest.java -index 83226ec2fa977819e12a499eb3765232543c17b3..a742774dabaee0629f4e6adabee5f3ec4b3be41c 100644 ---- a/src/test/java/org/bukkit/potion/PotionTest.java -+++ b/src/test/java/org/bukkit/potion/PotionTest.java -@@ -9,6 +9,7 @@ import net.minecraft.resources.ResourceLocation; - import net.minecraft.world.effect.MobEffect; - import net.minecraft.world.effect.MobEffectInstance; - import net.minecraft.world.item.alchemy.Potion; -+import org.bukkit.NamespacedKey; - import org.bukkit.support.AbstractTestingBase; - import org.junit.Test; - -@@ -47,4 +48,27 @@ public class PotionTest extends AbstractTestingBase { - assertEquals("Same type not returned by name " + key, bukkit, byName); - } - } -+ -+ // Purpur start -+ @Test -+ public void testNamespacedKey() { -+ NamespacedKey key = new NamespacedKey("testnamespace", "testkey"); -+ PotionEffect namedSpacedEffect = new PotionEffect(PotionEffectType.DOLPHINS_GRACE, 20, 0, true, true, true, key); -+ assertNotNull(namedSpacedEffect.getKey()); -+ assertTrue(namedSpacedEffect.hasKey()); -+ assertFalse(namedSpacedEffect.withKey(null).hasKey()); -+ -+ PotionEffect effect = new PotionEffect(PotionEffectType.DOLPHINS_GRACE, 20, 0, true, true, true); -+ assertNull(effect.getKey()); -+ assertFalse(effect.hasKey()); -+ assertTrue(namedSpacedEffect.withKey(key).hasKey()); -+ -+ Map s1 = namedSpacedEffect.serialize(); -+ Map s2 = effect.serialize(); -+ assertTrue(s1.containsKey("namespacedKey")); -+ assertFalse(s2.containsKey("namespacedKey")); -+ assertNotNull(new PotionEffect(s1).getKey()); -+ assertNull(new PotionEffect(s2).getKey()); -+ } -+ // Purpur end - } diff --git a/patches/server/0011-Remove-Mojang-username-check.patch b/patches/server/0009-Remove-Mojang-username-check.patch similarity index 95% rename from patches/server/0011-Remove-Mojang-username-check.patch rename to patches/server/0009-Remove-Mojang-username-check.patch index 0945d1fb..4c898f20 100644 --- a/patches/server/0011-Remove-Mojang-username-check.patch +++ b/patches/server/0009-Remove-Mojang-username-check.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Remove Mojang username check diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index 5ed89ce9d2c29927f48c1f7f8f9288f39d68b56c..30371f1a6dfb6fc170ac0cbdb3a7c78c837429c2 100644 +index 5b7c12db86be64433c65e31e3ecc0b444b0ddf48..cb8c4f91663c8653a1918a3864c17171c70ebe78 100644 --- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java @@ -37,6 +37,7 @@ import net.minecraft.util.Crypt; @@ -16,7 +16,7 @@ index 5ed89ce9d2c29927f48c1f7f8f9288f39d68b56c..30371f1a6dfb6fc170ac0cbdb3a7c78c import org.galemc.gale.configuration.GaleGlobalConfiguration; import org.slf4j.Logger; -@@ -245,10 +246,10 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, +@@ -243,10 +244,10 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, public void handleHello(ServerboundHelloPacket packet) { // Gale start - JettPack - reduce array allocations Validate.validState(this.state == ServerLoginPacketListenerImpl.State.HELLO, "Unexpected hello packet", ArrayConstants.emptyObjectArray); diff --git a/patches/server/0012-Remove-Spigot-Check-for-Broken-BungeeCord-Configurat.patch b/patches/server/0010-Remove-Spigot-Check-for-Broken-BungeeCord-Configurat.patch similarity index 96% rename from patches/server/0012-Remove-Spigot-Check-for-Broken-BungeeCord-Configurat.patch rename to patches/server/0010-Remove-Spigot-Check-for-Broken-BungeeCord-Configurat.patch index 40a3b820..6d16d24c 100644 --- a/patches/server/0012-Remove-Spigot-Check-for-Broken-BungeeCord-Configurat.patch +++ b/patches/server/0010-Remove-Spigot-Check-for-Broken-BungeeCord-Configurat.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Remove Spigot Check for Broken BungeeCord Configurations diff --git a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -index f3bbfb472b9be083dd1f1a317b90dda5c3c4f851..ff24597a21d2fb6b4143679e18e0a89d2e2d3914 100644 +index 2821de09a36fc315897129f4691ba713386737db..3f60c1e5bf49784ac2a812157a5d22ce28e34ed6 100644 --- a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java @@ -11,6 +11,7 @@ import net.minecraft.network.protocol.handshake.ServerHandshakePacketListener; diff --git a/patches/server/0013-Remove-Paper-s-Fix-tripwire-state-inconsistency.patch b/patches/server/0011-Remove-Paper-s-Fix-tripwire-state-inconsistency.patch similarity index 100% rename from patches/server/0013-Remove-Paper-s-Fix-tripwire-state-inconsistency.patch rename to patches/server/0011-Remove-Paper-s-Fix-tripwire-state-inconsistency.patch diff --git a/patches/server/0014-Remove-UseItemOnPacket-Too-Far-Check.patch b/patches/server/0012-Remove-UseItemOnPacket-Too-Far-Check.patch similarity index 89% rename from patches/server/0014-Remove-UseItemOnPacket-Too-Far-Check.patch rename to patches/server/0012-Remove-UseItemOnPacket-Too-Far-Check.patch index e016c1e9..819ebfb1 100644 --- a/patches/server/0014-Remove-UseItemOnPacket-Too-Far-Check.patch +++ b/patches/server/0012-Remove-UseItemOnPacket-Too-Far-Check.patch @@ -7,18 +7,18 @@ This Check is added in 1.17.x -> 1.18.x update by Mojang. By removing this check, it enable hackers to use some modules of hack clients. diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 5ee7fee40fd930424456f4f454e94bc88951b0d2..a157789fc6f3bdcbbf7321d5238c746833511630 100644 +index 79dba3796df83d0127db6e29f38a35e1d3289724..297a9c2eba28c66c511ba83daeaa3e8a07fa6504 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -190,6 +190,7 @@ import org.bukkit.craftbukkit.util.permissions.CraftDefaultPermissions; +@@ -189,6 +189,7 @@ import net.minecraft.world.phys.shapes.VoxelShape; + import org.bukkit.craftbukkit.util.permissions.CraftDefaultPermissions; + import org.apache.commons.lang3.StringUtils; import org.galemc.gale.configuration.GaleGlobalConfiguration; - import org.galemc.gale.executor.queue.BaseTaskQueues; - import org.galemc.gale.executor.queue.ScheduledServerThreadTaskQueues; +import org.dreeam.leaf.LeafConfig; import org.slf4j.Logger; // CraftBukkit start -@@ -2044,7 +2045,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -1977,7 +1978,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic Vec3 vec3d2 = vec3d.subtract(vec3d1); double d0 = 1.0000001D; diff --git a/patches/server/0015-KTP-Optimize-spigot-event-bus.patch b/patches/server/0013-KTP-Optimize-spigot-event-bus.patch similarity index 100% rename from patches/server/0015-KTP-Optimize-spigot-event-bus.patch rename to patches/server/0013-KTP-Optimize-spigot-event-bus.patch diff --git a/patches/server/0016-KeYi-Player-Skull-API.patch b/patches/server/0014-KeYi-Player-Skull-API.patch similarity index 89% rename from patches/server/0016-KeYi-Player-Skull-API.patch rename to patches/server/0014-KeYi-Player-Skull-API.patch index e94043c4..cf3b7574 100644 --- a/patches/server/0016-KeYi-Player-Skull-API.patch +++ b/patches/server/0014-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/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index e363891e8ab872ed24c557e3f94110f36c6fb277..e61ce64d14e0c216f9462ebbf640b1b60fdfcd87 100644 +index 548eddde8b0558b780f672d321507cfcbac92558..132d50e23ba713ab9178e17b34332b9af74f63f5 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -25,6 +25,11 @@ import java.util.Optional; @@ -30,10 +30,10 @@ index e363891e8ab872ed24c557e3f94110f36c6fb277..e61ce64d14e0c216f9462ebbf640b1b6 import org.bukkit.map.MapCursor; import org.bukkit.map.MapView; import org.bukkit.metadata.MetadataValue; -@@ -3304,4 +3310,28 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - this.getHandle().connection.send(packet); +@@ -3144,4 +3150,28 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + return this.spigot; } - // Purpur end + // Spigot end + + // KeYi start + @Override diff --git a/patches/server/0017-KeYi-Disable-arrow-despawn-counter-by-default.patch b/patches/server/0015-KeYi-Disable-arrow-despawn-counter-by-default.patch similarity index 90% rename from patches/server/0017-KeYi-Disable-arrow-despawn-counter-by-default.patch rename to patches/server/0015-KeYi-Disable-arrow-despawn-counter-by-default.patch index fb354781..15e2571b 100644 --- a/patches/server/0017-KeYi-Disable-arrow-despawn-counter-by-default.patch +++ b/patches/server/0015-KeYi-Disable-arrow-despawn-counter-by-default.patch @@ -7,10 +7,10 @@ Original license: MIT Original project: https://github.com/KeYiMC/KeYi diff --git a/src/main/java/org/galemc/gale/configuration/GaleWorldConfiguration.java b/src/main/java/org/galemc/gale/configuration/GaleWorldConfiguration.java -index 0b4621b60c2eee7201b38016063ec3db11706022..4892c6d9e1df2f25ae82c98bdabbe807e8c58b2e 100644 +index c03a6650d65ada6014019b09b29dc4d4f9b21286..961bb821d9e0fa795c8e086c9d2c6f5714fb14f9 100644 --- a/src/main/java/org/galemc/gale/configuration/GaleWorldConfiguration.java +++ b/src/main/java/org/galemc/gale/configuration/GaleWorldConfiguration.java -@@ -325,7 +325,7 @@ public class GaleWorldConfiguration extends ConfigurationPart { +@@ -323,7 +323,7 @@ public class GaleWorldConfiguration extends ConfigurationPart { public boolean entitiesCanEatBlocksInNonTickingChunks = false; // Gale - Purpur - prevent entities eating blocks in non-ticking chunks public boolean entitiesCanRandomStrollIntoNonTickingChunks = true; // Gale - MultiPaper - prevent entities random strolling into non-ticking chunks diff --git a/patches/server/0018-KeYi-Add-an-option-for-spigot-item-merging-mechanism.patch b/patches/server/0016-KeYi-Add-an-option-for-spigot-item-merging-mechanism.patch similarity index 94% rename from patches/server/0018-KeYi-Add-an-option-for-spigot-item-merging-mechanism.patch rename to patches/server/0016-KeYi-Add-an-option-for-spigot-item-merging-mechanism.patch index 5d677d87..37c8f0eb 100644 --- a/patches/server/0018-KeYi-Add-an-option-for-spigot-item-merging-mechanism.patch +++ b/patches/server/0016-KeYi-Add-an-option-for-spigot-item-merging-mechanism.patch @@ -7,7 +7,7 @@ Original license: MIT Original project: https://github.com/KeYiMC/KeYi diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -index bcfa484d9729242ae9e4dec2443d18d98dfd9243..5c08b2687f6588339a27cc3a8a80255473024014 100644 +index fb0cd2ad93d2dbd678662f2cdad0851fd698cf3d..a45dd00d9cf335156d67d711f3ac5d6e6fe67314 100644 --- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java @@ -38,6 +38,7 @@ import org.bukkit.event.entity.EntityPickupItemEvent; @@ -18,7 +18,7 @@ index bcfa484d9729242ae9e4dec2443d18d98dfd9243..5c08b2687f6588339a27cc3a8a802554 public class ItemEntity extends Entity implements TraceableEntity { -@@ -331,7 +332,7 @@ public class ItemEntity extends Entity implements TraceableEntity { +@@ -330,7 +331,7 @@ public class ItemEntity extends Entity implements TraceableEntity { ItemStack itemstack1 = other.getItem(); if (Objects.equals(this.target, other.target) && ItemEntity.areMergable(itemstack, itemstack1)) { diff --git a/patches/server/0019-Carpet-Fixes-Optimized-getBiome-method.patch b/patches/server/0017-Carpet-Fixes-Optimized-getBiome-method.patch similarity index 100% rename from patches/server/0019-Carpet-Fixes-Optimized-getBiome-method.patch rename to patches/server/0017-Carpet-Fixes-Optimized-getBiome-method.patch diff --git a/patches/server/0020-Carpet-Fixes-Use-optimized-RecipeManager.patch b/patches/server/0018-Carpet-Fixes-Use-optimized-RecipeManager.patch similarity index 100% rename from patches/server/0020-Carpet-Fixes-Use-optimized-RecipeManager.patch rename to patches/server/0018-Carpet-Fixes-Use-optimized-RecipeManager.patch diff --git a/patches/server/0023-Akarin-Save-Json-list-asynchronously.patch b/patches/server/0021-Akarin-Save-Json-list-asynchronously.patch similarity index 100% rename from patches/server/0023-Akarin-Save-Json-list-asynchronously.patch rename to patches/server/0021-Akarin-Save-Json-list-asynchronously.patch diff --git a/patches/server/0021-Petal-reduce-work-done-by-game-event-system.patch b/patches/server/0021-Petal-reduce-work-done-by-game-event-system.patch deleted file mode 100644 index 5985f4cd..00000000 --- a/patches/server/0021-Petal-reduce-work-done-by-game-event-system.patch +++ /dev/null @@ -1,192 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: peaches94 -Date: Sun, 10 Jul 2022 13:29:20 -0500 -Subject: [PATCH] Petal: reduce work done by game event system - -Original license: GPL v3 -Original project: https://github.com/Bloom-host/Petal - -1. going into game event dispatching can be expensive so run the checks before dispatching - -2. EuclideanGameEventListenerRegistry is not used concurrently so we ban that usage for improved performance with allays - -diff --git a/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java -index 902f2b39104bf059849228829bfe93b6dbc757d4..088bc38bfe82532a9ec80a96de8178dee21d07d6 100644 ---- a/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java -+++ b/src/main/java/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java -@@ -85,6 +85,13 @@ public class SculkCatalystBlockEntity extends BlockEntity implements GameEventLi - - } - -+ // petal start -+ @Override -+ public boolean listensToEvent(GameEvent gameEvent, GameEvent.Context context) { -+ return !this.isRemoved() && gameEvent == GameEvent.ENTITY_DIE && context.sourceEntity() instanceof LivingEntity; -+ } -+ // petal end -+ - public static void serverTick(Level world, BlockPos pos, BlockState state, SculkCatalystBlockEntity blockEntity) { - org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = blockEntity.getBlockPos(); // CraftBukkit - SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep. - blockEntity.sculkSpreader.updateCursors(world, pos, world.getRandom(), true); -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index fa9cb1312501689c70a3433e625b8704647e9273..27e4c92a3af2cf2396db6326859b3f6d62b5aaaf 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -83,7 +83,18 @@ public class LevelChunk extends ChunkAccess { - private Supplier fullStatus; - @Nullable - private LevelChunk.PostLoadProcessor postLoad; -- private final Int2ObjectMap gameEventListenerRegistrySections; -+ // petal start -+ private final GameEventListenerRegistry[] gameEventListenerRegistrySections; -+ private static final int GAME_EVENT_DISPATCHER_RADIUS = 2; -+ -+ private static int getGameEventSectionIndex(int sectionIndex) { -+ return sectionIndex + GAME_EVENT_DISPATCHER_RADIUS; -+ } -+ -+ private static int getGameEventSectionLength(int sectionCount) { -+ return sectionCount + (GAME_EVENT_DISPATCHER_RADIUS * 2); -+ } -+ // petal end - private final LevelChunkTicks blockTicks; - private final LevelChunkTicks fluidTicks; - -@@ -112,7 +123,7 @@ public class LevelChunk extends ChunkAccess { - this.tickersInLevel = Maps.newHashMap(); - this.clientLightReady = false; - this.level = (ServerLevel) world; // CraftBukkit - type -- this.gameEventListenerRegistrySections = new Int2ObjectOpenHashMap(); -+ this.gameEventListenerRegistrySections = new GameEventListenerRegistry[getGameEventSectionLength(this.getSectionsCount())]; // petal - Heightmap.Types[] aheightmap_type = Heightmap.Types.values(); - int j = aheightmap_type.length; - -@@ -438,9 +449,23 @@ public class LevelChunk extends ChunkAccess { - if (world instanceof ServerLevel) { - ServerLevel worldserver = (ServerLevel) world; - -- return (GameEventListenerRegistry) this.gameEventListenerRegistrySections.computeIfAbsent(ySectionCoord, (j) -> { -- return new EuclideanGameEventListenerRegistry(worldserver); -- }); -+ // petal start -+ int sectionIndex = getGameEventSectionIndex(this.getSectionIndexFromSectionY(ySectionCoord)); -+ -+ // drop game events that are too far away (32 blocks) from loaded sections -+ // this matches the highest radius of game events in the game -+ if (sectionIndex < 0 || sectionIndex >= this.gameEventListenerRegistrySections.length) { -+ return GameEventListenerRegistry.NOOP; -+ } -+ -+ var dispatcher = this.gameEventListenerRegistrySections[sectionIndex]; -+ -+ if (dispatcher == null) { -+ dispatcher = this.gameEventListenerRegistrySections[sectionIndex] = new EuclideanGameEventListenerRegistry(worldserver); -+ } -+ -+ return dispatcher; -+ // petal end - } else { - return super.getListenerRegistry(ySectionCoord); - } -@@ -804,7 +829,7 @@ public class LevelChunk extends ChunkAccess { - - gameeventlistenerregistry.unregister(gameeventlistener); - if (gameeventlistenerregistry.isEmpty()) { -- this.gameEventListenerRegistrySections.remove(i); -+ this.gameEventListenerRegistrySections[getGameEventSectionIndex(this.getSectionIndexFromSectionY(i))] = null; // petal - } - } - } -diff --git a/src/main/java/net/minecraft/world/level/gameevent/EuclideanGameEventListenerRegistry.java b/src/main/java/net/minecraft/world/level/gameevent/EuclideanGameEventListenerRegistry.java -index 878a42695ecedf0c3f2e6310e3ce44c6b6c36858..3c3168284bfc22dab4037a861aff9f1712cb1c05 100644 ---- a/src/main/java/net/minecraft/world/level/gameevent/EuclideanGameEventListenerRegistry.java -+++ b/src/main/java/net/minecraft/world/level/gameevent/EuclideanGameEventListenerRegistry.java -@@ -12,8 +12,8 @@ import net.minecraft.world.phys.Vec3; - - public class EuclideanGameEventListenerRegistry implements GameEventListenerRegistry { - private final List listeners = Lists.newArrayList(); -- private final Set listenersToRemove = Sets.newHashSet(); -- private final List listenersToAdd = Lists.newArrayList(); -+ //private final Set listenersToRemove = Sets.newHashSet(); // petal - not necessary -+ //private final List listenersToAdd = Lists.newArrayList(); // petal - private boolean processing; - private final ServerLevel level; - -@@ -29,7 +29,7 @@ public class EuclideanGameEventListenerRegistry implements GameEventListenerRegi - @Override - public void register(GameEventListener listener) { - if (this.processing) { -- this.listenersToAdd.add(listener); -+ throw new java.util.ConcurrentModificationException(); // petal - disallow concurrent modification - } else { - this.listeners.add(listener); - } -@@ -40,7 +40,7 @@ public class EuclideanGameEventListenerRegistry implements GameEventListenerRegi - @Override - public void unregister(GameEventListener listener) { - if (this.processing) { -- this.listenersToRemove.add(listener); -+ throw new java.util.ConcurrentModificationException(); // petal - disallow concurrent modification - } else { - this.listeners.remove(listener); - } -@@ -57,7 +57,7 @@ public class EuclideanGameEventListenerRegistry implements GameEventListenerRegi - - while(iterator.hasNext()) { - GameEventListener gameEventListener = iterator.next(); -- if (this.listenersToRemove.remove(gameEventListener)) { -+ if (false) { // petal - disallow concurrent modification - iterator.remove(); - } else { - Optional optional = getPostableListenerPosition(this.level, pos, gameEventListener); -@@ -71,6 +71,8 @@ public class EuclideanGameEventListenerRegistry implements GameEventListenerRegi - this.processing = false; - } - -+ // petal start -+ /* - if (!this.listenersToAdd.isEmpty()) { - this.listeners.addAll(this.listenersToAdd); - this.listenersToAdd.clear(); -@@ -80,6 +82,8 @@ public class EuclideanGameEventListenerRegistry implements GameEventListenerRegi - this.listeners.removeAll(this.listenersToRemove); - this.listenersToRemove.clear(); - } -+ */ -+ // petal end - - return bl; - } -diff --git a/src/main/java/net/minecraft/world/level/gameevent/GameEventListener.java b/src/main/java/net/minecraft/world/level/gameevent/GameEventListener.java -index a6689c03777c2b4b79dcafcae5d70c949519cd8e..f94169a3e177251a05644e3e384ac0514efd3719 100644 ---- a/src/main/java/net/minecraft/world/level/gameevent/GameEventListener.java -+++ b/src/main/java/net/minecraft/world/level/gameevent/GameEventListener.java -@@ -18,4 +18,10 @@ public interface GameEventListener { - UNSPECIFIED, - BY_DISTANCE; - } -+ -+ // petal start - add check for seeing if this listener cares about an event -+ default boolean listensToEvent(net.minecraft.world.level.gameevent.GameEvent gameEvent, net.minecraft.world.level.gameevent.GameEvent.Context context) { -+ return true; -+ } -+ // petal end - } -diff --git a/src/main/java/net/minecraft/world/level/gameevent/vibrations/VibrationListener.java b/src/main/java/net/minecraft/world/level/gameevent/vibrations/VibrationListener.java -index 103e12ec589dcbe6dbad7432b50e0644c3a37b1b..c90a827ca260d5f010c699488cba0dd00f9e97e4 100644 ---- a/src/main/java/net/minecraft/world/level/gameevent/vibrations/VibrationListener.java -+++ b/src/main/java/net/minecraft/world/level/gameevent/vibrations/VibrationListener.java -@@ -228,6 +228,13 @@ public class VibrationListener implements GameEventListener { - return true; - } - -+ // petal start -+ @Override -+ public boolean listensToEvent(GameEvent gameEvent, GameEvent.Context context) { -+ return this.currentVibration == null && gameEvent.is(this.config.getListenableEvents()); -+ } -+ // petal end -+ - public interface VibrationListenerConfig { - - default TagKey getListenableEvents() { diff --git a/patches/server/0022-Petal-Reduce-sensor-work.patch b/patches/server/0022-Petal-Reduce-sensor-work.patch deleted file mode 100644 index ee289918..00000000 --- a/patches/server/0022-Petal-Reduce-sensor-work.patch +++ /dev/null @@ -1,65 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: peaches94 -Date: Sun, 10 Jul 2022 15:44:38 -0500 -Subject: [PATCH] Petal: Reduce sensor work - -Original license: GPL v3 -Original project: https://github.com/Bloom-host/Petal - -this patch is focused around the sensors used for ai -delete the line of sight cache less often and use a faster nearby comparison - -diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 89a2e52dd1d93a9521d48218711f92305b0d7848..44248144491354b8ae0ebfede1de3091c06aa8f8 100644 ---- a/src/main/java/net/minecraft/world/entity/LivingEntity.java -+++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -1019,20 +1019,21 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - - if (entity != null) { -- ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD); -+ // petal start - only do itemstack lookup if we need to -+ //ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD); - EntityType entitytypes = entity.getType(); - - // Purpur start -- if (entitytypes == EntityType.SKELETON && itemstack.is(Items.SKELETON_SKULL)) { -+ if (entitytypes == EntityType.SKELETON && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.SKELETON_SKULL)) { - d0 *= entity.level.purpurConfig.skeletonHeadVisibilityPercent; - } -- else if (entitytypes == EntityType.ZOMBIE && itemstack.is(Items.ZOMBIE_HEAD)) { -+ else if (entitytypes == EntityType.ZOMBIE && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.ZOMBIE_HEAD)) { - d0 *= entity.level.purpurConfig.zombieHeadVisibilityPercent; - } -- else if (entitytypes == EntityType.CREEPER && itemstack.is(Items.CREEPER_HEAD)) { -+ else if (entitytypes == EntityType.CREEPER && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.CREEPER_HEAD)) { - d0 *= entity.level.purpurConfig.creeperHeadVisibilityPercent; - } -- else if ((entitytypes == EntityType.PIGLIN || entitytypes == EntityType.PIGLIN_BRUTE) && itemstack.is(Items.PIGLIN_HEAD)) { -+ else if ((entitytypes == EntityType.PIGLIN || entitytypes == EntityType.PIGLIN_BRUTE) && this.getItemBySlot(EquipmentSlot.HEAD).is(Items.PIGLIN_HEAD)) { - d0 *= entity.level.purpurConfig.piglinHeadVisibilityPercent; - } - // Purpur end -@@ -1047,6 +1048,7 @@ public abstract class LivingEntity extends Entity implements Attackable { - } - } - // Purpur end -+ // petal end - } - - return d0; -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index ad56f2ecd8bb74d94974fdd05f4550b48e6b8d9e..2f2b0c7a2ed23b956668cb1aa15eda9747a70afe 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -928,8 +928,8 @@ public abstract class Mob extends LivingEntity implements Targeting { - return; - } - // Paper end -- this.sensing.tick(); -- int i = this.level.getServer().getTickCount() + this.getId(); -+ int i = this.level.getServer().getTickCount() + this.getId(); // petal - move up -+ if (i % 10 == 0) this.sensing.tick(); // petal - only refresh line of sight cache every half second - - if (i % 2 != 0 && this.tickCount > 1) { - if (this.targetSelector.inactiveTick(this.activatedPriority, false)) // Pufferfish - use this to alternate ticking diff --git a/patches/server/0024-Slice-Smooth-Teleports.patch b/patches/server/0022-Slice-Smooth-Teleports.patch similarity index 65% rename from patches/server/0024-Slice-Smooth-Teleports.patch rename to patches/server/0022-Slice-Smooth-Teleports.patch index d217a6d5..2d47072b 100644 --- a/patches/server/0024-Slice-Smooth-Teleports.patch +++ b/patches/server/0022-Slice-Smooth-Teleports.patch @@ -7,41 +7,41 @@ Original license: MIT Original project: https://github.com/Cryptite/Slice diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 97f29244a4e7cf4edb346b9f0d8d61f320fe9ee0..ae1931d4756f237c3f3ad74eefabfc8de84cde40 100644 +index f4cc145f2670dee5893eeb1891b3fb8bfadd9b36..25fe104b1bc92ae6b21000c9accdfdb02493fe1f 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -283,6 +283,7 @@ public class ServerPlayer extends Player { - private boolean ramBar = false; // Purpur - private boolean tpsBar = false; // Purpur - private boolean compassBar = false; // Purpur +@@ -278,6 +278,7 @@ 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 smoothWorldTeleport; // Slice - public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile) { - super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile); + private final java.util.concurrent.atomic.AtomicReference viewDistances = new java.util.concurrent.atomic.AtomicReference<>(new io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.ViewDistances(-1, -1, -1)); + public io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.PlayerChunkLoaderData chunkLoader; diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 4e1db6c64254eeef8579d4cae5919b5a6a18d200..297141cb63786dc356a70ee8af2614fe060b9373 100644 +index 88d350ecfb16e34d6710ad7b17ce7438e4dbceb9..0669c806b8c6b150183b4e02146f970bc4c3e225 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -989,12 +989,12 @@ public abstract class PlayerList { +@@ -942,12 +942,12 @@ public abstract class PlayerList { int i = flag ? 1 : 0; // CraftBukkit start LevelData worlddata = worldserver1.getLevelData(); -- entityplayer1.connection.send(new ClientboundRespawnPacket(worldserver1.dimensionTypeId(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), entityplayer1.gameMode.getGameModeForPlayer(), entityplayer1.gameMode.getPreviousGameModeForPlayer(), worldserver1.isDebug(), worldserver1.isFlat(), (byte) i, entityplayer1.getLastDeathLocation())); -+ if (!entityplayer.smoothWorldTeleport) entityplayer1.connection.send(new ClientboundRespawnPacket(worldserver1.dimensionTypeId(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), entityplayer1.gameMode.getGameModeForPlayer(), entityplayer1.gameMode.getPreviousGameModeForPlayer(), worldserver1.isDebug(), worldserver1.isFlat(), (byte) i, entityplayer1.getLastDeathLocation())); - entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance())); // Spigot // Paper - replace old player chunk management - entityplayer1.connection.send(new ClientboundSetSimulationDistancePacket(worldserver1.getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance())); // Spigot // Paper - replace old player chunk management +- entityplayer1.connection.send(new ClientboundRespawnPacket(worldserver1.dimensionTypeId(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), entityplayer1.gameMode.getGameModeForPlayer(), entityplayer1.gameMode.getPreviousGameModeForPlayer(), worldserver1.isDebug(), worldserver1.isFlat(), (byte) i, entityplayer1.getLastDeathLocation(), entityplayer1.getPortalCooldown())); ++ if (!entityplayer.smoothWorldTeleport) entityplayer1.connection.send(new ClientboundRespawnPacket(worldserver1.dimensionTypeId(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), entityplayer1.gameMode.getGameModeForPlayer(), entityplayer1.gameMode.getPreviousGameModeForPlayer(), worldserver1.isDebug(), worldserver1.isFlat(), (byte) i, entityplayer1.getLastDeathLocation(), entityplayer1.getPortalCooldown())); // Slice + entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.getWorld().getSendViewDistance())); // Spigot // Paper - replace old player chunk management + entityplayer1.connection.send(new ClientboundSetSimulationDistancePacket(worldserver1.getWorld().getSimulationDistance())); // Spigot // Paper - replace old player chunk management entityplayer1.spawnIn(worldserver1); entityplayer1.unsetRemoved(); - entityplayer1.connection.teleport(CraftLocation.toBukkit(entityplayer1.position(), worldserver1.getWorld(), entityplayer1.getYRot(), entityplayer1.getXRot())); -+ if (!entityplayer.smoothWorldTeleport) entityplayer1.connection.teleport(CraftLocation.toBukkit(entityplayer1.position(), worldserver1.getWorld(), entityplayer1.getYRot(), entityplayer1.getXRot())); ++ if (!entityplayer.smoothWorldTeleport) entityplayer1.connection.teleport(CraftLocation.toBukkit(entityplayer1.position(), worldserver1.getWorld(), entityplayer1.getYRot(), entityplayer1.getXRot())); // Slice entityplayer1.setShiftKeyDown(false); // entityplayer1.connection.teleport(entityplayer1.getX(), entityplayer1.getY(), entityplayer1.getZ(), entityplayer1.getYRot(), entityplayer1.getXRot()); diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index e61ce64d14e0c216f9462ebbf640b1b60fdfcd87..8467f815b17e3a9ed28c525da63c0477a322462b 100644 +index 132d50e23ba713ab9178e17b34332b9af74f63f5..073f4e31bcd796484340dead39539b026e72680f 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1273,6 +1273,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -1242,6 +1242,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { // Paper end } diff --git a/patches/server/0025-PaperPR-Optimize-VarInts.patch b/patches/server/0023-PaperPR-Optimize-VarInts.patch similarity index 90% rename from patches/server/0025-PaperPR-Optimize-VarInts.patch rename to patches/server/0023-PaperPR-Optimize-VarInts.patch index d18a5140..0cee0253 100644 --- a/patches/server/0025-PaperPR-Optimize-VarInts.patch +++ b/patches/server/0023-PaperPR-Optimize-VarInts.patch @@ -8,10 +8,10 @@ Original project: https://github.com/PaperMC/Velocity Paper pull request: https://github.com/PaperMC/Paper/pull/8418 diff --git a/src/main/java/net/minecraft/network/FriendlyByteBuf.java b/src/main/java/net/minecraft/network/FriendlyByteBuf.java -index 1f4b64a5f812376c499c98cb4be62469bd0b7dbe..1f4320d8243cc64b86892c23c601d14101d5a7ad 100644 +index 9938bb90bef84cf784f9a1ceb02a1a45aa8b48a1..f5080a3716a65a7175b043c41f1e002322ecbf70 100644 --- a/src/main/java/net/minecraft/network/FriendlyByteBuf.java +++ b/src/main/java/net/minecraft/network/FriendlyByteBuf.java -@@ -105,6 +105,18 @@ public class FriendlyByteBuf extends ByteBuf { +@@ -103,6 +103,18 @@ public class FriendlyByteBuf extends ByteBuf { } public static int getVarIntSize(int value) { @@ -30,7 +30,7 @@ index 1f4b64a5f812376c499c98cb4be62469bd0b7dbe..1f4320d8243cc64b86892c23c601d141 for (int j = 1; j < 5; ++j) { if ((value & -1 << j * 7) == 0) { return j; -@@ -615,6 +627,21 @@ public class FriendlyByteBuf extends ByteBuf { +@@ -613,6 +625,21 @@ public class FriendlyByteBuf extends ByteBuf { } public FriendlyByteBuf writeVarInt(int value) { diff --git a/patches/server/0026-Parchment-Make-FixLight-use-action-bar.patch b/patches/server/0024-Parchment-Make-FixLight-use-action-bar.patch similarity index 96% rename from patches/server/0026-Parchment-Make-FixLight-use-action-bar.patch rename to patches/server/0024-Parchment-Make-FixLight-use-action-bar.patch index ad741022..570a4fef 100644 --- a/patches/server/0026-Parchment-Make-FixLight-use-action-bar.patch +++ b/patches/server/0024-Parchment-Make-FixLight-use-action-bar.patch @@ -7,7 +7,7 @@ Original license: GPLv3 Original project: https://github.com/ProjectEdenGG/Parchment diff --git a/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java -index 7784d72ddd6db00c674e22759c00c430222c4b85..b336789b74d6d6819b38e78cb2014f61de3f004b 100644 +index 463c6d8d5b114816ed9065558285945817c30385..d77fb29ea767fce5b8e00b76625188f19ecdbd24 100644 --- a/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java +++ b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java @@ -24,6 +24,7 @@ import static net.kyori.adventure.text.format.NamedTextColor.BLUE; diff --git a/patches/server/0027-Leaves-Server-Utils.patch b/patches/server/0025-Leaves-Server-Utils.patch similarity index 94% rename from patches/server/0027-Leaves-Server-Utils.patch rename to patches/server/0025-Leaves-Server-Utils.patch index 8f465aca..df4d4139 100644 --- a/patches/server/0027-Leaves-Server-Utils.patch +++ b/patches/server/0025-Leaves-Server-Utils.patch @@ -33,41 +33,41 @@ index 46954db7ecd35ac4018fdf476df7c8020d7ce6c8..044c51ebb058fc36074fd178929e3279 public PlayerAreaMap() { super(); diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 7fdcd4b78ff43050c157dea7e4fd7b9aad2cda5c..418686a2c3e64182558a7cc4204bdecd3fbbe564 100644 +index c6069b70d188a45950be27f9f6c63c8218dea7fa..09332e82647df95f3bdcfb998bff98e8ecfccb90 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -404,6 +404,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { +@@ -407,6 +407,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 + private CompoundTag leavesData = new CompoundTag(); // Leaves - Leaves ex data public void setOrigin(@javax.annotation.Nonnull Location location) { this.origin = location.toVector(); -@@ -2368,6 +2369,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - nbt.putBoolean("Purpur.FireImmune", immuneToFire); +@@ -2481,6 +2482,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + nbt.putBoolean("Paper.FreezeLock", true); } - // Purpur end + // Paper end + nbt.put("Leaves.Data", leavesData); // Leaves - leaves ex data return nbt; } catch (Throwable throwable) { CrashReport crashreport = CrashReport.forThrowable(throwable, "Saving entity NBT"); -@@ -2541,6 +2543,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - immuneToFire = nbt.getBoolean("Purpur.FireImmune"); +@@ -2649,6 +2651,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + freezeLocked = nbt.getBoolean("Paper.FreezeLock"); } - // Purpur end + // Paper end + // Leaves start - leaves ex data + if (nbt.contains("Leaves.Data")) { + leavesData = nbt.getCompound("Leaves.Data"); + } -+ // Leaves start - leaves ex data ++ // Leaves end - leaves ex data } catch (Throwable throwable) { CrashReport crashreport = CrashReport.forThrowable(throwable, "Loading entity NBT"); -@@ -4887,4 +4894,10 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { - return false; +@@ -4899,4 +4906,10 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource { + return ((net.minecraft.server.level.ServerChunkCache) level.getChunkSource()).isPositionTicking(this); } - // Purpur end + // Paper end + + // Leaves start - leaves ex data + public CompoundTag getLeavesData() { diff --git a/patches/server/0028-Leaves-Jade-Protocol.patch b/patches/server/0026-Leaves-Jade-Protocol.patch similarity index 96% rename from patches/server/0028-Leaves-Jade-Protocol.patch rename to patches/server/0026-Leaves-Jade-Protocol.patch index d865e9c5..0a3c6604 100644 --- a/patches/server/0028-Leaves-Jade-Protocol.patch +++ b/patches/server/0026-Leaves-Jade-Protocol.patch @@ -9,10 +9,10 @@ Original project: https://github.com/LeavesMC/Leaves This patch is Powered by Jade(https://github.com/Snownee/Jade) diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index a157789fc6f3bdcbbf7321d5238c746833511630..6d25421091018d1dc0c7bccbce31d8b4244639bb 100644 +index 297a9c2eba28c66c511ba83daeaa3e8a07fa6504..6155fa16a73de3314bc636b9af63106cf5b508e4 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -250,6 +250,7 @@ import org.bukkit.inventory.CraftingInventory; +@@ -249,6 +249,7 @@ import org.bukkit.inventory.CraftingInventory; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.InventoryView; import org.bukkit.inventory.SmithingInventory; @@ -20,7 +20,7 @@ index a157789fc6f3bdcbbf7321d5238c746833511630..6d25421091018d1dc0c7bccbce31d8b4 // CraftBukkit end public class ServerGamePacketListenerImpl implements ServerPlayerConnection, TickablePacketListener, ServerGamePacketListener { -@@ -3725,6 +3726,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic +@@ -3592,6 +3593,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic } // Paper end this.cserver.getMessenger().dispatchIncomingMessage(this.player.getBukkitEntity(), packet.identifier.toString(), data); @@ -31,10 +31,10 @@ index a157789fc6f3bdcbbf7321d5238c746833511630..6d25421091018d1dc0c7bccbce31d8b4 ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t dispatch custom payload", ex); this.disconnect("Invalid custom payload!", org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 297141cb63786dc356a70ee8af2614fe060b9373..e4be585e73f55ba0b2c8694df0694334e6573501 100644 +index 0669c806b8c6b150183b4e02146f970bc4c3e225..2bf8ba3463eab42dd70cee3c1cf0098ca1754e75 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -402,6 +402,8 @@ public abstract class PlayerList { +@@ -358,6 +358,8 @@ public abstract class PlayerList { player.didPlayerJoinEvent = true; // Gale - EMC - do not process chat/commands before player has joined @@ -44,10 +44,10 @@ index 297141cb63786dc356a70ee8af2614fe060b9373..e4be585e73f55ba0b2c8694df0694334 if (jm != null && !jm.equals(net.kyori.adventure.text.Component.empty())) { // Paper - Adventure 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 f85210c8d0d3e27398f52509f5e078f1d0d3eb93..911e83c6ce0166cea84dea92ae61d55462d7a9c0 100644 +index c83dabddf93249a6477c10725622119c939db4d5..bfb85d119355f99c96e8cebb0c9be43c6be4b6b4 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 -@@ -288,7 +288,7 @@ public class Tadpole extends AbstractFish { +@@ -251,7 +251,7 @@ public class Tadpole extends AbstractFish { } @@ -57,7 +57,7 @@ index f85210c8d0d3e27398f52509f5e078f1d0d3eb93..911e83c6ce0166cea84dea92ae61d554 } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index aa276ff3319b7eda1cf84de40924236175febb66..265b7272f20105bbe5eee6bb46022cb008ad9191 100644 +index ffdedab6808736ad96850d2e6eecf09fb758306a..8ac5414c04a601801bfcc872f809fb2bfeb306f9 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -254,6 +254,7 @@ import org.bukkit.scoreboard.Criteria; @@ -66,9 +66,9 @@ index aa276ff3319b7eda1cf84de40924236175febb66..265b7272f20105bbe5eee6bb46022cb0 import org.bukkit.util.permissions.DefaultPermissions; +import org.dreeam.leaf.LeafConfig; import org.yaml.snakeyaml.LoaderOptions; - import org.galemc.gale.configuration.GaleGlobalConfiguration; - import org.galemc.gale.util.CPUCoresEstimation; -@@ -409,6 +410,7 @@ public final class CraftServer implements Server { + import org.yaml.snakeyaml.Yaml; + import org.yaml.snakeyaml.constructor.SafeConstructor; +@@ -393,6 +394,7 @@ public final class CraftServer implements Server { MapPalette.setMapColorCache(new CraftMapColorCache(this.logger)); } datapackManager = new io.papermc.paper.datapack.PaperDatapackManager(console.getPackRepository()); // Paper @@ -76,16 +76,16 @@ index aa276ff3319b7eda1cf84de40924236175febb66..265b7272f20105bbe5eee6bb46022cb0 } public boolean getCommandBlockOverride(String command) { -@@ -1001,6 +1003,11 @@ public final class CraftServer implements Server { +@@ -984,6 +986,11 @@ public final class CraftServer implements Server { + org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings")); // Spigot this.console.paperConfigurations.reloadConfigs(this.console); - org.purpurmc.purpur.PurpurConfig.init((File) console.options.valueOf("purpur-settings")); // Purpur this.console.galeConfigurations.reloadConfigs(this.console); // Gale - Gale configuration + // Leaves start - Jade + if (LeafConfig.jadeProtocol) { + top.leavesmc.leaves.protocol.JadeProtocol.enableAllPlayer(); + } + // Leaves end - Jade - for (ServerLevel world : this.console.getAllLevelsArray()) { // Gale - base thread pool - optimize server levels + for (ServerLevel world : this.console.getAllLevels()) { // world.serverLevelData.setDifficulty(config.difficulty); // Paper - per level difficulty world.setSpawnSettings(world.serverLevelData.getDifficulty() != Difficulty.PEACEFUL && config.spawnMonsters, config.spawnAnimals); // Paper - per level difficulty (from MinecraftServer#setDifficulty(ServerLevel, Difficulty, boolean)) diff --git a/src/main/java/org/dreeam/leaf/LeafConfig.java b/src/main/java/org/dreeam/leaf/LeafConfig.java @@ -103,7 +103,7 @@ index 18976e4b84146422daf262af469abcaff5e6fbbe..f77a8b729846dc9be3adad659ccc02df } diff --git a/src/main/java/top/leavesmc/leaves/protocol/JadeProtocol.java b/src/main/java/top/leavesmc/leaves/protocol/JadeProtocol.java new file mode 100644 -index 0000000000000000000000000000000000000000..375b8a00accaf01c2be00080481e43c83979b005 +index 0000000000000000000000000000000000000000..fcbf73aaf404d9d28689748e2a6399fbac77845c --- /dev/null +++ b/src/main/java/top/leavesmc/leaves/protocol/JadeProtocol.java @@ -0,0 +1,572 @@ @@ -424,7 +424,7 @@ index 0000000000000000000000000000000000000000..375b8a00accaf01c2be00080481e43c8 + return; + } + -+ Level world = player.level; ++ Level world = player.level(); + Entity entity = world.getEntity(buf.readVarInt()); + boolean showDetails = buf.readBoolean(); + if (entity == null || player.distanceToSqr(entity) > MAX_DISTANCE_SQR) { @@ -460,7 +460,7 @@ index 0000000000000000000000000000000000000000..375b8a00accaf01c2be00080481e43c8 + + BlockPos pos = buf.readBlockPos(); + boolean showDetails = buf.readBoolean(); -+ Level world = player.level; ++ Level world = player.level(); + if (pos.distSqr(player.blockPosition()) > MAX_DISTANCE_SQR || !world.isLoaded(pos)) { + return; + } diff --git a/patches/server/0029-Leaves-Appleskin-Protocol.patch b/patches/server/0027-Leaves-Appleskin-Protocol.patch similarity index 84% rename from patches/server/0029-Leaves-Appleskin-Protocol.patch rename to patches/server/0027-Leaves-Appleskin-Protocol.patch index f42b2ed0..24666581 100644 --- a/patches/server/0029-Leaves-Appleskin-Protocol.patch +++ b/patches/server/0027-Leaves-Appleskin-Protocol.patch @@ -7,35 +7,23 @@ Original license: GPLv3 Original project: https://github.com/LeavesMC/Leaves diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 44a31750f08bee7e44565739fa1eaf6a78f10b3b..2d4a9ffd947e57e399a09bb3881c5e1cf7d0b031 100644 +index e178004a3360bf6841e6aa0c874ca8ef10587f49..c44fe9e25cbaeba46601ee6fd076792210b20adc 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1637,6 +1637,7 @@ public abstract class MinecraftServer extends MinecraftServerBlockableEventLoop - this.tickStep_tickConnection(); - this.tickStep_tickPlayerList(); - this.tickStep_tickGameTestTicker(); -+ this.tickStep_tickLeavesProtocol(); - this.tickStep_runTickables(); - } +@@ -1548,6 +1548,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop - +